1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
24 LanguageName, Override, ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(
2106 &DeleteToPreviousWordStart {
2107 ignore_newlines: false,
2108 },
2109 cx,
2110 );
2111 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2112 });
2113
2114 _ = view.update(cx, |view, cx| {
2115 view.change_selections(None, cx, |s| {
2116 s.select_display_ranges([
2117 // an empty selection - the following word fragment is deleted
2118 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2119 // characters selected - they are deleted
2120 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2121 ])
2122 });
2123 view.delete_to_next_word_end(
2124 &DeleteToNextWordEnd {
2125 ignore_newlines: false,
2126 },
2127 cx,
2128 );
2129 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2130 });
2131}
2132
2133#[gpui::test]
2134fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2135 init_test(cx, |_| {});
2136
2137 let view = cx.add_window(|cx| {
2138 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2139 build_editor(buffer.clone(), cx)
2140 });
2141 let del_to_prev_word_start = DeleteToPreviousWordStart {
2142 ignore_newlines: false,
2143 };
2144 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2145 ignore_newlines: true,
2146 };
2147
2148 _ = view.update(cx, |view, cx| {
2149 view.change_selections(None, cx, |s| {
2150 s.select_display_ranges([
2151 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2152 ])
2153 });
2154 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2155 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2156 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2157 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2158 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2159 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2160 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2161 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2162 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2163 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2164 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2165 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2166 });
2167}
2168
2169#[gpui::test]
2170fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2171 init_test(cx, |_| {});
2172
2173 let view = cx.add_window(|cx| {
2174 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2175 build_editor(buffer.clone(), cx)
2176 });
2177 let del_to_next_word_end = DeleteToNextWordEnd {
2178 ignore_newlines: false,
2179 };
2180 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2181 ignore_newlines: true,
2182 };
2183
2184 _ = view.update(cx, |view, cx| {
2185 view.change_selections(None, cx, |s| {
2186 s.select_display_ranges([
2187 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2188 ])
2189 });
2190 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2191 assert_eq!(
2192 view.buffer.read(cx).read(cx).text(),
2193 "one\n two\nthree\n four"
2194 );
2195 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2196 assert_eq!(
2197 view.buffer.read(cx).read(cx).text(),
2198 "\n two\nthree\n four"
2199 );
2200 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2201 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2202 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2203 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2204 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2205 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2206 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2207 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2208 });
2209}
2210
2211#[gpui::test]
2212fn test_newline(cx: &mut TestAppContext) {
2213 init_test(cx, |_| {});
2214
2215 let view = cx.add_window(|cx| {
2216 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2217 build_editor(buffer.clone(), cx)
2218 });
2219
2220 _ = view.update(cx, |view, cx| {
2221 view.change_selections(None, cx, |s| {
2222 s.select_display_ranges([
2223 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2224 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2225 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2226 ])
2227 });
2228
2229 view.newline(&Newline, cx);
2230 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2231 });
2232}
2233
2234#[gpui::test]
2235fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2236 init_test(cx, |_| {});
2237
2238 let editor = cx.add_window(|cx| {
2239 let buffer = MultiBuffer::build_simple(
2240 "
2241 a
2242 b(
2243 X
2244 )
2245 c(
2246 X
2247 )
2248 "
2249 .unindent()
2250 .as_str(),
2251 cx,
2252 );
2253 let mut editor = build_editor(buffer.clone(), cx);
2254 editor.change_selections(None, cx, |s| {
2255 s.select_ranges([
2256 Point::new(2, 4)..Point::new(2, 5),
2257 Point::new(5, 4)..Point::new(5, 5),
2258 ])
2259 });
2260 editor
2261 });
2262
2263 _ = editor.update(cx, |editor, cx| {
2264 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2265 editor.buffer.update(cx, |buffer, cx| {
2266 buffer.edit(
2267 [
2268 (Point::new(1, 2)..Point::new(3, 0), ""),
2269 (Point::new(4, 2)..Point::new(6, 0), ""),
2270 ],
2271 None,
2272 cx,
2273 );
2274 assert_eq!(
2275 buffer.read(cx).text(),
2276 "
2277 a
2278 b()
2279 c()
2280 "
2281 .unindent()
2282 );
2283 });
2284 assert_eq!(
2285 editor.selections.ranges(cx),
2286 &[
2287 Point::new(1, 2)..Point::new(1, 2),
2288 Point::new(2, 2)..Point::new(2, 2),
2289 ],
2290 );
2291
2292 editor.newline(&Newline, cx);
2293 assert_eq!(
2294 editor.text(cx),
2295 "
2296 a
2297 b(
2298 )
2299 c(
2300 )
2301 "
2302 .unindent()
2303 );
2304
2305 // The selections are moved after the inserted newlines
2306 assert_eq!(
2307 editor.selections.ranges(cx),
2308 &[
2309 Point::new(2, 0)..Point::new(2, 0),
2310 Point::new(4, 0)..Point::new(4, 0),
2311 ],
2312 );
2313 });
2314}
2315
2316#[gpui::test]
2317async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2318 init_test(cx, |settings| {
2319 settings.defaults.tab_size = NonZeroU32::new(4)
2320 });
2321
2322 let language = Arc::new(
2323 Language::new(
2324 LanguageConfig::default(),
2325 Some(tree_sitter_rust::LANGUAGE.into()),
2326 )
2327 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2328 .unwrap(),
2329 );
2330
2331 let mut cx = EditorTestContext::new(cx).await;
2332 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2333 cx.set_state(indoc! {"
2334 const a: ˇA = (
2335 (ˇ
2336 «const_functionˇ»(ˇ),
2337 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2338 )ˇ
2339 ˇ);ˇ
2340 "});
2341
2342 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2343 cx.assert_editor_state(indoc! {"
2344 ˇ
2345 const a: A = (
2346 ˇ
2347 (
2348 ˇ
2349 ˇ
2350 const_function(),
2351 ˇ
2352 ˇ
2353 ˇ
2354 ˇ
2355 something_else,
2356 ˇ
2357 )
2358 ˇ
2359 ˇ
2360 );
2361 "});
2362}
2363
2364#[gpui::test]
2365async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2366 init_test(cx, |settings| {
2367 settings.defaults.tab_size = NonZeroU32::new(4)
2368 });
2369
2370 let language = Arc::new(
2371 Language::new(
2372 LanguageConfig::default(),
2373 Some(tree_sitter_rust::LANGUAGE.into()),
2374 )
2375 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2376 .unwrap(),
2377 );
2378
2379 let mut cx = EditorTestContext::new(cx).await;
2380 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2381 cx.set_state(indoc! {"
2382 const a: ˇA = (
2383 (ˇ
2384 «const_functionˇ»(ˇ),
2385 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2386 )ˇ
2387 ˇ);ˇ
2388 "});
2389
2390 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2391 cx.assert_editor_state(indoc! {"
2392 const a: A = (
2393 ˇ
2394 (
2395 ˇ
2396 const_function(),
2397 ˇ
2398 ˇ
2399 something_else,
2400 ˇ
2401 ˇ
2402 ˇ
2403 ˇ
2404 )
2405 ˇ
2406 );
2407 ˇ
2408 ˇ
2409 "});
2410}
2411
2412#[gpui::test]
2413async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2414 init_test(cx, |settings| {
2415 settings.defaults.tab_size = NonZeroU32::new(4)
2416 });
2417
2418 let language = Arc::new(Language::new(
2419 LanguageConfig {
2420 line_comments: vec!["//".into()],
2421 ..LanguageConfig::default()
2422 },
2423 None,
2424 ));
2425 {
2426 let mut cx = EditorTestContext::new(cx).await;
2427 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2428 cx.set_state(indoc! {"
2429 // Fooˇ
2430 "});
2431
2432 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2433 cx.assert_editor_state(indoc! {"
2434 // Foo
2435 //ˇ
2436 "});
2437 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2438 cx.set_state(indoc! {"
2439 ˇ// Foo
2440 "});
2441 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2442 cx.assert_editor_state(indoc! {"
2443
2444 ˇ// Foo
2445 "});
2446 }
2447 // Ensure that comment continuations can be disabled.
2448 update_test_language_settings(cx, |settings| {
2449 settings.defaults.extend_comment_on_newline = Some(false);
2450 });
2451 let mut cx = EditorTestContext::new(cx).await;
2452 cx.set_state(indoc! {"
2453 // Fooˇ
2454 "});
2455 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2456 cx.assert_editor_state(indoc! {"
2457 // Foo
2458 ˇ
2459 "});
2460}
2461
2462#[gpui::test]
2463fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|cx| {
2467 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2468 let mut editor = build_editor(buffer.clone(), cx);
2469 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2470 editor
2471 });
2472
2473 _ = editor.update(cx, |editor, cx| {
2474 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2475 editor.buffer.update(cx, |buffer, cx| {
2476 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2477 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2478 });
2479 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2480
2481 editor.insert("Z", cx);
2482 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2483
2484 // The selections are moved after the inserted characters
2485 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2486 });
2487}
2488
2489#[gpui::test]
2490async fn test_tab(cx: &mut gpui::TestAppContext) {
2491 init_test(cx, |settings| {
2492 settings.defaults.tab_size = NonZeroU32::new(3)
2493 });
2494
2495 let mut cx = EditorTestContext::new(cx).await;
2496 cx.set_state(indoc! {"
2497 ˇabˇc
2498 ˇ🏀ˇ🏀ˇefg
2499 dˇ
2500 "});
2501 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2502 cx.assert_editor_state(indoc! {"
2503 ˇab ˇc
2504 ˇ🏀 ˇ🏀 ˇefg
2505 d ˇ
2506 "});
2507
2508 cx.set_state(indoc! {"
2509 a
2510 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2511 "});
2512 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2513 cx.assert_editor_state(indoc! {"
2514 a
2515 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2516 "});
2517}
2518
2519#[gpui::test]
2520async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2521 init_test(cx, |_| {});
2522
2523 let mut cx = EditorTestContext::new(cx).await;
2524 let language = Arc::new(
2525 Language::new(
2526 LanguageConfig::default(),
2527 Some(tree_sitter_rust::LANGUAGE.into()),
2528 )
2529 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2530 .unwrap(),
2531 );
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533
2534 // cursors that are already at the suggested indent level insert
2535 // a soft tab. cursors that are to the left of the suggested indent
2536 // auto-indent their line.
2537 cx.set_state(indoc! {"
2538 ˇ
2539 const a: B = (
2540 c(
2541 d(
2542 ˇ
2543 )
2544 ˇ
2545 ˇ )
2546 );
2547 "});
2548 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2549 cx.assert_editor_state(indoc! {"
2550 ˇ
2551 const a: B = (
2552 c(
2553 d(
2554 ˇ
2555 )
2556 ˇ
2557 ˇ)
2558 );
2559 "});
2560
2561 // handle auto-indent when there are multiple cursors on the same line
2562 cx.set_state(indoc! {"
2563 const a: B = (
2564 c(
2565 ˇ ˇ
2566 ˇ )
2567 );
2568 "});
2569 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2570 cx.assert_editor_state(indoc! {"
2571 const a: B = (
2572 c(
2573 ˇ
2574 ˇ)
2575 );
2576 "});
2577}
2578
2579#[gpui::test]
2580async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2581 init_test(cx, |settings| {
2582 settings.defaults.tab_size = NonZeroU32::new(4)
2583 });
2584
2585 let language = Arc::new(
2586 Language::new(
2587 LanguageConfig::default(),
2588 Some(tree_sitter_rust::LANGUAGE.into()),
2589 )
2590 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2591 .unwrap(),
2592 );
2593
2594 let mut cx = EditorTestContext::new(cx).await;
2595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2596 cx.set_state(indoc! {"
2597 fn a() {
2598 if b {
2599 \t ˇc
2600 }
2601 }
2602 "});
2603
2604 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2605 cx.assert_editor_state(indoc! {"
2606 fn a() {
2607 if b {
2608 ˇc
2609 }
2610 }
2611 "});
2612}
2613
2614#[gpui::test]
2615async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2616 init_test(cx, |settings| {
2617 settings.defaults.tab_size = NonZeroU32::new(4);
2618 });
2619
2620 let mut cx = EditorTestContext::new(cx).await;
2621
2622 cx.set_state(indoc! {"
2623 «oneˇ» «twoˇ»
2624 three
2625 four
2626 "});
2627 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2628 cx.assert_editor_state(indoc! {"
2629 «oneˇ» «twoˇ»
2630 three
2631 four
2632 "});
2633
2634 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2635 cx.assert_editor_state(indoc! {"
2636 «oneˇ» «twoˇ»
2637 three
2638 four
2639 "});
2640
2641 // select across line ending
2642 cx.set_state(indoc! {"
2643 one two
2644 t«hree
2645 ˇ» four
2646 "});
2647 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2648 cx.assert_editor_state(indoc! {"
2649 one two
2650 t«hree
2651 ˇ» four
2652 "});
2653
2654 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2655 cx.assert_editor_state(indoc! {"
2656 one two
2657 t«hree
2658 ˇ» four
2659 "});
2660
2661 // Ensure that indenting/outdenting works when the cursor is at column 0.
2662 cx.set_state(indoc! {"
2663 one two
2664 ˇthree
2665 four
2666 "});
2667 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2668 cx.assert_editor_state(indoc! {"
2669 one two
2670 ˇthree
2671 four
2672 "});
2673
2674 cx.set_state(indoc! {"
2675 one two
2676 ˇ three
2677 four
2678 "});
2679 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2680 cx.assert_editor_state(indoc! {"
2681 one two
2682 ˇthree
2683 four
2684 "});
2685}
2686
2687#[gpui::test]
2688async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2689 init_test(cx, |settings| {
2690 settings.defaults.hard_tabs = Some(true);
2691 });
2692
2693 let mut cx = EditorTestContext::new(cx).await;
2694
2695 // select two ranges on one line
2696 cx.set_state(indoc! {"
2697 «oneˇ» «twoˇ»
2698 three
2699 four
2700 "});
2701 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2702 cx.assert_editor_state(indoc! {"
2703 \t«oneˇ» «twoˇ»
2704 three
2705 four
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 \t\t«oneˇ» «twoˇ»
2710 three
2711 four
2712 "});
2713 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2714 cx.assert_editor_state(indoc! {"
2715 \t«oneˇ» «twoˇ»
2716 three
2717 four
2718 "});
2719 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2720 cx.assert_editor_state(indoc! {"
2721 «oneˇ» «twoˇ»
2722 three
2723 four
2724 "});
2725
2726 // select across a line ending
2727 cx.set_state(indoc! {"
2728 one two
2729 t«hree
2730 ˇ»four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 one two
2735 \tt«hree
2736 ˇ»four
2737 "});
2738 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2739 cx.assert_editor_state(indoc! {"
2740 one two
2741 \t\tt«hree
2742 ˇ»four
2743 "});
2744 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2745 cx.assert_editor_state(indoc! {"
2746 one two
2747 \tt«hree
2748 ˇ»four
2749 "});
2750 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2751 cx.assert_editor_state(indoc! {"
2752 one two
2753 t«hree
2754 ˇ»four
2755 "});
2756
2757 // Ensure that indenting/outdenting works when the cursor is at column 0.
2758 cx.set_state(indoc! {"
2759 one two
2760 ˇthree
2761 four
2762 "});
2763 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2764 cx.assert_editor_state(indoc! {"
2765 one two
2766 ˇthree
2767 four
2768 "});
2769 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2770 cx.assert_editor_state(indoc! {"
2771 one two
2772 \tˇthree
2773 four
2774 "});
2775 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2776 cx.assert_editor_state(indoc! {"
2777 one two
2778 ˇthree
2779 four
2780 "});
2781}
2782
2783#[gpui::test]
2784fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2785 init_test(cx, |settings| {
2786 settings.languages.extend([
2787 (
2788 "TOML".into(),
2789 LanguageSettingsContent {
2790 tab_size: NonZeroU32::new(2),
2791 ..Default::default()
2792 },
2793 ),
2794 (
2795 "Rust".into(),
2796 LanguageSettingsContent {
2797 tab_size: NonZeroU32::new(4),
2798 ..Default::default()
2799 },
2800 ),
2801 ]);
2802 });
2803
2804 let toml_language = Arc::new(Language::new(
2805 LanguageConfig {
2806 name: "TOML".into(),
2807 ..Default::default()
2808 },
2809 None,
2810 ));
2811 let rust_language = Arc::new(Language::new(
2812 LanguageConfig {
2813 name: "Rust".into(),
2814 ..Default::default()
2815 },
2816 None,
2817 ));
2818
2819 let toml_buffer =
2820 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2821 let rust_buffer = cx.new_model(|cx| {
2822 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2823 });
2824 let multibuffer = cx.new_model(|cx| {
2825 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2826 multibuffer.push_excerpts(
2827 toml_buffer.clone(),
2828 [ExcerptRange {
2829 context: Point::new(0, 0)..Point::new(2, 0),
2830 primary: None,
2831 }],
2832 cx,
2833 );
2834 multibuffer.push_excerpts(
2835 rust_buffer.clone(),
2836 [ExcerptRange {
2837 context: Point::new(0, 0)..Point::new(1, 0),
2838 primary: None,
2839 }],
2840 cx,
2841 );
2842 multibuffer
2843 });
2844
2845 cx.add_window(|cx| {
2846 let mut editor = build_editor(multibuffer, cx);
2847
2848 assert_eq!(
2849 editor.text(cx),
2850 indoc! {"
2851 a = 1
2852 b = 2
2853
2854 const c: usize = 3;
2855 "}
2856 );
2857
2858 select_ranges(
2859 &mut editor,
2860 indoc! {"
2861 «aˇ» = 1
2862 b = 2
2863
2864 «const c:ˇ» usize = 3;
2865 "},
2866 cx,
2867 );
2868
2869 editor.tab(&Tab, cx);
2870 assert_text_with_selections(
2871 &mut editor,
2872 indoc! {"
2873 «aˇ» = 1
2874 b = 2
2875
2876 «const c:ˇ» usize = 3;
2877 "},
2878 cx,
2879 );
2880 editor.tab_prev(&TabPrev, cx);
2881 assert_text_with_selections(
2882 &mut editor,
2883 indoc! {"
2884 «aˇ» = 1
2885 b = 2
2886
2887 «const c:ˇ» usize = 3;
2888 "},
2889 cx,
2890 );
2891
2892 editor
2893 });
2894}
2895
2896#[gpui::test]
2897async fn test_backspace(cx: &mut gpui::TestAppContext) {
2898 init_test(cx, |_| {});
2899
2900 let mut cx = EditorTestContext::new(cx).await;
2901
2902 // Basic backspace
2903 cx.set_state(indoc! {"
2904 onˇe two three
2905 fou«rˇ» five six
2906 seven «ˇeight nine
2907 »ten
2908 "});
2909 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2910 cx.assert_editor_state(indoc! {"
2911 oˇe two three
2912 fouˇ five six
2913 seven ˇten
2914 "});
2915
2916 // Test backspace inside and around indents
2917 cx.set_state(indoc! {"
2918 zero
2919 ˇone
2920 ˇtwo
2921 ˇ ˇ ˇ three
2922 ˇ ˇ four
2923 "});
2924 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2925 cx.assert_editor_state(indoc! {"
2926 zero
2927 ˇone
2928 ˇtwo
2929 ˇ threeˇ four
2930 "});
2931
2932 // Test backspace with line_mode set to true
2933 cx.update_editor(|e, _| e.selections.line_mode = true);
2934 cx.set_state(indoc! {"
2935 The ˇquick ˇbrown
2936 fox jumps over
2937 the lazy dog
2938 ˇThe qu«ick bˇ»rown"});
2939 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2940 cx.assert_editor_state(indoc! {"
2941 ˇfox jumps over
2942 the lazy dogˇ"});
2943}
2944
2945#[gpui::test]
2946async fn test_delete(cx: &mut gpui::TestAppContext) {
2947 init_test(cx, |_| {});
2948
2949 let mut cx = EditorTestContext::new(cx).await;
2950 cx.set_state(indoc! {"
2951 onˇe two three
2952 fou«rˇ» five six
2953 seven «ˇeight nine
2954 »ten
2955 "});
2956 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2957 cx.assert_editor_state(indoc! {"
2958 onˇ two three
2959 fouˇ five six
2960 seven ˇten
2961 "});
2962
2963 // Test backspace with line_mode set to true
2964 cx.update_editor(|e, _| e.selections.line_mode = true);
2965 cx.set_state(indoc! {"
2966 The ˇquick ˇbrown
2967 fox «ˇjum»ps over
2968 the lazy dog
2969 ˇThe qu«ick bˇ»rown"});
2970 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2971 cx.assert_editor_state("ˇthe lazy dogˇ");
2972}
2973
2974#[gpui::test]
2975fn test_delete_line(cx: &mut TestAppContext) {
2976 init_test(cx, |_| {});
2977
2978 let view = cx.add_window(|cx| {
2979 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2980 build_editor(buffer, cx)
2981 });
2982 _ = view.update(cx, |view, cx| {
2983 view.change_selections(None, cx, |s| {
2984 s.select_display_ranges([
2985 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2986 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2987 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2988 ])
2989 });
2990 view.delete_line(&DeleteLine, cx);
2991 assert_eq!(view.display_text(cx), "ghi");
2992 assert_eq!(
2993 view.selections.display_ranges(cx),
2994 vec![
2995 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2996 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2997 ]
2998 );
2999 });
3000
3001 let view = cx.add_window(|cx| {
3002 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3003 build_editor(buffer, cx)
3004 });
3005 _ = view.update(cx, |view, cx| {
3006 view.change_selections(None, cx, |s| {
3007 s.select_display_ranges([
3008 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3009 ])
3010 });
3011 view.delete_line(&DeleteLine, cx);
3012 assert_eq!(view.display_text(cx), "ghi\n");
3013 assert_eq!(
3014 view.selections.display_ranges(cx),
3015 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3016 );
3017 });
3018}
3019
3020#[gpui::test]
3021fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3022 init_test(cx, |_| {});
3023
3024 cx.add_window(|cx| {
3025 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3026 let mut editor = build_editor(buffer.clone(), cx);
3027 let buffer = buffer.read(cx).as_singleton().unwrap();
3028
3029 assert_eq!(
3030 editor.selections.ranges::<Point>(cx),
3031 &[Point::new(0, 0)..Point::new(0, 0)]
3032 );
3033
3034 // When on single line, replace newline at end by space
3035 editor.join_lines(&JoinLines, cx);
3036 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3037 assert_eq!(
3038 editor.selections.ranges::<Point>(cx),
3039 &[Point::new(0, 3)..Point::new(0, 3)]
3040 );
3041
3042 // When multiple lines are selected, remove newlines that are spanned by the selection
3043 editor.change_selections(None, cx, |s| {
3044 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3045 });
3046 editor.join_lines(&JoinLines, cx);
3047 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3048 assert_eq!(
3049 editor.selections.ranges::<Point>(cx),
3050 &[Point::new(0, 11)..Point::new(0, 11)]
3051 );
3052
3053 // Undo should be transactional
3054 editor.undo(&Undo, cx);
3055 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3056 assert_eq!(
3057 editor.selections.ranges::<Point>(cx),
3058 &[Point::new(0, 5)..Point::new(2, 2)]
3059 );
3060
3061 // When joining an empty line don't insert a space
3062 editor.change_selections(None, cx, |s| {
3063 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3064 });
3065 editor.join_lines(&JoinLines, cx);
3066 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3067 assert_eq!(
3068 editor.selections.ranges::<Point>(cx),
3069 [Point::new(2, 3)..Point::new(2, 3)]
3070 );
3071
3072 // We can remove trailing newlines
3073 editor.join_lines(&JoinLines, cx);
3074 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3075 assert_eq!(
3076 editor.selections.ranges::<Point>(cx),
3077 [Point::new(2, 3)..Point::new(2, 3)]
3078 );
3079
3080 // We don't blow up on the last line
3081 editor.join_lines(&JoinLines, cx);
3082 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3083 assert_eq!(
3084 editor.selections.ranges::<Point>(cx),
3085 [Point::new(2, 3)..Point::new(2, 3)]
3086 );
3087
3088 // reset to test indentation
3089 editor.buffer.update(cx, |buffer, cx| {
3090 buffer.edit(
3091 [
3092 (Point::new(1, 0)..Point::new(1, 2), " "),
3093 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3094 ],
3095 None,
3096 cx,
3097 )
3098 });
3099
3100 // We remove any leading spaces
3101 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3102 editor.change_selections(None, cx, |s| {
3103 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3104 });
3105 editor.join_lines(&JoinLines, cx);
3106 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3107
3108 // We don't insert a space for a line containing only spaces
3109 editor.join_lines(&JoinLines, cx);
3110 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3111
3112 // We ignore any leading tabs
3113 editor.join_lines(&JoinLines, cx);
3114 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3115
3116 editor
3117 });
3118}
3119
3120#[gpui::test]
3121fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3122 init_test(cx, |_| {});
3123
3124 cx.add_window(|cx| {
3125 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3126 let mut editor = build_editor(buffer.clone(), cx);
3127 let buffer = buffer.read(cx).as_singleton().unwrap();
3128
3129 editor.change_selections(None, cx, |s| {
3130 s.select_ranges([
3131 Point::new(0, 2)..Point::new(1, 1),
3132 Point::new(1, 2)..Point::new(1, 2),
3133 Point::new(3, 1)..Point::new(3, 2),
3134 ])
3135 });
3136
3137 editor.join_lines(&JoinLines, cx);
3138 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3139
3140 assert_eq!(
3141 editor.selections.ranges::<Point>(cx),
3142 [
3143 Point::new(0, 7)..Point::new(0, 7),
3144 Point::new(1, 3)..Point::new(1, 3)
3145 ]
3146 );
3147 editor
3148 });
3149}
3150
3151#[gpui::test]
3152async fn test_join_lines_with_git_diff_base(
3153 executor: BackgroundExecutor,
3154 cx: &mut gpui::TestAppContext,
3155) {
3156 init_test(cx, |_| {});
3157
3158 let mut cx = EditorTestContext::new(cx).await;
3159
3160 let diff_base = r#"
3161 Line 0
3162 Line 1
3163 Line 2
3164 Line 3
3165 "#
3166 .unindent();
3167
3168 cx.set_state(
3169 &r#"
3170 ˇLine 0
3171 Line 1
3172 Line 2
3173 Line 3
3174 "#
3175 .unindent(),
3176 );
3177
3178 cx.set_diff_base(Some(&diff_base));
3179 executor.run_until_parked();
3180
3181 // Join lines
3182 cx.update_editor(|editor, cx| {
3183 editor.join_lines(&JoinLines, cx);
3184 });
3185 executor.run_until_parked();
3186
3187 cx.assert_editor_state(
3188 &r#"
3189 Line 0ˇ Line 1
3190 Line 2
3191 Line 3
3192 "#
3193 .unindent(),
3194 );
3195 // Join again
3196 cx.update_editor(|editor, cx| {
3197 editor.join_lines(&JoinLines, cx);
3198 });
3199 executor.run_until_parked();
3200
3201 cx.assert_editor_state(
3202 &r#"
3203 Line 0 Line 1ˇ Line 2
3204 Line 3
3205 "#
3206 .unindent(),
3207 );
3208}
3209
3210#[gpui::test]
3211async fn test_custom_newlines_cause_no_false_positive_diffs(
3212 executor: BackgroundExecutor,
3213 cx: &mut gpui::TestAppContext,
3214) {
3215 init_test(cx, |_| {});
3216 let mut cx = EditorTestContext::new(cx).await;
3217 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3218 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3219 executor.run_until_parked();
3220
3221 cx.update_editor(|editor, cx| {
3222 assert_eq!(
3223 editor
3224 .buffer()
3225 .read(cx)
3226 .snapshot(cx)
3227 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3228 .collect::<Vec<_>>(),
3229 Vec::new(),
3230 "Should not have any diffs for files with custom newlines"
3231 );
3232 });
3233}
3234
3235#[gpui::test]
3236async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3237 init_test(cx, |_| {});
3238
3239 let mut cx = EditorTestContext::new(cx).await;
3240
3241 // Test sort_lines_case_insensitive()
3242 cx.set_state(indoc! {"
3243 «z
3244 y
3245 x
3246 Z
3247 Y
3248 Xˇ»
3249 "});
3250 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3251 cx.assert_editor_state(indoc! {"
3252 «x
3253 X
3254 y
3255 Y
3256 z
3257 Zˇ»
3258 "});
3259
3260 // Test reverse_lines()
3261 cx.set_state(indoc! {"
3262 «5
3263 4
3264 3
3265 2
3266 1ˇ»
3267 "});
3268 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3269 cx.assert_editor_state(indoc! {"
3270 «1
3271 2
3272 3
3273 4
3274 5ˇ»
3275 "});
3276
3277 // Skip testing shuffle_line()
3278
3279 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3280 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3281
3282 // Don't manipulate when cursor is on single line, but expand the selection
3283 cx.set_state(indoc! {"
3284 ddˇdd
3285 ccc
3286 bb
3287 a
3288 "});
3289 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3290 cx.assert_editor_state(indoc! {"
3291 «ddddˇ»
3292 ccc
3293 bb
3294 a
3295 "});
3296
3297 // Basic manipulate case
3298 // Start selection moves to column 0
3299 // End of selection shrinks to fit shorter line
3300 cx.set_state(indoc! {"
3301 dd«d
3302 ccc
3303 bb
3304 aaaaaˇ»
3305 "});
3306 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3307 cx.assert_editor_state(indoc! {"
3308 «aaaaa
3309 bb
3310 ccc
3311 dddˇ»
3312 "});
3313
3314 // Manipulate case with newlines
3315 cx.set_state(indoc! {"
3316 dd«d
3317 ccc
3318
3319 bb
3320 aaaaa
3321
3322 ˇ»
3323 "});
3324 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3325 cx.assert_editor_state(indoc! {"
3326 «
3327
3328 aaaaa
3329 bb
3330 ccc
3331 dddˇ»
3332
3333 "});
3334
3335 // Adding new line
3336 cx.set_state(indoc! {"
3337 aa«a
3338 bbˇ»b
3339 "});
3340 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3341 cx.assert_editor_state(indoc! {"
3342 «aaa
3343 bbb
3344 added_lineˇ»
3345 "});
3346
3347 // Removing line
3348 cx.set_state(indoc! {"
3349 aa«a
3350 bbbˇ»
3351 "});
3352 cx.update_editor(|e, cx| {
3353 e.manipulate_lines(cx, |lines| {
3354 lines.pop();
3355 })
3356 });
3357 cx.assert_editor_state(indoc! {"
3358 «aaaˇ»
3359 "});
3360
3361 // Removing all lines
3362 cx.set_state(indoc! {"
3363 aa«a
3364 bbbˇ»
3365 "});
3366 cx.update_editor(|e, cx| {
3367 e.manipulate_lines(cx, |lines| {
3368 lines.drain(..);
3369 })
3370 });
3371 cx.assert_editor_state(indoc! {"
3372 ˇ
3373 "});
3374}
3375
3376#[gpui::test]
3377async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3378 init_test(cx, |_| {});
3379
3380 let mut cx = EditorTestContext::new(cx).await;
3381
3382 // Consider continuous selection as single selection
3383 cx.set_state(indoc! {"
3384 Aaa«aa
3385 cˇ»c«c
3386 bb
3387 aaaˇ»aa
3388 "});
3389 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3390 cx.assert_editor_state(indoc! {"
3391 «Aaaaa
3392 ccc
3393 bb
3394 aaaaaˇ»
3395 "});
3396
3397 cx.set_state(indoc! {"
3398 Aaa«aa
3399 cˇ»c«c
3400 bb
3401 aaaˇ»aa
3402 "});
3403 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3404 cx.assert_editor_state(indoc! {"
3405 «Aaaaa
3406 ccc
3407 bbˇ»
3408 "});
3409
3410 // Consider non continuous selection as distinct dedup operations
3411 cx.set_state(indoc! {"
3412 «aaaaa
3413 bb
3414 aaaaa
3415 aaaaaˇ»
3416
3417 aaa«aaˇ»
3418 "});
3419 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3420 cx.assert_editor_state(indoc! {"
3421 «aaaaa
3422 bbˇ»
3423
3424 «aaaaaˇ»
3425 "});
3426}
3427
3428#[gpui::test]
3429async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3430 init_test(cx, |_| {});
3431
3432 let mut cx = EditorTestContext::new(cx).await;
3433
3434 cx.set_state(indoc! {"
3435 «Aaa
3436 aAa
3437 Aaaˇ»
3438 "});
3439 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3440 cx.assert_editor_state(indoc! {"
3441 «Aaa
3442 aAaˇ»
3443 "});
3444
3445 cx.set_state(indoc! {"
3446 «Aaa
3447 aAa
3448 aaAˇ»
3449 "});
3450 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3451 cx.assert_editor_state(indoc! {"
3452 «Aaaˇ»
3453 "});
3454}
3455
3456#[gpui::test]
3457async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3458 init_test(cx, |_| {});
3459
3460 let mut cx = EditorTestContext::new(cx).await;
3461
3462 // Manipulate with multiple selections on a single line
3463 cx.set_state(indoc! {"
3464 dd«dd
3465 cˇ»c«c
3466 bb
3467 aaaˇ»aa
3468 "});
3469 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3470 cx.assert_editor_state(indoc! {"
3471 «aaaaa
3472 bb
3473 ccc
3474 ddddˇ»
3475 "});
3476
3477 // Manipulate with multiple disjoin selections
3478 cx.set_state(indoc! {"
3479 5«
3480 4
3481 3
3482 2
3483 1ˇ»
3484
3485 dd«dd
3486 ccc
3487 bb
3488 aaaˇ»aa
3489 "});
3490 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3491 cx.assert_editor_state(indoc! {"
3492 «1
3493 2
3494 3
3495 4
3496 5ˇ»
3497
3498 «aaaaa
3499 bb
3500 ccc
3501 ddddˇ»
3502 "});
3503
3504 // Adding lines on each selection
3505 cx.set_state(indoc! {"
3506 2«
3507 1ˇ»
3508
3509 bb«bb
3510 aaaˇ»aa
3511 "});
3512 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3513 cx.assert_editor_state(indoc! {"
3514 «2
3515 1
3516 added lineˇ»
3517
3518 «bbbb
3519 aaaaa
3520 added lineˇ»
3521 "});
3522
3523 // Removing lines on each selection
3524 cx.set_state(indoc! {"
3525 2«
3526 1ˇ»
3527
3528 bb«bb
3529 aaaˇ»aa
3530 "});
3531 cx.update_editor(|e, cx| {
3532 e.manipulate_lines(cx, |lines| {
3533 lines.pop();
3534 })
3535 });
3536 cx.assert_editor_state(indoc! {"
3537 «2ˇ»
3538
3539 «bbbbˇ»
3540 "});
3541}
3542
3543#[gpui::test]
3544async fn test_manipulate_text(cx: &mut TestAppContext) {
3545 init_test(cx, |_| {});
3546
3547 let mut cx = EditorTestContext::new(cx).await;
3548
3549 // Test convert_to_upper_case()
3550 cx.set_state(indoc! {"
3551 «hello worldˇ»
3552 "});
3553 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3554 cx.assert_editor_state(indoc! {"
3555 «HELLO WORLDˇ»
3556 "});
3557
3558 // Test convert_to_lower_case()
3559 cx.set_state(indoc! {"
3560 «HELLO WORLDˇ»
3561 "});
3562 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3563 cx.assert_editor_state(indoc! {"
3564 «hello worldˇ»
3565 "});
3566
3567 // Test multiple line, single selection case
3568 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3569 cx.set_state(indoc! {"
3570 «The quick brown
3571 fox jumps over
3572 the lazy dogˇ»
3573 "});
3574 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «The Quick Brown
3577 Fox Jumps Over
3578 The Lazy Dogˇ»
3579 "});
3580
3581 // Test multiple line, single selection case
3582 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3583 cx.set_state(indoc! {"
3584 «The quick brown
3585 fox jumps over
3586 the lazy dogˇ»
3587 "});
3588 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3589 cx.assert_editor_state(indoc! {"
3590 «TheQuickBrown
3591 FoxJumpsOver
3592 TheLazyDogˇ»
3593 "});
3594
3595 // From here on out, test more complex cases of manipulate_text()
3596
3597 // Test no selection case - should affect words cursors are in
3598 // Cursor at beginning, middle, and end of word
3599 cx.set_state(indoc! {"
3600 ˇhello big beauˇtiful worldˇ
3601 "});
3602 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3603 cx.assert_editor_state(indoc! {"
3604 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3605 "});
3606
3607 // Test multiple selections on a single line and across multiple lines
3608 cx.set_state(indoc! {"
3609 «Theˇ» quick «brown
3610 foxˇ» jumps «overˇ»
3611 the «lazyˇ» dog
3612 "});
3613 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3614 cx.assert_editor_state(indoc! {"
3615 «THEˇ» quick «BROWN
3616 FOXˇ» jumps «OVERˇ»
3617 the «LAZYˇ» dog
3618 "});
3619
3620 // Test case where text length grows
3621 cx.set_state(indoc! {"
3622 «tschüߡ»
3623 "});
3624 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3625 cx.assert_editor_state(indoc! {"
3626 «TSCHÜSSˇ»
3627 "});
3628
3629 // Test to make sure we don't crash when text shrinks
3630 cx.set_state(indoc! {"
3631 aaa_bbbˇ
3632 "});
3633 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3634 cx.assert_editor_state(indoc! {"
3635 «aaaBbbˇ»
3636 "});
3637
3638 // Test to make sure we all aware of the fact that each word can grow and shrink
3639 // Final selections should be aware of this fact
3640 cx.set_state(indoc! {"
3641 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3642 "});
3643 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3644 cx.assert_editor_state(indoc! {"
3645 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3646 "});
3647
3648 cx.set_state(indoc! {"
3649 «hElLo, WoRld!ˇ»
3650 "});
3651 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3652 cx.assert_editor_state(indoc! {"
3653 «HeLlO, wOrLD!ˇ»
3654 "});
3655}
3656
3657#[gpui::test]
3658fn test_duplicate_line(cx: &mut TestAppContext) {
3659 init_test(cx, |_| {});
3660
3661 let view = cx.add_window(|cx| {
3662 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3663 build_editor(buffer, cx)
3664 });
3665 _ = view.update(cx, |view, cx| {
3666 view.change_selections(None, cx, |s| {
3667 s.select_display_ranges([
3668 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3669 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3670 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3671 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3672 ])
3673 });
3674 view.duplicate_line_down(&DuplicateLineDown, cx);
3675 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3676 assert_eq!(
3677 view.selections.display_ranges(cx),
3678 vec![
3679 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3680 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3681 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3682 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3683 ]
3684 );
3685 });
3686
3687 let view = cx.add_window(|cx| {
3688 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3689 build_editor(buffer, cx)
3690 });
3691 _ = view.update(cx, |view, cx| {
3692 view.change_selections(None, cx, |s| {
3693 s.select_display_ranges([
3694 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3695 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3696 ])
3697 });
3698 view.duplicate_line_down(&DuplicateLineDown, cx);
3699 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3700 assert_eq!(
3701 view.selections.display_ranges(cx),
3702 vec![
3703 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3704 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3705 ]
3706 );
3707 });
3708
3709 // With `move_upwards` the selections stay in place, except for
3710 // the lines inserted above them
3711 let view = cx.add_window(|cx| {
3712 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3713 build_editor(buffer, cx)
3714 });
3715 _ = view.update(cx, |view, cx| {
3716 view.change_selections(None, cx, |s| {
3717 s.select_display_ranges([
3718 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3719 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3720 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3721 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3722 ])
3723 });
3724 view.duplicate_line_up(&DuplicateLineUp, cx);
3725 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3726 assert_eq!(
3727 view.selections.display_ranges(cx),
3728 vec![
3729 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3730 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3731 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3732 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3733 ]
3734 );
3735 });
3736
3737 let view = cx.add_window(|cx| {
3738 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3739 build_editor(buffer, cx)
3740 });
3741 _ = view.update(cx, |view, cx| {
3742 view.change_selections(None, cx, |s| {
3743 s.select_display_ranges([
3744 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3745 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3746 ])
3747 });
3748 view.duplicate_line_up(&DuplicateLineUp, cx);
3749 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3750 assert_eq!(
3751 view.selections.display_ranges(cx),
3752 vec![
3753 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3754 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3755 ]
3756 );
3757 });
3758}
3759
3760#[gpui::test]
3761fn test_move_line_up_down(cx: &mut TestAppContext) {
3762 init_test(cx, |_| {});
3763
3764 let view = cx.add_window(|cx| {
3765 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3766 build_editor(buffer, cx)
3767 });
3768 _ = view.update(cx, |view, cx| {
3769 view.fold_ranges(
3770 vec![
3771 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3772 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3773 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3774 ],
3775 true,
3776 cx,
3777 );
3778 view.change_selections(None, cx, |s| {
3779 s.select_display_ranges([
3780 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3781 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3782 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3783 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3784 ])
3785 });
3786 assert_eq!(
3787 view.display_text(cx),
3788 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3789 );
3790
3791 view.move_line_up(&MoveLineUp, cx);
3792 assert_eq!(
3793 view.display_text(cx),
3794 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3795 );
3796 assert_eq!(
3797 view.selections.display_ranges(cx),
3798 vec![
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3800 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3801 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3802 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3803 ]
3804 );
3805 });
3806
3807 _ = view.update(cx, |view, cx| {
3808 view.move_line_down(&MoveLineDown, cx);
3809 assert_eq!(
3810 view.display_text(cx),
3811 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3812 );
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3818 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3819 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3820 ]
3821 );
3822 });
3823
3824 _ = view.update(cx, |view, cx| {
3825 view.move_line_down(&MoveLineDown, cx);
3826 assert_eq!(
3827 view.display_text(cx),
3828 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3829 );
3830 assert_eq!(
3831 view.selections.display_ranges(cx),
3832 vec![
3833 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3834 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3835 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3836 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3837 ]
3838 );
3839 });
3840
3841 _ = view.update(cx, |view, cx| {
3842 view.move_line_up(&MoveLineUp, cx);
3843 assert_eq!(
3844 view.display_text(cx),
3845 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3846 );
3847 assert_eq!(
3848 view.selections.display_ranges(cx),
3849 vec![
3850 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3851 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3852 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3853 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3854 ]
3855 );
3856 });
3857}
3858
3859#[gpui::test]
3860fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3861 init_test(cx, |_| {});
3862
3863 let editor = cx.add_window(|cx| {
3864 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3865 build_editor(buffer, cx)
3866 });
3867 _ = editor.update(cx, |editor, cx| {
3868 let snapshot = editor.buffer.read(cx).snapshot(cx);
3869 editor.insert_blocks(
3870 [BlockProperties {
3871 style: BlockStyle::Fixed,
3872 position: snapshot.anchor_after(Point::new(2, 0)),
3873 disposition: BlockDisposition::Below,
3874 height: 1,
3875 render: Box::new(|_| div().into_any()),
3876 priority: 0,
3877 }],
3878 Some(Autoscroll::fit()),
3879 cx,
3880 );
3881 editor.change_selections(None, cx, |s| {
3882 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3883 });
3884 editor.move_line_down(&MoveLineDown, cx);
3885 });
3886}
3887
3888#[gpui::test]
3889fn test_transpose(cx: &mut TestAppContext) {
3890 init_test(cx, |_| {});
3891
3892 _ = cx.add_window(|cx| {
3893 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3894 editor.set_style(EditorStyle::default(), cx);
3895 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3896 editor.transpose(&Default::default(), cx);
3897 assert_eq!(editor.text(cx), "bac");
3898 assert_eq!(editor.selections.ranges(cx), [2..2]);
3899
3900 editor.transpose(&Default::default(), cx);
3901 assert_eq!(editor.text(cx), "bca");
3902 assert_eq!(editor.selections.ranges(cx), [3..3]);
3903
3904 editor.transpose(&Default::default(), cx);
3905 assert_eq!(editor.text(cx), "bac");
3906 assert_eq!(editor.selections.ranges(cx), [3..3]);
3907
3908 editor
3909 });
3910
3911 _ = cx.add_window(|cx| {
3912 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3913 editor.set_style(EditorStyle::default(), cx);
3914 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3915 editor.transpose(&Default::default(), cx);
3916 assert_eq!(editor.text(cx), "acb\nde");
3917 assert_eq!(editor.selections.ranges(cx), [3..3]);
3918
3919 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3920 editor.transpose(&Default::default(), cx);
3921 assert_eq!(editor.text(cx), "acbd\ne");
3922 assert_eq!(editor.selections.ranges(cx), [5..5]);
3923
3924 editor.transpose(&Default::default(), cx);
3925 assert_eq!(editor.text(cx), "acbde\n");
3926 assert_eq!(editor.selections.ranges(cx), [6..6]);
3927
3928 editor.transpose(&Default::default(), cx);
3929 assert_eq!(editor.text(cx), "acbd\ne");
3930 assert_eq!(editor.selections.ranges(cx), [6..6]);
3931
3932 editor
3933 });
3934
3935 _ = cx.add_window(|cx| {
3936 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3937 editor.set_style(EditorStyle::default(), cx);
3938 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3939 editor.transpose(&Default::default(), cx);
3940 assert_eq!(editor.text(cx), "bacd\ne");
3941 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3942
3943 editor.transpose(&Default::default(), cx);
3944 assert_eq!(editor.text(cx), "bcade\n");
3945 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3946
3947 editor.transpose(&Default::default(), cx);
3948 assert_eq!(editor.text(cx), "bcda\ne");
3949 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3950
3951 editor.transpose(&Default::default(), cx);
3952 assert_eq!(editor.text(cx), "bcade\n");
3953 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3954
3955 editor.transpose(&Default::default(), cx);
3956 assert_eq!(editor.text(cx), "bcaed\n");
3957 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3958
3959 editor
3960 });
3961
3962 _ = cx.add_window(|cx| {
3963 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3964 editor.set_style(EditorStyle::default(), cx);
3965 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3966 editor.transpose(&Default::default(), cx);
3967 assert_eq!(editor.text(cx), "🏀🍐✋");
3968 assert_eq!(editor.selections.ranges(cx), [8..8]);
3969
3970 editor.transpose(&Default::default(), cx);
3971 assert_eq!(editor.text(cx), "🏀✋🍐");
3972 assert_eq!(editor.selections.ranges(cx), [11..11]);
3973
3974 editor.transpose(&Default::default(), cx);
3975 assert_eq!(editor.text(cx), "🏀🍐✋");
3976 assert_eq!(editor.selections.ranges(cx), [11..11]);
3977
3978 editor
3979 });
3980}
3981
3982#[gpui::test]
3983async fn test_rewrap(cx: &mut TestAppContext) {
3984 init_test(cx, |_| {});
3985
3986 let mut cx = EditorTestContext::new(cx).await;
3987
3988 {
3989 let language = Arc::new(Language::new(
3990 LanguageConfig {
3991 line_comments: vec!["// ".into()],
3992 ..LanguageConfig::default()
3993 },
3994 None,
3995 ));
3996 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3997
3998 let unwrapped_text = indoc! {"
3999 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4000 "};
4001
4002 let wrapped_text = indoc! {"
4003 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4004 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4005 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4006 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4007 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4008 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4009 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4010 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4011 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4012 // porttitor id. Aliquam id accumsan eros.ˇ
4013 "};
4014
4015 cx.set_state(unwrapped_text);
4016 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4017 cx.assert_editor_state(wrapped_text);
4018 }
4019
4020 // Test that rewrapping works inside of a selection
4021 {
4022 let language = Arc::new(Language::new(
4023 LanguageConfig {
4024 line_comments: vec!["// ".into()],
4025 ..LanguageConfig::default()
4026 },
4027 None,
4028 ));
4029 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4030
4031 let unwrapped_text = indoc! {"
4032 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4033 "};
4034
4035 let wrapped_text = indoc! {"
4036 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4037 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4038 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4039 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4040 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4041 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4042 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4043 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4044 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4045 // porttitor id. Aliquam id accumsan eros.ˇ
4046 "};
4047
4048 cx.set_state(unwrapped_text);
4049 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4050 cx.assert_editor_state(wrapped_text);
4051 }
4052
4053 // Test that cursors that expand to the same region are collapsed.
4054 {
4055 let language = Arc::new(Language::new(
4056 LanguageConfig {
4057 line_comments: vec!["// ".into()],
4058 ..LanguageConfig::default()
4059 },
4060 None,
4061 ));
4062 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4063
4064 let unwrapped_text = indoc! {"
4065 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4066 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4067 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4068 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4069 "};
4070
4071 let wrapped_text = indoc! {"
4072 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4073 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4074 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4075 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4076 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4077 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4078 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4079 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4080 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4081 // porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
4082 "};
4083
4084 cx.set_state(unwrapped_text);
4085 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4086 cx.assert_editor_state(wrapped_text);
4087 }
4088
4089 // Test that non-contiguous selections are treated separately.
4090 {
4091 let language = Arc::new(Language::new(
4092 LanguageConfig {
4093 line_comments: vec!["// ".into()],
4094 ..LanguageConfig::default()
4095 },
4096 None,
4097 ));
4098 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4099
4100 let unwrapped_text = indoc! {"
4101 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4102 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4103 //
4104 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4105 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4106 "};
4107
4108 let wrapped_text = indoc! {"
4109 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4110 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4111 // auctor, eu lacinia sapien scelerisque.ˇˇ
4112 //
4113 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4114 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4115 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4116 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4117 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4118 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4119 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
4120 "};
4121
4122 cx.set_state(unwrapped_text);
4123 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4124 cx.assert_editor_state(wrapped_text);
4125 }
4126
4127 // Test that different comment prefixes are supported.
4128 {
4129 let language = Arc::new(Language::new(
4130 LanguageConfig {
4131 line_comments: vec!["# ".into()],
4132 ..LanguageConfig::default()
4133 },
4134 None,
4135 ));
4136 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4137
4138 let unwrapped_text = indoc! {"
4139 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4140 "};
4141
4142 let wrapped_text = indoc! {"
4143 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4144 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4145 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4146 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4147 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4148 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4149 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4150 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4151 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4152 # accumsan eros.ˇ
4153 "};
4154
4155 cx.set_state(unwrapped_text);
4156 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4157 cx.assert_editor_state(wrapped_text);
4158 }
4159
4160 // Test that rewrapping is ignored outside of comments in most languages.
4161 {
4162 let language = Arc::new(Language::new(
4163 LanguageConfig {
4164 line_comments: vec!["// ".into(), "/// ".into()],
4165 ..LanguageConfig::default()
4166 },
4167 Some(tree_sitter_rust::LANGUAGE.into()),
4168 ));
4169 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4170
4171 let unwrapped_text = indoc! {"
4172 /// Adds two numbers.
4173 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4174 fn add(a: u32, b: u32) -> u32 {
4175 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4176 }
4177 "};
4178
4179 let wrapped_text = indoc! {"
4180 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4181 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4182 fn add(a: u32, b: u32) -> u32 {
4183 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4184 }
4185 "};
4186
4187 cx.set_state(unwrapped_text);
4188 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4189 cx.assert_editor_state(wrapped_text);
4190 }
4191
4192 // Test that rewrapping works in Markdown and Plain Text languages.
4193 {
4194 let markdown_language = Arc::new(Language::new(
4195 LanguageConfig {
4196 name: "Markdown".into(),
4197 ..LanguageConfig::default()
4198 },
4199 None,
4200 ));
4201 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4202
4203 let unwrapped_text = indoc! {"
4204 # Hello
4205
4206 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4207 "};
4208
4209 let wrapped_text = indoc! {"
4210 # Hello
4211
4212 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4213 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4214 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4215 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4216 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4217 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4218 Integer sit amet scelerisque nisi.ˇ
4219 "};
4220
4221 cx.set_state(unwrapped_text);
4222 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4223 cx.assert_editor_state(wrapped_text);
4224
4225 let plaintext_language = Arc::new(Language::new(
4226 LanguageConfig {
4227 name: "Plain Text".into(),
4228 ..LanguageConfig::default()
4229 },
4230 None,
4231 ));
4232 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4233
4234 let unwrapped_text = indoc! {"
4235 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4236 "};
4237
4238 let wrapped_text = indoc! {"
4239 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4240 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4241 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4242 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4243 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4244 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4245 Integer sit amet scelerisque nisi.ˇ
4246 "};
4247
4248 cx.set_state(unwrapped_text);
4249 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4250 cx.assert_editor_state(wrapped_text);
4251 }
4252
4253 // 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
4277 let wrapped_text = indoc! {"
4278 fn foo() {
4279 if true {
4280 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4281 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4282 // egestas tellus id dignissim.ˇ
4283 do_something();
4284 } else {
4285 //
4286 }
4287
4288 }
4289 "};
4290
4291 cx.set_state(unwrapped_text);
4292 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4293 cx.assert_editor_state(wrapped_text);
4294
4295 let unwrapped_text = indoc! {"
4296 fn foo() {
4297 if true {
4298 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4299 // Praesent semper egestas tellus id dignissim.»
4300 do_something();
4301 } else {
4302 //
4303 }
4304
4305 }
4306 "};
4307
4308 let wrapped_text = indoc! {"
4309 fn foo() {
4310 if true {
4311 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4312 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4313 // egestas tellus id dignissim.ˇ
4314 do_something();
4315 } else {
4316 //
4317 }
4318
4319 }
4320 "};
4321
4322 cx.set_state(unwrapped_text);
4323 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4324 cx.assert_editor_state(wrapped_text);
4325 }
4326}
4327
4328#[gpui::test]
4329async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4330 init_test(cx, |_| {});
4331
4332 let mut cx = EditorTestContext::new(cx).await;
4333
4334 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4335 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4336 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4337
4338 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4339 cx.set_state("two ˇfour ˇsix ˇ");
4340 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4341 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4342
4343 // Paste again but with only two cursors. Since the number of cursors doesn't
4344 // match the number of slices in the clipboard, the entire clipboard text
4345 // is pasted at each cursor.
4346 cx.set_state("ˇtwo one✅ four three six five ˇ");
4347 cx.update_editor(|e, cx| {
4348 e.handle_input("( ", cx);
4349 e.paste(&Paste, cx);
4350 e.handle_input(") ", cx);
4351 });
4352 cx.assert_editor_state(
4353 &([
4354 "( one✅ ",
4355 "three ",
4356 "five ) ˇtwo one✅ four three six five ( one✅ ",
4357 "three ",
4358 "five ) ˇ",
4359 ]
4360 .join("\n")),
4361 );
4362
4363 // Cut with three selections, one of which is full-line.
4364 cx.set_state(indoc! {"
4365 1«2ˇ»3
4366 4ˇ567
4367 «8ˇ»9"});
4368 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4369 cx.assert_editor_state(indoc! {"
4370 1ˇ3
4371 ˇ9"});
4372
4373 // Paste with three selections, noticing how the copied selection that was full-line
4374 // gets inserted before the second cursor.
4375 cx.set_state(indoc! {"
4376 1ˇ3
4377 9ˇ
4378 «oˇ»ne"});
4379 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4380 cx.assert_editor_state(indoc! {"
4381 12ˇ3
4382 4567
4383 9ˇ
4384 8ˇne"});
4385
4386 // Copy with a single cursor only, which writes the whole line into the clipboard.
4387 cx.set_state(indoc! {"
4388 The quick brown
4389 fox juˇmps over
4390 the lazy dog"});
4391 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4392 assert_eq!(
4393 cx.read_from_clipboard()
4394 .and_then(|item| item.text().as_deref().map(str::to_string)),
4395 Some("fox jumps over\n".to_string())
4396 );
4397
4398 // Paste with three selections, noticing how the copied full-line selection is inserted
4399 // before the empty selections but replaces the selection that is non-empty.
4400 cx.set_state(indoc! {"
4401 Tˇhe quick brown
4402 «foˇ»x jumps over
4403 tˇhe lazy dog"});
4404 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4405 cx.assert_editor_state(indoc! {"
4406 fox jumps over
4407 Tˇhe quick brown
4408 fox jumps over
4409 ˇx jumps over
4410 fox jumps over
4411 tˇhe lazy dog"});
4412}
4413
4414#[gpui::test]
4415async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4416 init_test(cx, |_| {});
4417
4418 let mut cx = EditorTestContext::new(cx).await;
4419 let language = Arc::new(Language::new(
4420 LanguageConfig::default(),
4421 Some(tree_sitter_rust::LANGUAGE.into()),
4422 ));
4423 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4424
4425 // Cut an indented block, without the leading whitespace.
4426 cx.set_state(indoc! {"
4427 const a: B = (
4428 c(),
4429 «d(
4430 e,
4431 f
4432 )ˇ»
4433 );
4434 "});
4435 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4436 cx.assert_editor_state(indoc! {"
4437 const a: B = (
4438 c(),
4439 ˇ
4440 );
4441 "});
4442
4443 // Paste it at the same position.
4444 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4445 cx.assert_editor_state(indoc! {"
4446 const a: B = (
4447 c(),
4448 d(
4449 e,
4450 f
4451 )ˇ
4452 );
4453 "});
4454
4455 // Paste it at a line with a lower indent level.
4456 cx.set_state(indoc! {"
4457 ˇ
4458 const a: B = (
4459 c(),
4460 );
4461 "});
4462 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4463 cx.assert_editor_state(indoc! {"
4464 d(
4465 e,
4466 f
4467 )ˇ
4468 const a: B = (
4469 c(),
4470 );
4471 "});
4472
4473 // Cut an indented block, with the leading whitespace.
4474 cx.set_state(indoc! {"
4475 const a: B = (
4476 c(),
4477 « d(
4478 e,
4479 f
4480 )
4481 ˇ»);
4482 "});
4483 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4484 cx.assert_editor_state(indoc! {"
4485 const a: B = (
4486 c(),
4487 ˇ);
4488 "});
4489
4490 // Paste it at the same position.
4491 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4492 cx.assert_editor_state(indoc! {"
4493 const a: B = (
4494 c(),
4495 d(
4496 e,
4497 f
4498 )
4499 ˇ);
4500 "});
4501
4502 // Paste it at a line with a higher indent level.
4503 cx.set_state(indoc! {"
4504 const a: B = (
4505 c(),
4506 d(
4507 e,
4508 fˇ
4509 )
4510 );
4511 "});
4512 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4513 cx.assert_editor_state(indoc! {"
4514 const a: B = (
4515 c(),
4516 d(
4517 e,
4518 f d(
4519 e,
4520 f
4521 )
4522 ˇ
4523 )
4524 );
4525 "});
4526}
4527
4528#[gpui::test]
4529fn test_select_all(cx: &mut TestAppContext) {
4530 init_test(cx, |_| {});
4531
4532 let view = cx.add_window(|cx| {
4533 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4534 build_editor(buffer, cx)
4535 });
4536 _ = view.update(cx, |view, cx| {
4537 view.select_all(&SelectAll, cx);
4538 assert_eq!(
4539 view.selections.display_ranges(cx),
4540 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4541 );
4542 });
4543}
4544
4545#[gpui::test]
4546fn test_select_line(cx: &mut TestAppContext) {
4547 init_test(cx, |_| {});
4548
4549 let view = cx.add_window(|cx| {
4550 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4551 build_editor(buffer, cx)
4552 });
4553 _ = view.update(cx, |view, cx| {
4554 view.change_selections(None, cx, |s| {
4555 s.select_display_ranges([
4556 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4557 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4558 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4559 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4560 ])
4561 });
4562 view.select_line(&SelectLine, cx);
4563 assert_eq!(
4564 view.selections.display_ranges(cx),
4565 vec![
4566 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4567 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4568 ]
4569 );
4570 });
4571
4572 _ = view.update(cx, |view, cx| {
4573 view.select_line(&SelectLine, cx);
4574 assert_eq!(
4575 view.selections.display_ranges(cx),
4576 vec![
4577 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4578 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4579 ]
4580 );
4581 });
4582
4583 _ = view.update(cx, |view, cx| {
4584 view.select_line(&SelectLine, cx);
4585 assert_eq!(
4586 view.selections.display_ranges(cx),
4587 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4588 );
4589 });
4590}
4591
4592#[gpui::test]
4593fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4594 init_test(cx, |_| {});
4595
4596 let view = cx.add_window(|cx| {
4597 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4598 build_editor(buffer, cx)
4599 });
4600 _ = view.update(cx, |view, cx| {
4601 view.fold_ranges(
4602 vec![
4603 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4604 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4605 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4606 ],
4607 true,
4608 cx,
4609 );
4610 view.change_selections(None, cx, |s| {
4611 s.select_display_ranges([
4612 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4613 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4614 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4615 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4616 ])
4617 });
4618 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4619 });
4620
4621 _ = view.update(cx, |view, cx| {
4622 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4623 assert_eq!(
4624 view.display_text(cx),
4625 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4626 );
4627 assert_eq!(
4628 view.selections.display_ranges(cx),
4629 [
4630 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4632 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4633 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4634 ]
4635 );
4636 });
4637
4638 _ = view.update(cx, |view, cx| {
4639 view.change_selections(None, cx, |s| {
4640 s.select_display_ranges([
4641 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4642 ])
4643 });
4644 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4645 assert_eq!(
4646 view.display_text(cx),
4647 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4648 );
4649 assert_eq!(
4650 view.selections.display_ranges(cx),
4651 [
4652 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4653 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4654 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4655 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4656 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4657 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4658 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4659 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4660 ]
4661 );
4662 });
4663}
4664
4665#[gpui::test]
4666async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4667 init_test(cx, |_| {});
4668
4669 let mut cx = EditorTestContext::new(cx).await;
4670
4671 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4672 cx.set_state(indoc!(
4673 r#"abc
4674 defˇghi
4675
4676 jk
4677 nlmo
4678 "#
4679 ));
4680
4681 cx.update_editor(|editor, cx| {
4682 editor.add_selection_above(&Default::default(), cx);
4683 });
4684
4685 cx.assert_editor_state(indoc!(
4686 r#"abcˇ
4687 defˇghi
4688
4689 jk
4690 nlmo
4691 "#
4692 ));
4693
4694 cx.update_editor(|editor, cx| {
4695 editor.add_selection_above(&Default::default(), cx);
4696 });
4697
4698 cx.assert_editor_state(indoc!(
4699 r#"abcˇ
4700 defˇghi
4701
4702 jk
4703 nlmo
4704 "#
4705 ));
4706
4707 cx.update_editor(|view, cx| {
4708 view.add_selection_below(&Default::default(), cx);
4709 });
4710
4711 cx.assert_editor_state(indoc!(
4712 r#"abc
4713 defˇghi
4714
4715 jk
4716 nlmo
4717 "#
4718 ));
4719
4720 cx.update_editor(|view, cx| {
4721 view.undo_selection(&Default::default(), cx);
4722 });
4723
4724 cx.assert_editor_state(indoc!(
4725 r#"abcˇ
4726 defˇghi
4727
4728 jk
4729 nlmo
4730 "#
4731 ));
4732
4733 cx.update_editor(|view, cx| {
4734 view.redo_selection(&Default::default(), cx);
4735 });
4736
4737 cx.assert_editor_state(indoc!(
4738 r#"abc
4739 defˇghi
4740
4741 jk
4742 nlmo
4743 "#
4744 ));
4745
4746 cx.update_editor(|view, cx| {
4747 view.add_selection_below(&Default::default(), cx);
4748 });
4749
4750 cx.assert_editor_state(indoc!(
4751 r#"abc
4752 defˇghi
4753
4754 jk
4755 nlmˇo
4756 "#
4757 ));
4758
4759 cx.update_editor(|view, cx| {
4760 view.add_selection_below(&Default::default(), cx);
4761 });
4762
4763 cx.assert_editor_state(indoc!(
4764 r#"abc
4765 defˇghi
4766
4767 jk
4768 nlmˇo
4769 "#
4770 ));
4771
4772 // change selections
4773 cx.set_state(indoc!(
4774 r#"abc
4775 def«ˇg»hi
4776
4777 jk
4778 nlmo
4779 "#
4780 ));
4781
4782 cx.update_editor(|view, cx| {
4783 view.add_selection_below(&Default::default(), cx);
4784 });
4785
4786 cx.assert_editor_state(indoc!(
4787 r#"abc
4788 def«ˇg»hi
4789
4790 jk
4791 nlm«ˇo»
4792 "#
4793 ));
4794
4795 cx.update_editor(|view, cx| {
4796 view.add_selection_below(&Default::default(), cx);
4797 });
4798
4799 cx.assert_editor_state(indoc!(
4800 r#"abc
4801 def«ˇg»hi
4802
4803 jk
4804 nlm«ˇo»
4805 "#
4806 ));
4807
4808 cx.update_editor(|view, cx| {
4809 view.add_selection_above(&Default::default(), cx);
4810 });
4811
4812 cx.assert_editor_state(indoc!(
4813 r#"abc
4814 def«ˇg»hi
4815
4816 jk
4817 nlmo
4818 "#
4819 ));
4820
4821 cx.update_editor(|view, cx| {
4822 view.add_selection_above(&Default::default(), cx);
4823 });
4824
4825 cx.assert_editor_state(indoc!(
4826 r#"abc
4827 def«ˇg»hi
4828
4829 jk
4830 nlmo
4831 "#
4832 ));
4833
4834 // Change selections again
4835 cx.set_state(indoc!(
4836 r#"a«bc
4837 defgˇ»hi
4838
4839 jk
4840 nlmo
4841 "#
4842 ));
4843
4844 cx.update_editor(|view, cx| {
4845 view.add_selection_below(&Default::default(), cx);
4846 });
4847
4848 cx.assert_editor_state(indoc!(
4849 r#"a«bcˇ»
4850 d«efgˇ»hi
4851
4852 j«kˇ»
4853 nlmo
4854 "#
4855 ));
4856
4857 cx.update_editor(|view, cx| {
4858 view.add_selection_below(&Default::default(), cx);
4859 });
4860 cx.assert_editor_state(indoc!(
4861 r#"a«bcˇ»
4862 d«efgˇ»hi
4863
4864 j«kˇ»
4865 n«lmoˇ»
4866 "#
4867 ));
4868 cx.update_editor(|view, cx| {
4869 view.add_selection_above(&Default::default(), cx);
4870 });
4871
4872 cx.assert_editor_state(indoc!(
4873 r#"a«bcˇ»
4874 d«efgˇ»hi
4875
4876 j«kˇ»
4877 nlmo
4878 "#
4879 ));
4880
4881 // Change selections again
4882 cx.set_state(indoc!(
4883 r#"abc
4884 d«ˇefghi
4885
4886 jk
4887 nlm»o
4888 "#
4889 ));
4890
4891 cx.update_editor(|view, cx| {
4892 view.add_selection_above(&Default::default(), cx);
4893 });
4894
4895 cx.assert_editor_state(indoc!(
4896 r#"a«ˇbc»
4897 d«ˇef»ghi
4898
4899 j«ˇk»
4900 n«ˇlm»o
4901 "#
4902 ));
4903
4904 cx.update_editor(|view, cx| {
4905 view.add_selection_below(&Default::default(), cx);
4906 });
4907
4908 cx.assert_editor_state(indoc!(
4909 r#"abc
4910 d«ˇef»ghi
4911
4912 j«ˇk»
4913 n«ˇlm»o
4914 "#
4915 ));
4916}
4917
4918#[gpui::test]
4919async fn test_select_next(cx: &mut gpui::TestAppContext) {
4920 init_test(cx, |_| {});
4921
4922 let mut cx = EditorTestContext::new(cx).await;
4923 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4924
4925 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4926 .unwrap();
4927 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4928
4929 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4930 .unwrap();
4931 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4932
4933 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4934 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4935
4936 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4937 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4938
4939 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4940 .unwrap();
4941 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4942
4943 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4944 .unwrap();
4945 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4946}
4947
4948#[gpui::test]
4949async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4950 init_test(cx, |_| {});
4951
4952 let mut cx = EditorTestContext::new(cx).await;
4953 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4954
4955 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4956 .unwrap();
4957 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4958}
4959
4960#[gpui::test]
4961async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4962 init_test(cx, |_| {});
4963
4964 let mut cx = EditorTestContext::new(cx).await;
4965 cx.set_state(
4966 r#"let foo = 2;
4967lˇet foo = 2;
4968let fooˇ = 2;
4969let foo = 2;
4970let foo = ˇ2;"#,
4971 );
4972
4973 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4974 .unwrap();
4975 cx.assert_editor_state(
4976 r#"let foo = 2;
4977«letˇ» foo = 2;
4978let «fooˇ» = 2;
4979let foo = 2;
4980let foo = «2ˇ»;"#,
4981 );
4982
4983 // noop for multiple selections with different contents
4984 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4985 .unwrap();
4986 cx.assert_editor_state(
4987 r#"let foo = 2;
4988«letˇ» foo = 2;
4989let «fooˇ» = 2;
4990let foo = 2;
4991let foo = «2ˇ»;"#,
4992 );
4993}
4994
4995#[gpui::test]
4996async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4997 init_test(cx, |_| {});
4998
4999 let mut cx =
5000 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5001
5002 cx.assert_editor_state(indoc! {"
5003 ˇbbb
5004 ccc
5005
5006 bbb
5007 ccc
5008 "});
5009 cx.dispatch_action(SelectPrevious::default());
5010 cx.assert_editor_state(indoc! {"
5011 «bbbˇ»
5012 ccc
5013
5014 bbb
5015 ccc
5016 "});
5017 cx.dispatch_action(SelectPrevious::default());
5018 cx.assert_editor_state(indoc! {"
5019 «bbbˇ»
5020 ccc
5021
5022 «bbbˇ»
5023 ccc
5024 "});
5025}
5026
5027#[gpui::test]
5028async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5029 init_test(cx, |_| {});
5030
5031 let mut cx = EditorTestContext::new(cx).await;
5032 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5033
5034 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5035 .unwrap();
5036 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5037
5038 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5039 .unwrap();
5040 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5041
5042 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5043 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5044
5045 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5046 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5047
5048 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5049 .unwrap();
5050 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5051
5052 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5053 .unwrap();
5054 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5055
5056 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5057 .unwrap();
5058 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5059}
5060
5061#[gpui::test]
5062async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5063 init_test(cx, |_| {});
5064
5065 let mut cx = EditorTestContext::new(cx).await;
5066 cx.set_state(
5067 r#"let foo = 2;
5068lˇet foo = 2;
5069let fooˇ = 2;
5070let foo = 2;
5071let foo = ˇ2;"#,
5072 );
5073
5074 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5075 .unwrap();
5076 cx.assert_editor_state(
5077 r#"let foo = 2;
5078«letˇ» foo = 2;
5079let «fooˇ» = 2;
5080let foo = 2;
5081let foo = «2ˇ»;"#,
5082 );
5083
5084 // noop for multiple selections with different contents
5085 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5086 .unwrap();
5087 cx.assert_editor_state(
5088 r#"let foo = 2;
5089«letˇ» foo = 2;
5090let «fooˇ» = 2;
5091let foo = 2;
5092let foo = «2ˇ»;"#,
5093 );
5094}
5095
5096#[gpui::test]
5097async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5098 init_test(cx, |_| {});
5099
5100 let mut cx = EditorTestContext::new(cx).await;
5101 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5102
5103 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5104 .unwrap();
5105 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5106
5107 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5108 .unwrap();
5109 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5110
5111 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5112 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5113
5114 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5115 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5116
5117 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5118 .unwrap();
5119 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5120
5121 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5122 .unwrap();
5123 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5124}
5125
5126#[gpui::test]
5127async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5128 init_test(cx, |_| {});
5129
5130 let language = Arc::new(Language::new(
5131 LanguageConfig::default(),
5132 Some(tree_sitter_rust::LANGUAGE.into()),
5133 ));
5134
5135 let text = r#"
5136 use mod1::mod2::{mod3, mod4};
5137
5138 fn fn_1(param1: bool, param2: &str) {
5139 let var1 = "text";
5140 }
5141 "#
5142 .unindent();
5143
5144 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5145 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5146 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5147
5148 editor
5149 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5150 .await;
5151
5152 editor.update(cx, |view, cx| {
5153 view.change_selections(None, cx, |s| {
5154 s.select_display_ranges([
5155 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5156 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5157 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5158 ]);
5159 });
5160 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5161 });
5162 editor.update(cx, |editor, cx| {
5163 assert_text_with_selections(
5164 editor,
5165 indoc! {r#"
5166 use mod1::mod2::{mod3, «mod4ˇ»};
5167
5168 fn fn_1«ˇ(param1: bool, param2: &str)» {
5169 let var1 = "«textˇ»";
5170 }
5171 "#},
5172 cx,
5173 );
5174 });
5175
5176 editor.update(cx, |view, cx| {
5177 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5178 });
5179 editor.update(cx, |editor, cx| {
5180 assert_text_with_selections(
5181 editor,
5182 indoc! {r#"
5183 use mod1::mod2::«{mod3, mod4}ˇ»;
5184
5185 «ˇfn fn_1(param1: bool, param2: &str) {
5186 let var1 = "text";
5187 }»
5188 "#},
5189 cx,
5190 );
5191 });
5192
5193 editor.update(cx, |view, cx| {
5194 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5195 });
5196 assert_eq!(
5197 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5198 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5199 );
5200
5201 // Trying to expand the selected syntax node one more time has no effect.
5202 editor.update(cx, |view, cx| {
5203 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5204 });
5205 assert_eq!(
5206 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5207 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5208 );
5209
5210 editor.update(cx, |view, cx| {
5211 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5212 });
5213 editor.update(cx, |editor, cx| {
5214 assert_text_with_selections(
5215 editor,
5216 indoc! {r#"
5217 use mod1::mod2::«{mod3, mod4}ˇ»;
5218
5219 «ˇfn fn_1(param1: bool, param2: &str) {
5220 let var1 = "text";
5221 }»
5222 "#},
5223 cx,
5224 );
5225 });
5226
5227 editor.update(cx, |view, cx| {
5228 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5229 });
5230 editor.update(cx, |editor, cx| {
5231 assert_text_with_selections(
5232 editor,
5233 indoc! {r#"
5234 use mod1::mod2::{mod3, «mod4ˇ»};
5235
5236 fn fn_1«ˇ(param1: bool, param2: &str)» {
5237 let var1 = "«textˇ»";
5238 }
5239 "#},
5240 cx,
5241 );
5242 });
5243
5244 editor.update(cx, |view, cx| {
5245 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5246 });
5247 editor.update(cx, |editor, cx| {
5248 assert_text_with_selections(
5249 editor,
5250 indoc! {r#"
5251 use mod1::mod2::{mod3, mo«ˇ»d4};
5252
5253 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5254 let var1 = "te«ˇ»xt";
5255 }
5256 "#},
5257 cx,
5258 );
5259 });
5260
5261 // Trying to shrink the selected syntax node one more time has no effect.
5262 editor.update(cx, |view, cx| {
5263 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5264 });
5265 editor.update(cx, |editor, cx| {
5266 assert_text_with_selections(
5267 editor,
5268 indoc! {r#"
5269 use mod1::mod2::{mod3, mo«ˇ»d4};
5270
5271 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5272 let var1 = "te«ˇ»xt";
5273 }
5274 "#},
5275 cx,
5276 );
5277 });
5278
5279 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5280 // a fold.
5281 editor.update(cx, |view, cx| {
5282 view.fold_ranges(
5283 vec![
5284 (
5285 Point::new(0, 21)..Point::new(0, 24),
5286 FoldPlaceholder::test(),
5287 ),
5288 (
5289 Point::new(3, 20)..Point::new(3, 22),
5290 FoldPlaceholder::test(),
5291 ),
5292 ],
5293 true,
5294 cx,
5295 );
5296 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5297 });
5298 editor.update(cx, |editor, cx| {
5299 assert_text_with_selections(
5300 editor,
5301 indoc! {r#"
5302 use mod1::mod2::«{mod3, mod4}ˇ»;
5303
5304 fn fn_1«ˇ(param1: bool, param2: &str)» {
5305 «let var1 = "text";ˇ»
5306 }
5307 "#},
5308 cx,
5309 );
5310 });
5311}
5312
5313#[gpui::test]
5314async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5315 init_test(cx, |_| {});
5316
5317 let language = Arc::new(
5318 Language::new(
5319 LanguageConfig {
5320 brackets: BracketPairConfig {
5321 pairs: vec![
5322 BracketPair {
5323 start: "{".to_string(),
5324 end: "}".to_string(),
5325 close: false,
5326 surround: false,
5327 newline: true,
5328 },
5329 BracketPair {
5330 start: "(".to_string(),
5331 end: ")".to_string(),
5332 close: false,
5333 surround: false,
5334 newline: true,
5335 },
5336 ],
5337 ..Default::default()
5338 },
5339 ..Default::default()
5340 },
5341 Some(tree_sitter_rust::LANGUAGE.into()),
5342 )
5343 .with_indents_query(
5344 r#"
5345 (_ "(" ")" @end) @indent
5346 (_ "{" "}" @end) @indent
5347 "#,
5348 )
5349 .unwrap(),
5350 );
5351
5352 let text = "fn a() {}";
5353
5354 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5355 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5356 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5357 editor
5358 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5359 .await;
5360
5361 editor.update(cx, |editor, cx| {
5362 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5363 editor.newline(&Newline, cx);
5364 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5365 assert_eq!(
5366 editor.selections.ranges(cx),
5367 &[
5368 Point::new(1, 4)..Point::new(1, 4),
5369 Point::new(3, 4)..Point::new(3, 4),
5370 Point::new(5, 0)..Point::new(5, 0)
5371 ]
5372 );
5373 });
5374}
5375
5376#[gpui::test]
5377async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5378 init_test(cx, |_| {});
5379
5380 let mut cx = EditorTestContext::new(cx).await;
5381
5382 let language = Arc::new(Language::new(
5383 LanguageConfig {
5384 brackets: BracketPairConfig {
5385 pairs: vec![
5386 BracketPair {
5387 start: "{".to_string(),
5388 end: "}".to_string(),
5389 close: true,
5390 surround: true,
5391 newline: true,
5392 },
5393 BracketPair {
5394 start: "(".to_string(),
5395 end: ")".to_string(),
5396 close: true,
5397 surround: true,
5398 newline: true,
5399 },
5400 BracketPair {
5401 start: "/*".to_string(),
5402 end: " */".to_string(),
5403 close: true,
5404 surround: true,
5405 newline: true,
5406 },
5407 BracketPair {
5408 start: "[".to_string(),
5409 end: "]".to_string(),
5410 close: false,
5411 surround: false,
5412 newline: true,
5413 },
5414 BracketPair {
5415 start: "\"".to_string(),
5416 end: "\"".to_string(),
5417 close: true,
5418 surround: true,
5419 newline: false,
5420 },
5421 BracketPair {
5422 start: "<".to_string(),
5423 end: ">".to_string(),
5424 close: false,
5425 surround: true,
5426 newline: true,
5427 },
5428 ],
5429 ..Default::default()
5430 },
5431 autoclose_before: "})]".to_string(),
5432 ..Default::default()
5433 },
5434 Some(tree_sitter_rust::LANGUAGE.into()),
5435 ));
5436
5437 cx.language_registry().add(language.clone());
5438 cx.update_buffer(|buffer, cx| {
5439 buffer.set_language(Some(language), cx);
5440 });
5441
5442 cx.set_state(
5443 &r#"
5444 🏀ˇ
5445 εˇ
5446 ❤️ˇ
5447 "#
5448 .unindent(),
5449 );
5450
5451 // autoclose multiple nested brackets at multiple cursors
5452 cx.update_editor(|view, cx| {
5453 view.handle_input("{", cx);
5454 view.handle_input("{", cx);
5455 view.handle_input("{", cx);
5456 });
5457 cx.assert_editor_state(
5458 &"
5459 🏀{{{ˇ}}}
5460 ε{{{ˇ}}}
5461 ❤️{{{ˇ}}}
5462 "
5463 .unindent(),
5464 );
5465
5466 // insert a different closing bracket
5467 cx.update_editor(|view, cx| {
5468 view.handle_input(")", cx);
5469 });
5470 cx.assert_editor_state(
5471 &"
5472 🏀{{{)ˇ}}}
5473 ε{{{)ˇ}}}
5474 ❤️{{{)ˇ}}}
5475 "
5476 .unindent(),
5477 );
5478
5479 // skip over the auto-closed brackets when typing a closing bracket
5480 cx.update_editor(|view, cx| {
5481 view.move_right(&MoveRight, cx);
5482 view.handle_input("}", cx);
5483 view.handle_input("}", cx);
5484 view.handle_input("}", cx);
5485 });
5486 cx.assert_editor_state(
5487 &"
5488 🏀{{{)}}}}ˇ
5489 ε{{{)}}}}ˇ
5490 ❤️{{{)}}}}ˇ
5491 "
5492 .unindent(),
5493 );
5494
5495 // autoclose multi-character pairs
5496 cx.set_state(
5497 &"
5498 ˇ
5499 ˇ
5500 "
5501 .unindent(),
5502 );
5503 cx.update_editor(|view, cx| {
5504 view.handle_input("/", cx);
5505 view.handle_input("*", cx);
5506 });
5507 cx.assert_editor_state(
5508 &"
5509 /*ˇ */
5510 /*ˇ */
5511 "
5512 .unindent(),
5513 );
5514
5515 // one cursor autocloses a multi-character pair, one cursor
5516 // does not autoclose.
5517 cx.set_state(
5518 &"
5519 /ˇ
5520 ˇ
5521 "
5522 .unindent(),
5523 );
5524 cx.update_editor(|view, cx| view.handle_input("*", cx));
5525 cx.assert_editor_state(
5526 &"
5527 /*ˇ */
5528 *ˇ
5529 "
5530 .unindent(),
5531 );
5532
5533 // Don't autoclose if the next character isn't whitespace and isn't
5534 // listed in the language's "autoclose_before" section.
5535 cx.set_state("ˇa b");
5536 cx.update_editor(|view, cx| view.handle_input("{", cx));
5537 cx.assert_editor_state("{ˇa b");
5538
5539 // Don't autoclose if `close` is false for the bracket pair
5540 cx.set_state("ˇ");
5541 cx.update_editor(|view, cx| view.handle_input("[", cx));
5542 cx.assert_editor_state("[ˇ");
5543
5544 // Surround with brackets if text is selected
5545 cx.set_state("«aˇ» b");
5546 cx.update_editor(|view, cx| view.handle_input("{", cx));
5547 cx.assert_editor_state("{«aˇ»} b");
5548
5549 // Autclose pair where the start and end characters are the same
5550 cx.set_state("aˇ");
5551 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5552 cx.assert_editor_state("a\"ˇ\"");
5553 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5554 cx.assert_editor_state("a\"\"ˇ");
5555
5556 // Don't autoclose pair if autoclose is disabled
5557 cx.set_state("ˇ");
5558 cx.update_editor(|view, cx| view.handle_input("<", cx));
5559 cx.assert_editor_state("<ˇ");
5560
5561 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5562 cx.set_state("«aˇ» b");
5563 cx.update_editor(|view, cx| view.handle_input("<", cx));
5564 cx.assert_editor_state("<«aˇ»> b");
5565}
5566
5567#[gpui::test]
5568async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5569 init_test(cx, |settings| {
5570 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5571 });
5572
5573 let mut cx = EditorTestContext::new(cx).await;
5574
5575 let language = Arc::new(Language::new(
5576 LanguageConfig {
5577 brackets: BracketPairConfig {
5578 pairs: vec![
5579 BracketPair {
5580 start: "{".to_string(),
5581 end: "}".to_string(),
5582 close: true,
5583 surround: true,
5584 newline: true,
5585 },
5586 BracketPair {
5587 start: "(".to_string(),
5588 end: ")".to_string(),
5589 close: true,
5590 surround: true,
5591 newline: true,
5592 },
5593 BracketPair {
5594 start: "[".to_string(),
5595 end: "]".to_string(),
5596 close: false,
5597 surround: false,
5598 newline: true,
5599 },
5600 ],
5601 ..Default::default()
5602 },
5603 autoclose_before: "})]".to_string(),
5604 ..Default::default()
5605 },
5606 Some(tree_sitter_rust::LANGUAGE.into()),
5607 ));
5608
5609 cx.language_registry().add(language.clone());
5610 cx.update_buffer(|buffer, cx| {
5611 buffer.set_language(Some(language), cx);
5612 });
5613
5614 cx.set_state(
5615 &"
5616 ˇ
5617 ˇ
5618 ˇ
5619 "
5620 .unindent(),
5621 );
5622
5623 // ensure only matching closing brackets are skipped over
5624 cx.update_editor(|view, cx| {
5625 view.handle_input("}", cx);
5626 view.move_left(&MoveLeft, cx);
5627 view.handle_input(")", cx);
5628 view.move_left(&MoveLeft, cx);
5629 });
5630 cx.assert_editor_state(
5631 &"
5632 ˇ)}
5633 ˇ)}
5634 ˇ)}
5635 "
5636 .unindent(),
5637 );
5638
5639 // skip-over closing brackets at multiple cursors
5640 cx.update_editor(|view, cx| {
5641 view.handle_input(")", cx);
5642 view.handle_input("}", cx);
5643 });
5644 cx.assert_editor_state(
5645 &"
5646 )}ˇ
5647 )}ˇ
5648 )}ˇ
5649 "
5650 .unindent(),
5651 );
5652
5653 // ignore non-close brackets
5654 cx.update_editor(|view, cx| {
5655 view.handle_input("]", cx);
5656 view.move_left(&MoveLeft, cx);
5657 view.handle_input("]", cx);
5658 });
5659 cx.assert_editor_state(
5660 &"
5661 )}]ˇ]
5662 )}]ˇ]
5663 )}]ˇ]
5664 "
5665 .unindent(),
5666 );
5667}
5668
5669#[gpui::test]
5670async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5671 init_test(cx, |_| {});
5672
5673 let mut cx = EditorTestContext::new(cx).await;
5674
5675 let html_language = Arc::new(
5676 Language::new(
5677 LanguageConfig {
5678 name: "HTML".into(),
5679 brackets: BracketPairConfig {
5680 pairs: vec![
5681 BracketPair {
5682 start: "<".into(),
5683 end: ">".into(),
5684 close: true,
5685 ..Default::default()
5686 },
5687 BracketPair {
5688 start: "{".into(),
5689 end: "}".into(),
5690 close: true,
5691 ..Default::default()
5692 },
5693 BracketPair {
5694 start: "(".into(),
5695 end: ")".into(),
5696 close: true,
5697 ..Default::default()
5698 },
5699 ],
5700 ..Default::default()
5701 },
5702 autoclose_before: "})]>".into(),
5703 ..Default::default()
5704 },
5705 Some(tree_sitter_html::language()),
5706 )
5707 .with_injection_query(
5708 r#"
5709 (script_element
5710 (raw_text) @content
5711 (#set! "language" "javascript"))
5712 "#,
5713 )
5714 .unwrap(),
5715 );
5716
5717 let javascript_language = Arc::new(Language::new(
5718 LanguageConfig {
5719 name: "JavaScript".into(),
5720 brackets: BracketPairConfig {
5721 pairs: vec![
5722 BracketPair {
5723 start: "/*".into(),
5724 end: " */".into(),
5725 close: true,
5726 ..Default::default()
5727 },
5728 BracketPair {
5729 start: "{".into(),
5730 end: "}".into(),
5731 close: true,
5732 ..Default::default()
5733 },
5734 BracketPair {
5735 start: "(".into(),
5736 end: ")".into(),
5737 close: true,
5738 ..Default::default()
5739 },
5740 ],
5741 ..Default::default()
5742 },
5743 autoclose_before: "})]>".into(),
5744 ..Default::default()
5745 },
5746 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5747 ));
5748
5749 cx.language_registry().add(html_language.clone());
5750 cx.language_registry().add(javascript_language.clone());
5751
5752 cx.update_buffer(|buffer, cx| {
5753 buffer.set_language(Some(html_language), cx);
5754 });
5755
5756 cx.set_state(
5757 &r#"
5758 <body>ˇ
5759 <script>
5760 var x = 1;ˇ
5761 </script>
5762 </body>ˇ
5763 "#
5764 .unindent(),
5765 );
5766
5767 // Precondition: different languages are active at different locations.
5768 cx.update_editor(|editor, cx| {
5769 let snapshot = editor.snapshot(cx);
5770 let cursors = editor.selections.ranges::<usize>(cx);
5771 let languages = cursors
5772 .iter()
5773 .map(|c| snapshot.language_at(c.start).unwrap().name())
5774 .collect::<Vec<_>>();
5775 assert_eq!(
5776 languages,
5777 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5778 );
5779 });
5780
5781 // Angle brackets autoclose in HTML, but not JavaScript.
5782 cx.update_editor(|editor, cx| {
5783 editor.handle_input("<", cx);
5784 editor.handle_input("a", cx);
5785 });
5786 cx.assert_editor_state(
5787 &r#"
5788 <body><aˇ>
5789 <script>
5790 var x = 1;<aˇ
5791 </script>
5792 </body><aˇ>
5793 "#
5794 .unindent(),
5795 );
5796
5797 // Curly braces and parens autoclose in both HTML and JavaScript.
5798 cx.update_editor(|editor, cx| {
5799 editor.handle_input(" b=", cx);
5800 editor.handle_input("{", cx);
5801 editor.handle_input("c", cx);
5802 editor.handle_input("(", cx);
5803 });
5804 cx.assert_editor_state(
5805 &r#"
5806 <body><a b={c(ˇ)}>
5807 <script>
5808 var x = 1;<a b={c(ˇ)}
5809 </script>
5810 </body><a b={c(ˇ)}>
5811 "#
5812 .unindent(),
5813 );
5814
5815 // Brackets that were already autoclosed are skipped.
5816 cx.update_editor(|editor, cx| {
5817 editor.handle_input(")", cx);
5818 editor.handle_input("d", cx);
5819 editor.handle_input("}", cx);
5820 });
5821 cx.assert_editor_state(
5822 &r#"
5823 <body><a b={c()d}ˇ>
5824 <script>
5825 var x = 1;<a b={c()d}ˇ
5826 </script>
5827 </body><a b={c()d}ˇ>
5828 "#
5829 .unindent(),
5830 );
5831 cx.update_editor(|editor, cx| {
5832 editor.handle_input(">", cx);
5833 });
5834 cx.assert_editor_state(
5835 &r#"
5836 <body><a b={c()d}>ˇ
5837 <script>
5838 var x = 1;<a b={c()d}>ˇ
5839 </script>
5840 </body><a b={c()d}>ˇ
5841 "#
5842 .unindent(),
5843 );
5844
5845 // Reset
5846 cx.set_state(
5847 &r#"
5848 <body>ˇ
5849 <script>
5850 var x = 1;ˇ
5851 </script>
5852 </body>ˇ
5853 "#
5854 .unindent(),
5855 );
5856
5857 cx.update_editor(|editor, cx| {
5858 editor.handle_input("<", cx);
5859 });
5860 cx.assert_editor_state(
5861 &r#"
5862 <body><ˇ>
5863 <script>
5864 var x = 1;<ˇ
5865 </script>
5866 </body><ˇ>
5867 "#
5868 .unindent(),
5869 );
5870
5871 // When backspacing, the closing angle brackets are removed.
5872 cx.update_editor(|editor, cx| {
5873 editor.backspace(&Backspace, cx);
5874 });
5875 cx.assert_editor_state(
5876 &r#"
5877 <body>ˇ
5878 <script>
5879 var x = 1;ˇ
5880 </script>
5881 </body>ˇ
5882 "#
5883 .unindent(),
5884 );
5885
5886 // Block comments autoclose in JavaScript, but not HTML.
5887 cx.update_editor(|editor, cx| {
5888 editor.handle_input("/", cx);
5889 editor.handle_input("*", cx);
5890 });
5891 cx.assert_editor_state(
5892 &r#"
5893 <body>/*ˇ
5894 <script>
5895 var x = 1;/*ˇ */
5896 </script>
5897 </body>/*ˇ
5898 "#
5899 .unindent(),
5900 );
5901}
5902
5903#[gpui::test]
5904async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5905 init_test(cx, |_| {});
5906
5907 let mut cx = EditorTestContext::new(cx).await;
5908
5909 let rust_language = Arc::new(
5910 Language::new(
5911 LanguageConfig {
5912 name: "Rust".into(),
5913 brackets: serde_json::from_value(json!([
5914 { "start": "{", "end": "}", "close": true, "newline": true },
5915 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5916 ]))
5917 .unwrap(),
5918 autoclose_before: "})]>".into(),
5919 ..Default::default()
5920 },
5921 Some(tree_sitter_rust::LANGUAGE.into()),
5922 )
5923 .with_override_query("(string_literal) @string")
5924 .unwrap(),
5925 );
5926
5927 cx.language_registry().add(rust_language.clone());
5928 cx.update_buffer(|buffer, cx| {
5929 buffer.set_language(Some(rust_language), cx);
5930 });
5931
5932 cx.set_state(
5933 &r#"
5934 let x = ˇ
5935 "#
5936 .unindent(),
5937 );
5938
5939 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5940 cx.update_editor(|editor, cx| {
5941 editor.handle_input("\"", cx);
5942 });
5943 cx.assert_editor_state(
5944 &r#"
5945 let x = "ˇ"
5946 "#
5947 .unindent(),
5948 );
5949
5950 // Inserting another quotation mark. The cursor moves across the existing
5951 // automatically-inserted quotation mark.
5952 cx.update_editor(|editor, cx| {
5953 editor.handle_input("\"", cx);
5954 });
5955 cx.assert_editor_state(
5956 &r#"
5957 let x = ""ˇ
5958 "#
5959 .unindent(),
5960 );
5961
5962 // Reset
5963 cx.set_state(
5964 &r#"
5965 let x = ˇ
5966 "#
5967 .unindent(),
5968 );
5969
5970 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5971 cx.update_editor(|editor, cx| {
5972 editor.handle_input("\"", cx);
5973 editor.handle_input(" ", cx);
5974 editor.move_left(&Default::default(), cx);
5975 editor.handle_input("\\", cx);
5976 editor.handle_input("\"", cx);
5977 });
5978 cx.assert_editor_state(
5979 &r#"
5980 let x = "\"ˇ "
5981 "#
5982 .unindent(),
5983 );
5984
5985 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5986 // mark. Nothing is inserted.
5987 cx.update_editor(|editor, cx| {
5988 editor.move_right(&Default::default(), cx);
5989 editor.handle_input("\"", cx);
5990 });
5991 cx.assert_editor_state(
5992 &r#"
5993 let x = "\" "ˇ
5994 "#
5995 .unindent(),
5996 );
5997}
5998
5999#[gpui::test]
6000async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6001 init_test(cx, |_| {});
6002
6003 let language = Arc::new(Language::new(
6004 LanguageConfig {
6005 brackets: BracketPairConfig {
6006 pairs: vec![
6007 BracketPair {
6008 start: "{".to_string(),
6009 end: "}".to_string(),
6010 close: true,
6011 surround: true,
6012 newline: true,
6013 },
6014 BracketPair {
6015 start: "/* ".to_string(),
6016 end: "*/".to_string(),
6017 close: true,
6018 surround: true,
6019 ..Default::default()
6020 },
6021 ],
6022 ..Default::default()
6023 },
6024 ..Default::default()
6025 },
6026 Some(tree_sitter_rust::LANGUAGE.into()),
6027 ));
6028
6029 let text = r#"
6030 a
6031 b
6032 c
6033 "#
6034 .unindent();
6035
6036 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6037 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6038 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6039 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6040 .await;
6041
6042 view.update(cx, |view, cx| {
6043 view.change_selections(None, cx, |s| {
6044 s.select_display_ranges([
6045 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6046 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6047 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6048 ])
6049 });
6050
6051 view.handle_input("{", cx);
6052 view.handle_input("{", cx);
6053 view.handle_input("{", cx);
6054 assert_eq!(
6055 view.text(cx),
6056 "
6057 {{{a}}}
6058 {{{b}}}
6059 {{{c}}}
6060 "
6061 .unindent()
6062 );
6063 assert_eq!(
6064 view.selections.display_ranges(cx),
6065 [
6066 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6067 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6068 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6069 ]
6070 );
6071
6072 view.undo(&Undo, cx);
6073 view.undo(&Undo, cx);
6074 view.undo(&Undo, cx);
6075 assert_eq!(
6076 view.text(cx),
6077 "
6078 a
6079 b
6080 c
6081 "
6082 .unindent()
6083 );
6084 assert_eq!(
6085 view.selections.display_ranges(cx),
6086 [
6087 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6088 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6089 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6090 ]
6091 );
6092
6093 // Ensure inserting the first character of a multi-byte bracket pair
6094 // doesn't surround the selections with the bracket.
6095 view.handle_input("/", cx);
6096 assert_eq!(
6097 view.text(cx),
6098 "
6099 /
6100 /
6101 /
6102 "
6103 .unindent()
6104 );
6105 assert_eq!(
6106 view.selections.display_ranges(cx),
6107 [
6108 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6109 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6110 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6111 ]
6112 );
6113
6114 view.undo(&Undo, cx);
6115 assert_eq!(
6116 view.text(cx),
6117 "
6118 a
6119 b
6120 c
6121 "
6122 .unindent()
6123 );
6124 assert_eq!(
6125 view.selections.display_ranges(cx),
6126 [
6127 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6128 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6129 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6130 ]
6131 );
6132
6133 // Ensure inserting the last character of a multi-byte bracket pair
6134 // doesn't surround the selections with the bracket.
6135 view.handle_input("*", cx);
6136 assert_eq!(
6137 view.text(cx),
6138 "
6139 *
6140 *
6141 *
6142 "
6143 .unindent()
6144 );
6145 assert_eq!(
6146 view.selections.display_ranges(cx),
6147 [
6148 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6149 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6150 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6151 ]
6152 );
6153 });
6154}
6155
6156#[gpui::test]
6157async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6158 init_test(cx, |_| {});
6159
6160 let language = Arc::new(Language::new(
6161 LanguageConfig {
6162 brackets: BracketPairConfig {
6163 pairs: vec![BracketPair {
6164 start: "{".to_string(),
6165 end: "}".to_string(),
6166 close: true,
6167 surround: true,
6168 newline: true,
6169 }],
6170 ..Default::default()
6171 },
6172 autoclose_before: "}".to_string(),
6173 ..Default::default()
6174 },
6175 Some(tree_sitter_rust::LANGUAGE.into()),
6176 ));
6177
6178 let text = r#"
6179 a
6180 b
6181 c
6182 "#
6183 .unindent();
6184
6185 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6186 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6187 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6188 editor
6189 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6190 .await;
6191
6192 editor.update(cx, |editor, cx| {
6193 editor.change_selections(None, cx, |s| {
6194 s.select_ranges([
6195 Point::new(0, 1)..Point::new(0, 1),
6196 Point::new(1, 1)..Point::new(1, 1),
6197 Point::new(2, 1)..Point::new(2, 1),
6198 ])
6199 });
6200
6201 editor.handle_input("{", cx);
6202 editor.handle_input("{", cx);
6203 editor.handle_input("_", cx);
6204 assert_eq!(
6205 editor.text(cx),
6206 "
6207 a{{_}}
6208 b{{_}}
6209 c{{_}}
6210 "
6211 .unindent()
6212 );
6213 assert_eq!(
6214 editor.selections.ranges::<Point>(cx),
6215 [
6216 Point::new(0, 4)..Point::new(0, 4),
6217 Point::new(1, 4)..Point::new(1, 4),
6218 Point::new(2, 4)..Point::new(2, 4)
6219 ]
6220 );
6221
6222 editor.backspace(&Default::default(), cx);
6223 editor.backspace(&Default::default(), cx);
6224 assert_eq!(
6225 editor.text(cx),
6226 "
6227 a{}
6228 b{}
6229 c{}
6230 "
6231 .unindent()
6232 );
6233 assert_eq!(
6234 editor.selections.ranges::<Point>(cx),
6235 [
6236 Point::new(0, 2)..Point::new(0, 2),
6237 Point::new(1, 2)..Point::new(1, 2),
6238 Point::new(2, 2)..Point::new(2, 2)
6239 ]
6240 );
6241
6242 editor.delete_to_previous_word_start(&Default::default(), cx);
6243 assert_eq!(
6244 editor.text(cx),
6245 "
6246 a
6247 b
6248 c
6249 "
6250 .unindent()
6251 );
6252 assert_eq!(
6253 editor.selections.ranges::<Point>(cx),
6254 [
6255 Point::new(0, 1)..Point::new(0, 1),
6256 Point::new(1, 1)..Point::new(1, 1),
6257 Point::new(2, 1)..Point::new(2, 1)
6258 ]
6259 );
6260 });
6261}
6262
6263#[gpui::test]
6264async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6265 init_test(cx, |settings| {
6266 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6267 });
6268
6269 let mut cx = EditorTestContext::new(cx).await;
6270
6271 let language = Arc::new(Language::new(
6272 LanguageConfig {
6273 brackets: BracketPairConfig {
6274 pairs: vec![
6275 BracketPair {
6276 start: "{".to_string(),
6277 end: "}".to_string(),
6278 close: true,
6279 surround: true,
6280 newline: true,
6281 },
6282 BracketPair {
6283 start: "(".to_string(),
6284 end: ")".to_string(),
6285 close: true,
6286 surround: true,
6287 newline: true,
6288 },
6289 BracketPair {
6290 start: "[".to_string(),
6291 end: "]".to_string(),
6292 close: false,
6293 surround: true,
6294 newline: true,
6295 },
6296 ],
6297 ..Default::default()
6298 },
6299 autoclose_before: "})]".to_string(),
6300 ..Default::default()
6301 },
6302 Some(tree_sitter_rust::LANGUAGE.into()),
6303 ));
6304
6305 cx.language_registry().add(language.clone());
6306 cx.update_buffer(|buffer, cx| {
6307 buffer.set_language(Some(language), cx);
6308 });
6309
6310 cx.set_state(
6311 &"
6312 {(ˇ)}
6313 [[ˇ]]
6314 {(ˇ)}
6315 "
6316 .unindent(),
6317 );
6318
6319 cx.update_editor(|view, cx| {
6320 view.backspace(&Default::default(), cx);
6321 view.backspace(&Default::default(), cx);
6322 });
6323
6324 cx.assert_editor_state(
6325 &"
6326 ˇ
6327 ˇ]]
6328 ˇ
6329 "
6330 .unindent(),
6331 );
6332
6333 cx.update_editor(|view, cx| {
6334 view.handle_input("{", cx);
6335 view.handle_input("{", cx);
6336 view.move_right(&MoveRight, cx);
6337 view.move_right(&MoveRight, cx);
6338 view.move_left(&MoveLeft, cx);
6339 view.move_left(&MoveLeft, cx);
6340 view.backspace(&Default::default(), cx);
6341 });
6342
6343 cx.assert_editor_state(
6344 &"
6345 {ˇ}
6346 {ˇ}]]
6347 {ˇ}
6348 "
6349 .unindent(),
6350 );
6351
6352 cx.update_editor(|view, cx| {
6353 view.backspace(&Default::default(), cx);
6354 });
6355
6356 cx.assert_editor_state(
6357 &"
6358 ˇ
6359 ˇ]]
6360 ˇ
6361 "
6362 .unindent(),
6363 );
6364}
6365
6366#[gpui::test]
6367async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6368 init_test(cx, |_| {});
6369
6370 let language = Arc::new(Language::new(
6371 LanguageConfig::default(),
6372 Some(tree_sitter_rust::LANGUAGE.into()),
6373 ));
6374
6375 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6376 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6377 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6378 editor
6379 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6380 .await;
6381
6382 editor.update(cx, |editor, cx| {
6383 editor.set_auto_replace_emoji_shortcode(true);
6384
6385 editor.handle_input("Hello ", cx);
6386 editor.handle_input(":wave", cx);
6387 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6388
6389 editor.handle_input(":", cx);
6390 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6391
6392 editor.handle_input(" :smile", cx);
6393 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6394
6395 editor.handle_input(":", cx);
6396 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6397
6398 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6399 editor.handle_input(":wave", cx);
6400 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6401
6402 editor.handle_input(":", cx);
6403 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6404
6405 editor.handle_input(":1", cx);
6406 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6407
6408 editor.handle_input(":", cx);
6409 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6410
6411 // Ensure shortcode does not get replaced when it is part of a word
6412 editor.handle_input(" Test:wave", cx);
6413 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6414
6415 editor.handle_input(":", cx);
6416 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6417
6418 editor.set_auto_replace_emoji_shortcode(false);
6419
6420 // Ensure shortcode does not get replaced when auto replace is off
6421 editor.handle_input(" :wave", cx);
6422 assert_eq!(
6423 editor.text(cx),
6424 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6425 );
6426
6427 editor.handle_input(":", cx);
6428 assert_eq!(
6429 editor.text(cx),
6430 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6431 );
6432 });
6433}
6434
6435#[gpui::test]
6436async fn test_snippets(cx: &mut gpui::TestAppContext) {
6437 init_test(cx, |_| {});
6438
6439 let (text, insertion_ranges) = marked_text_ranges(
6440 indoc! {"
6441 a.ˇ b
6442 a.ˇ b
6443 a.ˇ b
6444 "},
6445 false,
6446 );
6447
6448 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6449 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6450
6451 editor.update(cx, |editor, cx| {
6452 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6453
6454 editor
6455 .insert_snippet(&insertion_ranges, snippet, cx)
6456 .unwrap();
6457
6458 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6459 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6460 assert_eq!(editor.text(cx), expected_text);
6461 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6462 }
6463
6464 assert(
6465 editor,
6466 cx,
6467 indoc! {"
6468 a.f(«one», two, «three») b
6469 a.f(«one», two, «three») b
6470 a.f(«one», two, «three») b
6471 "},
6472 );
6473
6474 // Can't move earlier than the first tab stop
6475 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6476 assert(
6477 editor,
6478 cx,
6479 indoc! {"
6480 a.f(«one», two, «three») b
6481 a.f(«one», two, «three») b
6482 a.f(«one», two, «three») b
6483 "},
6484 );
6485
6486 assert!(editor.move_to_next_snippet_tabstop(cx));
6487 assert(
6488 editor,
6489 cx,
6490 indoc! {"
6491 a.f(one, «two», three) b
6492 a.f(one, «two», three) b
6493 a.f(one, «two», three) b
6494 "},
6495 );
6496
6497 editor.move_to_prev_snippet_tabstop(cx);
6498 assert(
6499 editor,
6500 cx,
6501 indoc! {"
6502 a.f(«one», two, «three») b
6503 a.f(«one», two, «three») b
6504 a.f(«one», two, «three») b
6505 "},
6506 );
6507
6508 assert!(editor.move_to_next_snippet_tabstop(cx));
6509 assert(
6510 editor,
6511 cx,
6512 indoc! {"
6513 a.f(one, «two», three) b
6514 a.f(one, «two», three) b
6515 a.f(one, «two», three) b
6516 "},
6517 );
6518 assert!(editor.move_to_next_snippet_tabstop(cx));
6519 assert(
6520 editor,
6521 cx,
6522 indoc! {"
6523 a.f(one, two, three)ˇ b
6524 a.f(one, two, three)ˇ b
6525 a.f(one, two, three)ˇ b
6526 "},
6527 );
6528
6529 // As soon as the last tab stop is reached, snippet state is gone
6530 editor.move_to_prev_snippet_tabstop(cx);
6531 assert(
6532 editor,
6533 cx,
6534 indoc! {"
6535 a.f(one, two, three)ˇ b
6536 a.f(one, two, three)ˇ b
6537 a.f(one, two, three)ˇ b
6538 "},
6539 );
6540 });
6541}
6542
6543#[gpui::test]
6544async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6545 init_test(cx, |_| {});
6546
6547 let fs = FakeFs::new(cx.executor());
6548 fs.insert_file("/file.rs", Default::default()).await;
6549
6550 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6551
6552 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6553 language_registry.add(rust_lang());
6554 let mut fake_servers = language_registry.register_fake_lsp(
6555 "Rust",
6556 FakeLspAdapter {
6557 capabilities: lsp::ServerCapabilities {
6558 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6559 ..Default::default()
6560 },
6561 ..Default::default()
6562 },
6563 );
6564
6565 let buffer = project
6566 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6567 .await
6568 .unwrap();
6569
6570 cx.executor().start_waiting();
6571 let fake_server = fake_servers.next().await.unwrap();
6572
6573 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6574 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6575 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6576 assert!(cx.read(|cx| editor.is_dirty(cx)));
6577
6578 let save = editor
6579 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6580 .unwrap();
6581 fake_server
6582 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6583 assert_eq!(
6584 params.text_document.uri,
6585 lsp::Url::from_file_path("/file.rs").unwrap()
6586 );
6587 assert_eq!(params.options.tab_size, 4);
6588 Ok(Some(vec![lsp::TextEdit::new(
6589 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6590 ", ".to_string(),
6591 )]))
6592 })
6593 .next()
6594 .await;
6595 cx.executor().start_waiting();
6596 save.await;
6597
6598 assert_eq!(
6599 editor.update(cx, |editor, cx| editor.text(cx)),
6600 "one, two\nthree\n"
6601 );
6602 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6603
6604 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6605 assert!(cx.read(|cx| editor.is_dirty(cx)));
6606
6607 // Ensure we can still save even if formatting hangs.
6608 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6609 assert_eq!(
6610 params.text_document.uri,
6611 lsp::Url::from_file_path("/file.rs").unwrap()
6612 );
6613 futures::future::pending::<()>().await;
6614 unreachable!()
6615 });
6616 let save = editor
6617 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6618 .unwrap();
6619 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6620 cx.executor().start_waiting();
6621 save.await;
6622 assert_eq!(
6623 editor.update(cx, |editor, cx| editor.text(cx)),
6624 "one\ntwo\nthree\n"
6625 );
6626 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6627
6628 // For non-dirty buffer, no formatting request should be sent
6629 let save = editor
6630 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6631 .unwrap();
6632 let _pending_format_request = fake_server
6633 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6634 panic!("Should not be invoked on non-dirty buffer");
6635 })
6636 .next();
6637 cx.executor().start_waiting();
6638 save.await;
6639
6640 // Set rust language override and assert overridden tabsize is sent to language server
6641 update_test_language_settings(cx, |settings| {
6642 settings.languages.insert(
6643 "Rust".into(),
6644 LanguageSettingsContent {
6645 tab_size: NonZeroU32::new(8),
6646 ..Default::default()
6647 },
6648 );
6649 });
6650
6651 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6652 assert!(cx.read(|cx| editor.is_dirty(cx)));
6653 let save = editor
6654 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6655 .unwrap();
6656 fake_server
6657 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6658 assert_eq!(
6659 params.text_document.uri,
6660 lsp::Url::from_file_path("/file.rs").unwrap()
6661 );
6662 assert_eq!(params.options.tab_size, 8);
6663 Ok(Some(vec![]))
6664 })
6665 .next()
6666 .await;
6667 cx.executor().start_waiting();
6668 save.await;
6669}
6670
6671#[gpui::test]
6672async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6673 init_test(cx, |_| {});
6674
6675 let cols = 4;
6676 let rows = 10;
6677 let sample_text_1 = sample_text(rows, cols, 'a');
6678 assert_eq!(
6679 sample_text_1,
6680 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6681 );
6682 let sample_text_2 = sample_text(rows, cols, 'l');
6683 assert_eq!(
6684 sample_text_2,
6685 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6686 );
6687 let sample_text_3 = sample_text(rows, cols, 'v');
6688 assert_eq!(
6689 sample_text_3,
6690 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6691 );
6692
6693 let fs = FakeFs::new(cx.executor());
6694 fs.insert_tree(
6695 "/a",
6696 json!({
6697 "main.rs": sample_text_1,
6698 "other.rs": sample_text_2,
6699 "lib.rs": sample_text_3,
6700 }),
6701 )
6702 .await;
6703
6704 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6705 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6706 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6707
6708 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6709 language_registry.add(rust_lang());
6710 let mut fake_servers = language_registry.register_fake_lsp(
6711 "Rust",
6712 FakeLspAdapter {
6713 capabilities: lsp::ServerCapabilities {
6714 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6715 ..Default::default()
6716 },
6717 ..Default::default()
6718 },
6719 );
6720
6721 let worktree = project.update(cx, |project, cx| {
6722 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6723 assert_eq!(worktrees.len(), 1);
6724 worktrees.pop().unwrap()
6725 });
6726 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6727
6728 let buffer_1 = project
6729 .update(cx, |project, cx| {
6730 project.open_buffer((worktree_id, "main.rs"), cx)
6731 })
6732 .await
6733 .unwrap();
6734 let buffer_2 = project
6735 .update(cx, |project, cx| {
6736 project.open_buffer((worktree_id, "other.rs"), cx)
6737 })
6738 .await
6739 .unwrap();
6740 let buffer_3 = project
6741 .update(cx, |project, cx| {
6742 project.open_buffer((worktree_id, "lib.rs"), cx)
6743 })
6744 .await
6745 .unwrap();
6746
6747 let multi_buffer = cx.new_model(|cx| {
6748 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6749 multi_buffer.push_excerpts(
6750 buffer_1.clone(),
6751 [
6752 ExcerptRange {
6753 context: Point::new(0, 0)..Point::new(3, 0),
6754 primary: None,
6755 },
6756 ExcerptRange {
6757 context: Point::new(5, 0)..Point::new(7, 0),
6758 primary: None,
6759 },
6760 ExcerptRange {
6761 context: Point::new(9, 0)..Point::new(10, 4),
6762 primary: None,
6763 },
6764 ],
6765 cx,
6766 );
6767 multi_buffer.push_excerpts(
6768 buffer_2.clone(),
6769 [
6770 ExcerptRange {
6771 context: Point::new(0, 0)..Point::new(3, 0),
6772 primary: None,
6773 },
6774 ExcerptRange {
6775 context: Point::new(5, 0)..Point::new(7, 0),
6776 primary: None,
6777 },
6778 ExcerptRange {
6779 context: Point::new(9, 0)..Point::new(10, 4),
6780 primary: None,
6781 },
6782 ],
6783 cx,
6784 );
6785 multi_buffer.push_excerpts(
6786 buffer_3.clone(),
6787 [
6788 ExcerptRange {
6789 context: Point::new(0, 0)..Point::new(3, 0),
6790 primary: None,
6791 },
6792 ExcerptRange {
6793 context: Point::new(5, 0)..Point::new(7, 0),
6794 primary: None,
6795 },
6796 ExcerptRange {
6797 context: Point::new(9, 0)..Point::new(10, 4),
6798 primary: None,
6799 },
6800 ],
6801 cx,
6802 );
6803 multi_buffer
6804 });
6805 let multi_buffer_editor = cx.new_view(|cx| {
6806 Editor::new(
6807 EditorMode::Full,
6808 multi_buffer,
6809 Some(project.clone()),
6810 true,
6811 cx,
6812 )
6813 });
6814
6815 multi_buffer_editor.update(cx, |editor, cx| {
6816 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6817 editor.insert("|one|two|three|", cx);
6818 });
6819 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6820 multi_buffer_editor.update(cx, |editor, cx| {
6821 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6822 s.select_ranges(Some(60..70))
6823 });
6824 editor.insert("|four|five|six|", cx);
6825 });
6826 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6827
6828 // First two buffers should be edited, but not the third one.
6829 assert_eq!(
6830 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6831 "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}",
6832 );
6833 buffer_1.update(cx, |buffer, _| {
6834 assert!(buffer.is_dirty());
6835 assert_eq!(
6836 buffer.text(),
6837 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6838 )
6839 });
6840 buffer_2.update(cx, |buffer, _| {
6841 assert!(buffer.is_dirty());
6842 assert_eq!(
6843 buffer.text(),
6844 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6845 )
6846 });
6847 buffer_3.update(cx, |buffer, _| {
6848 assert!(!buffer.is_dirty());
6849 assert_eq!(buffer.text(), sample_text_3,)
6850 });
6851
6852 cx.executor().start_waiting();
6853 let save = multi_buffer_editor
6854 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6855 .unwrap();
6856
6857 let fake_server = fake_servers.next().await.unwrap();
6858 fake_server
6859 .server
6860 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6861 Ok(Some(vec![lsp::TextEdit::new(
6862 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6863 format!("[{} formatted]", params.text_document.uri),
6864 )]))
6865 })
6866 .detach();
6867 save.await;
6868
6869 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6870 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6871 assert_eq!(
6872 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6873 "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}",
6874 );
6875 buffer_1.update(cx, |buffer, _| {
6876 assert!(!buffer.is_dirty());
6877 assert_eq!(
6878 buffer.text(),
6879 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6880 )
6881 });
6882 buffer_2.update(cx, |buffer, _| {
6883 assert!(!buffer.is_dirty());
6884 assert_eq!(
6885 buffer.text(),
6886 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6887 )
6888 });
6889 buffer_3.update(cx, |buffer, _| {
6890 assert!(!buffer.is_dirty());
6891 assert_eq!(buffer.text(), sample_text_3,)
6892 });
6893}
6894
6895#[gpui::test]
6896async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6897 init_test(cx, |_| {});
6898
6899 let fs = FakeFs::new(cx.executor());
6900 fs.insert_file("/file.rs", Default::default()).await;
6901
6902 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6903
6904 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6905 language_registry.add(rust_lang());
6906 let mut fake_servers = language_registry.register_fake_lsp(
6907 "Rust",
6908 FakeLspAdapter {
6909 capabilities: lsp::ServerCapabilities {
6910 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6911 ..Default::default()
6912 },
6913 ..Default::default()
6914 },
6915 );
6916
6917 let buffer = project
6918 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6919 .await
6920 .unwrap();
6921
6922 cx.executor().start_waiting();
6923 let fake_server = fake_servers.next().await.unwrap();
6924
6925 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6926 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6927 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6928 assert!(cx.read(|cx| editor.is_dirty(cx)));
6929
6930 let save = editor
6931 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6932 .unwrap();
6933 fake_server
6934 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6935 assert_eq!(
6936 params.text_document.uri,
6937 lsp::Url::from_file_path("/file.rs").unwrap()
6938 );
6939 assert_eq!(params.options.tab_size, 4);
6940 Ok(Some(vec![lsp::TextEdit::new(
6941 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6942 ", ".to_string(),
6943 )]))
6944 })
6945 .next()
6946 .await;
6947 cx.executor().start_waiting();
6948 save.await;
6949 assert_eq!(
6950 editor.update(cx, |editor, cx| editor.text(cx)),
6951 "one, two\nthree\n"
6952 );
6953 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6954
6955 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6956 assert!(cx.read(|cx| editor.is_dirty(cx)));
6957
6958 // Ensure we can still save even if formatting hangs.
6959 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6960 move |params, _| async move {
6961 assert_eq!(
6962 params.text_document.uri,
6963 lsp::Url::from_file_path("/file.rs").unwrap()
6964 );
6965 futures::future::pending::<()>().await;
6966 unreachable!()
6967 },
6968 );
6969 let save = editor
6970 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6971 .unwrap();
6972 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6973 cx.executor().start_waiting();
6974 save.await;
6975 assert_eq!(
6976 editor.update(cx, |editor, cx| editor.text(cx)),
6977 "one\ntwo\nthree\n"
6978 );
6979 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6980
6981 // For non-dirty buffer, no formatting request should be sent
6982 let save = editor
6983 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6984 .unwrap();
6985 let _pending_format_request = fake_server
6986 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6987 panic!("Should not be invoked on non-dirty buffer");
6988 })
6989 .next();
6990 cx.executor().start_waiting();
6991 save.await;
6992
6993 // Set Rust language override and assert overridden tabsize is sent to language server
6994 update_test_language_settings(cx, |settings| {
6995 settings.languages.insert(
6996 "Rust".into(),
6997 LanguageSettingsContent {
6998 tab_size: NonZeroU32::new(8),
6999 ..Default::default()
7000 },
7001 );
7002 });
7003
7004 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7005 assert!(cx.read(|cx| editor.is_dirty(cx)));
7006 let save = editor
7007 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7008 .unwrap();
7009 fake_server
7010 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7011 assert_eq!(
7012 params.text_document.uri,
7013 lsp::Url::from_file_path("/file.rs").unwrap()
7014 );
7015 assert_eq!(params.options.tab_size, 8);
7016 Ok(Some(vec![]))
7017 })
7018 .next()
7019 .await;
7020 cx.executor().start_waiting();
7021 save.await;
7022}
7023
7024#[gpui::test]
7025async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7026 init_test(cx, |settings| {
7027 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7028 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7029 ))
7030 });
7031
7032 let fs = FakeFs::new(cx.executor());
7033 fs.insert_file("/file.rs", Default::default()).await;
7034
7035 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7036
7037 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7038 language_registry.add(Arc::new(Language::new(
7039 LanguageConfig {
7040 name: "Rust".into(),
7041 matcher: LanguageMatcher {
7042 path_suffixes: vec!["rs".to_string()],
7043 ..Default::default()
7044 },
7045 ..LanguageConfig::default()
7046 },
7047 Some(tree_sitter_rust::LANGUAGE.into()),
7048 )));
7049 update_test_language_settings(cx, |settings| {
7050 // Enable Prettier formatting for the same buffer, and ensure
7051 // LSP is called instead of Prettier.
7052 settings.defaults.prettier = Some(PrettierSettings {
7053 allowed: true,
7054 ..PrettierSettings::default()
7055 });
7056 });
7057 let mut fake_servers = language_registry.register_fake_lsp(
7058 "Rust",
7059 FakeLspAdapter {
7060 capabilities: lsp::ServerCapabilities {
7061 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7062 ..Default::default()
7063 },
7064 ..Default::default()
7065 },
7066 );
7067
7068 let buffer = project
7069 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7070 .await
7071 .unwrap();
7072
7073 cx.executor().start_waiting();
7074 let fake_server = fake_servers.next().await.unwrap();
7075
7076 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7077 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7078 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7079
7080 let format = editor
7081 .update(cx, |editor, cx| {
7082 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
7083 })
7084 .unwrap();
7085 fake_server
7086 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7087 assert_eq!(
7088 params.text_document.uri,
7089 lsp::Url::from_file_path("/file.rs").unwrap()
7090 );
7091 assert_eq!(params.options.tab_size, 4);
7092 Ok(Some(vec![lsp::TextEdit::new(
7093 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7094 ", ".to_string(),
7095 )]))
7096 })
7097 .next()
7098 .await;
7099 cx.executor().start_waiting();
7100 format.await;
7101 assert_eq!(
7102 editor.update(cx, |editor, cx| editor.text(cx)),
7103 "one, two\nthree\n"
7104 );
7105
7106 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7107 // Ensure we don't lock if formatting hangs.
7108 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7109 assert_eq!(
7110 params.text_document.uri,
7111 lsp::Url::from_file_path("/file.rs").unwrap()
7112 );
7113 futures::future::pending::<()>().await;
7114 unreachable!()
7115 });
7116 let format = editor
7117 .update(cx, |editor, cx| {
7118 editor.perform_format(project, FormatTrigger::Manual, cx)
7119 })
7120 .unwrap();
7121 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7122 cx.executor().start_waiting();
7123 format.await;
7124 assert_eq!(
7125 editor.update(cx, |editor, cx| editor.text(cx)),
7126 "one\ntwo\nthree\n"
7127 );
7128}
7129
7130#[gpui::test]
7131async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7132 init_test(cx, |_| {});
7133
7134 let mut cx = EditorLspTestContext::new_rust(
7135 lsp::ServerCapabilities {
7136 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7137 ..Default::default()
7138 },
7139 cx,
7140 )
7141 .await;
7142
7143 cx.set_state(indoc! {"
7144 one.twoˇ
7145 "});
7146
7147 // The format request takes a long time. When it completes, it inserts
7148 // a newline and an indent before the `.`
7149 cx.lsp
7150 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7151 let executor = cx.background_executor().clone();
7152 async move {
7153 executor.timer(Duration::from_millis(100)).await;
7154 Ok(Some(vec![lsp::TextEdit {
7155 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7156 new_text: "\n ".into(),
7157 }]))
7158 }
7159 });
7160
7161 // Submit a format request.
7162 let format_1 = cx
7163 .update_editor(|editor, cx| editor.format(&Format, cx))
7164 .unwrap();
7165 cx.executor().run_until_parked();
7166
7167 // Submit a second format request.
7168 let format_2 = cx
7169 .update_editor(|editor, cx| editor.format(&Format, cx))
7170 .unwrap();
7171 cx.executor().run_until_parked();
7172
7173 // Wait for both format requests to complete
7174 cx.executor().advance_clock(Duration::from_millis(200));
7175 cx.executor().start_waiting();
7176 format_1.await.unwrap();
7177 cx.executor().start_waiting();
7178 format_2.await.unwrap();
7179
7180 // The formatting edits only happens once.
7181 cx.assert_editor_state(indoc! {"
7182 one
7183 .twoˇ
7184 "});
7185}
7186
7187#[gpui::test]
7188async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7189 init_test(cx, |settings| {
7190 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7191 });
7192
7193 let mut cx = EditorLspTestContext::new_rust(
7194 lsp::ServerCapabilities {
7195 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7196 ..Default::default()
7197 },
7198 cx,
7199 )
7200 .await;
7201
7202 // Set up a buffer white some trailing whitespace and no trailing newline.
7203 cx.set_state(
7204 &[
7205 "one ", //
7206 "twoˇ", //
7207 "three ", //
7208 "four", //
7209 ]
7210 .join("\n"),
7211 );
7212
7213 // Submit a format request.
7214 let format = cx
7215 .update_editor(|editor, cx| editor.format(&Format, cx))
7216 .unwrap();
7217
7218 // Record which buffer changes have been sent to the language server
7219 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7220 cx.lsp
7221 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7222 let buffer_changes = buffer_changes.clone();
7223 move |params, _| {
7224 buffer_changes.lock().extend(
7225 params
7226 .content_changes
7227 .into_iter()
7228 .map(|e| (e.range.unwrap(), e.text)),
7229 );
7230 }
7231 });
7232
7233 // Handle formatting requests to the language server.
7234 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7235 let buffer_changes = buffer_changes.clone();
7236 move |_, _| {
7237 // When formatting is requested, trailing whitespace has already been stripped,
7238 // and the trailing newline has already been added.
7239 assert_eq!(
7240 &buffer_changes.lock()[1..],
7241 &[
7242 (
7243 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7244 "".into()
7245 ),
7246 (
7247 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7248 "".into()
7249 ),
7250 (
7251 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7252 "\n".into()
7253 ),
7254 ]
7255 );
7256
7257 // Insert blank lines between each line of the buffer.
7258 async move {
7259 Ok(Some(vec![
7260 lsp::TextEdit {
7261 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7262 new_text: "\n".into(),
7263 },
7264 lsp::TextEdit {
7265 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7266 new_text: "\n".into(),
7267 },
7268 ]))
7269 }
7270 }
7271 });
7272
7273 // After formatting the buffer, the trailing whitespace is stripped,
7274 // a newline is appended, and the edits provided by the language server
7275 // have been applied.
7276 format.await.unwrap();
7277 cx.assert_editor_state(
7278 &[
7279 "one", //
7280 "", //
7281 "twoˇ", //
7282 "", //
7283 "three", //
7284 "four", //
7285 "", //
7286 ]
7287 .join("\n"),
7288 );
7289
7290 // Undoing the formatting undoes the trailing whitespace removal, the
7291 // trailing newline, and the LSP edits.
7292 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7293 cx.assert_editor_state(
7294 &[
7295 "one ", //
7296 "twoˇ", //
7297 "three ", //
7298 "four", //
7299 ]
7300 .join("\n"),
7301 );
7302}
7303
7304#[gpui::test]
7305async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7306 cx: &mut gpui::TestAppContext,
7307) {
7308 init_test(cx, |_| {});
7309
7310 cx.update(|cx| {
7311 cx.update_global::<SettingsStore, _>(|settings, cx| {
7312 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7313 settings.auto_signature_help = Some(true);
7314 });
7315 });
7316 });
7317
7318 let mut cx = EditorLspTestContext::new_rust(
7319 lsp::ServerCapabilities {
7320 signature_help_provider: Some(lsp::SignatureHelpOptions {
7321 ..Default::default()
7322 }),
7323 ..Default::default()
7324 },
7325 cx,
7326 )
7327 .await;
7328
7329 let language = Language::new(
7330 LanguageConfig {
7331 name: "Rust".into(),
7332 brackets: BracketPairConfig {
7333 pairs: vec![
7334 BracketPair {
7335 start: "{".to_string(),
7336 end: "}".to_string(),
7337 close: true,
7338 surround: true,
7339 newline: true,
7340 },
7341 BracketPair {
7342 start: "(".to_string(),
7343 end: ")".to_string(),
7344 close: true,
7345 surround: true,
7346 newline: true,
7347 },
7348 BracketPair {
7349 start: "/*".to_string(),
7350 end: " */".to_string(),
7351 close: true,
7352 surround: true,
7353 newline: true,
7354 },
7355 BracketPair {
7356 start: "[".to_string(),
7357 end: "]".to_string(),
7358 close: false,
7359 surround: false,
7360 newline: true,
7361 },
7362 BracketPair {
7363 start: "\"".to_string(),
7364 end: "\"".to_string(),
7365 close: true,
7366 surround: true,
7367 newline: false,
7368 },
7369 BracketPair {
7370 start: "<".to_string(),
7371 end: ">".to_string(),
7372 close: false,
7373 surround: true,
7374 newline: true,
7375 },
7376 ],
7377 ..Default::default()
7378 },
7379 autoclose_before: "})]".to_string(),
7380 ..Default::default()
7381 },
7382 Some(tree_sitter_rust::LANGUAGE.into()),
7383 );
7384 let language = Arc::new(language);
7385
7386 cx.language_registry().add(language.clone());
7387 cx.update_buffer(|buffer, cx| {
7388 buffer.set_language(Some(language), cx);
7389 });
7390
7391 cx.set_state(
7392 &r#"
7393 fn main() {
7394 sampleˇ
7395 }
7396 "#
7397 .unindent(),
7398 );
7399
7400 cx.update_editor(|view, cx| {
7401 view.handle_input("(", cx);
7402 });
7403 cx.assert_editor_state(
7404 &"
7405 fn main() {
7406 sample(ˇ)
7407 }
7408 "
7409 .unindent(),
7410 );
7411
7412 let mocked_response = lsp::SignatureHelp {
7413 signatures: vec![lsp::SignatureInformation {
7414 label: "fn sample(param1: u8, param2: u8)".to_string(),
7415 documentation: None,
7416 parameters: Some(vec![
7417 lsp::ParameterInformation {
7418 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7419 documentation: None,
7420 },
7421 lsp::ParameterInformation {
7422 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7423 documentation: None,
7424 },
7425 ]),
7426 active_parameter: None,
7427 }],
7428 active_signature: Some(0),
7429 active_parameter: Some(0),
7430 };
7431 handle_signature_help_request(&mut cx, mocked_response).await;
7432
7433 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7434 .await;
7435
7436 cx.editor(|editor, _| {
7437 let signature_help_state = editor.signature_help_state.popover().cloned();
7438 assert!(signature_help_state.is_some());
7439 let ParsedMarkdown {
7440 text, highlights, ..
7441 } = signature_help_state.unwrap().parsed_content;
7442 assert_eq!(text, "param1: u8, param2: u8");
7443 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7444 });
7445}
7446
7447#[gpui::test]
7448async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7449 init_test(cx, |_| {});
7450
7451 cx.update(|cx| {
7452 cx.update_global::<SettingsStore, _>(|settings, cx| {
7453 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7454 settings.auto_signature_help = Some(false);
7455 settings.show_signature_help_after_edits = Some(false);
7456 });
7457 });
7458 });
7459
7460 let mut cx = EditorLspTestContext::new_rust(
7461 lsp::ServerCapabilities {
7462 signature_help_provider: Some(lsp::SignatureHelpOptions {
7463 ..Default::default()
7464 }),
7465 ..Default::default()
7466 },
7467 cx,
7468 )
7469 .await;
7470
7471 let language = Language::new(
7472 LanguageConfig {
7473 name: "Rust".into(),
7474 brackets: BracketPairConfig {
7475 pairs: vec![
7476 BracketPair {
7477 start: "{".to_string(),
7478 end: "}".to_string(),
7479 close: true,
7480 surround: true,
7481 newline: true,
7482 },
7483 BracketPair {
7484 start: "(".to_string(),
7485 end: ")".to_string(),
7486 close: true,
7487 surround: true,
7488 newline: true,
7489 },
7490 BracketPair {
7491 start: "/*".to_string(),
7492 end: " */".to_string(),
7493 close: true,
7494 surround: true,
7495 newline: true,
7496 },
7497 BracketPair {
7498 start: "[".to_string(),
7499 end: "]".to_string(),
7500 close: false,
7501 surround: false,
7502 newline: true,
7503 },
7504 BracketPair {
7505 start: "\"".to_string(),
7506 end: "\"".to_string(),
7507 close: true,
7508 surround: true,
7509 newline: false,
7510 },
7511 BracketPair {
7512 start: "<".to_string(),
7513 end: ">".to_string(),
7514 close: false,
7515 surround: true,
7516 newline: true,
7517 },
7518 ],
7519 ..Default::default()
7520 },
7521 autoclose_before: "})]".to_string(),
7522 ..Default::default()
7523 },
7524 Some(tree_sitter_rust::LANGUAGE.into()),
7525 );
7526 let language = Arc::new(language);
7527
7528 cx.language_registry().add(language.clone());
7529 cx.update_buffer(|buffer, cx| {
7530 buffer.set_language(Some(language), cx);
7531 });
7532
7533 // Ensure that signature_help is not called when no signature help is enabled.
7534 cx.set_state(
7535 &r#"
7536 fn main() {
7537 sampleˇ
7538 }
7539 "#
7540 .unindent(),
7541 );
7542 cx.update_editor(|view, cx| {
7543 view.handle_input("(", cx);
7544 });
7545 cx.assert_editor_state(
7546 &"
7547 fn main() {
7548 sample(ˇ)
7549 }
7550 "
7551 .unindent(),
7552 );
7553 cx.editor(|editor, _| {
7554 assert!(editor.signature_help_state.task().is_none());
7555 });
7556
7557 let mocked_response = lsp::SignatureHelp {
7558 signatures: vec![lsp::SignatureInformation {
7559 label: "fn sample(param1: u8, param2: u8)".to_string(),
7560 documentation: None,
7561 parameters: Some(vec![
7562 lsp::ParameterInformation {
7563 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7564 documentation: None,
7565 },
7566 lsp::ParameterInformation {
7567 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7568 documentation: None,
7569 },
7570 ]),
7571 active_parameter: None,
7572 }],
7573 active_signature: Some(0),
7574 active_parameter: Some(0),
7575 };
7576
7577 // Ensure that signature_help is called when enabled afte edits
7578 cx.update(|cx| {
7579 cx.update_global::<SettingsStore, _>(|settings, cx| {
7580 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7581 settings.auto_signature_help = Some(false);
7582 settings.show_signature_help_after_edits = Some(true);
7583 });
7584 });
7585 });
7586 cx.set_state(
7587 &r#"
7588 fn main() {
7589 sampleˇ
7590 }
7591 "#
7592 .unindent(),
7593 );
7594 cx.update_editor(|view, cx| {
7595 view.handle_input("(", cx);
7596 });
7597 cx.assert_editor_state(
7598 &"
7599 fn main() {
7600 sample(ˇ)
7601 }
7602 "
7603 .unindent(),
7604 );
7605 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7606 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7607 .await;
7608 cx.update_editor(|editor, _| {
7609 let signature_help_state = editor.signature_help_state.popover().cloned();
7610 assert!(signature_help_state.is_some());
7611 let ParsedMarkdown {
7612 text, highlights, ..
7613 } = signature_help_state.unwrap().parsed_content;
7614 assert_eq!(text, "param1: u8, param2: u8");
7615 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7616 editor.signature_help_state = SignatureHelpState::default();
7617 });
7618
7619 // Ensure that signature_help is called when auto signature help override is enabled
7620 cx.update(|cx| {
7621 cx.update_global::<SettingsStore, _>(|settings, cx| {
7622 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7623 settings.auto_signature_help = Some(true);
7624 settings.show_signature_help_after_edits = Some(false);
7625 });
7626 });
7627 });
7628 cx.set_state(
7629 &r#"
7630 fn main() {
7631 sampleˇ
7632 }
7633 "#
7634 .unindent(),
7635 );
7636 cx.update_editor(|view, cx| {
7637 view.handle_input("(", cx);
7638 });
7639 cx.assert_editor_state(
7640 &"
7641 fn main() {
7642 sample(ˇ)
7643 }
7644 "
7645 .unindent(),
7646 );
7647 handle_signature_help_request(&mut cx, mocked_response).await;
7648 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7649 .await;
7650 cx.editor(|editor, _| {
7651 let signature_help_state = editor.signature_help_state.popover().cloned();
7652 assert!(signature_help_state.is_some());
7653 let ParsedMarkdown {
7654 text, highlights, ..
7655 } = signature_help_state.unwrap().parsed_content;
7656 assert_eq!(text, "param1: u8, param2: u8");
7657 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7658 });
7659}
7660
7661#[gpui::test]
7662async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7663 init_test(cx, |_| {});
7664 cx.update(|cx| {
7665 cx.update_global::<SettingsStore, _>(|settings, cx| {
7666 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7667 settings.auto_signature_help = Some(true);
7668 });
7669 });
7670 });
7671
7672 let mut cx = EditorLspTestContext::new_rust(
7673 lsp::ServerCapabilities {
7674 signature_help_provider: Some(lsp::SignatureHelpOptions {
7675 ..Default::default()
7676 }),
7677 ..Default::default()
7678 },
7679 cx,
7680 )
7681 .await;
7682
7683 // A test that directly calls `show_signature_help`
7684 cx.update_editor(|editor, cx| {
7685 editor.show_signature_help(&ShowSignatureHelp, cx);
7686 });
7687
7688 let mocked_response = lsp::SignatureHelp {
7689 signatures: vec![lsp::SignatureInformation {
7690 label: "fn sample(param1: u8, param2: u8)".to_string(),
7691 documentation: None,
7692 parameters: Some(vec![
7693 lsp::ParameterInformation {
7694 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7695 documentation: None,
7696 },
7697 lsp::ParameterInformation {
7698 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7699 documentation: None,
7700 },
7701 ]),
7702 active_parameter: None,
7703 }],
7704 active_signature: Some(0),
7705 active_parameter: Some(0),
7706 };
7707 handle_signature_help_request(&mut cx, mocked_response).await;
7708
7709 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7710 .await;
7711
7712 cx.editor(|editor, _| {
7713 let signature_help_state = editor.signature_help_state.popover().cloned();
7714 assert!(signature_help_state.is_some());
7715 let ParsedMarkdown {
7716 text, highlights, ..
7717 } = signature_help_state.unwrap().parsed_content;
7718 assert_eq!(text, "param1: u8, param2: u8");
7719 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7720 });
7721
7722 // When exiting outside from inside the brackets, `signature_help` is closed.
7723 cx.set_state(indoc! {"
7724 fn main() {
7725 sample(ˇ);
7726 }
7727
7728 fn sample(param1: u8, param2: u8) {}
7729 "});
7730
7731 cx.update_editor(|editor, cx| {
7732 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7733 });
7734
7735 let mocked_response = lsp::SignatureHelp {
7736 signatures: Vec::new(),
7737 active_signature: None,
7738 active_parameter: None,
7739 };
7740 handle_signature_help_request(&mut cx, mocked_response).await;
7741
7742 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7743 .await;
7744
7745 cx.editor(|editor, _| {
7746 assert!(!editor.signature_help_state.is_shown());
7747 });
7748
7749 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7750 cx.set_state(indoc! {"
7751 fn main() {
7752 sample(ˇ);
7753 }
7754
7755 fn sample(param1: u8, param2: u8) {}
7756 "});
7757
7758 let mocked_response = lsp::SignatureHelp {
7759 signatures: vec![lsp::SignatureInformation {
7760 label: "fn sample(param1: u8, param2: u8)".to_string(),
7761 documentation: None,
7762 parameters: Some(vec![
7763 lsp::ParameterInformation {
7764 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7765 documentation: None,
7766 },
7767 lsp::ParameterInformation {
7768 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7769 documentation: None,
7770 },
7771 ]),
7772 active_parameter: None,
7773 }],
7774 active_signature: Some(0),
7775 active_parameter: Some(0),
7776 };
7777 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7778 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7779 .await;
7780 cx.editor(|editor, _| {
7781 assert!(editor.signature_help_state.is_shown());
7782 });
7783
7784 // Restore the popover with more parameter input
7785 cx.set_state(indoc! {"
7786 fn main() {
7787 sample(param1, param2ˇ);
7788 }
7789
7790 fn sample(param1: u8, param2: u8) {}
7791 "});
7792
7793 let mocked_response = lsp::SignatureHelp {
7794 signatures: vec![lsp::SignatureInformation {
7795 label: "fn sample(param1: u8, param2: u8)".to_string(),
7796 documentation: None,
7797 parameters: Some(vec![
7798 lsp::ParameterInformation {
7799 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7800 documentation: None,
7801 },
7802 lsp::ParameterInformation {
7803 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7804 documentation: None,
7805 },
7806 ]),
7807 active_parameter: None,
7808 }],
7809 active_signature: Some(0),
7810 active_parameter: Some(1),
7811 };
7812 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7813 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7814 .await;
7815
7816 // When selecting a range, the popover is gone.
7817 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7818 cx.update_editor(|editor, cx| {
7819 editor.change_selections(None, cx, |s| {
7820 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7821 })
7822 });
7823 cx.assert_editor_state(indoc! {"
7824 fn main() {
7825 sample(param1, «ˇparam2»);
7826 }
7827
7828 fn sample(param1: u8, param2: u8) {}
7829 "});
7830 cx.editor(|editor, _| {
7831 assert!(!editor.signature_help_state.is_shown());
7832 });
7833
7834 // When unselecting again, the popover is back if within the brackets.
7835 cx.update_editor(|editor, cx| {
7836 editor.change_selections(None, cx, |s| {
7837 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7838 })
7839 });
7840 cx.assert_editor_state(indoc! {"
7841 fn main() {
7842 sample(param1, ˇparam2);
7843 }
7844
7845 fn sample(param1: u8, param2: u8) {}
7846 "});
7847 handle_signature_help_request(&mut cx, mocked_response).await;
7848 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7849 .await;
7850 cx.editor(|editor, _| {
7851 assert!(editor.signature_help_state.is_shown());
7852 });
7853
7854 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7855 cx.update_editor(|editor, cx| {
7856 editor.change_selections(None, cx, |s| {
7857 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7858 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7859 })
7860 });
7861 cx.assert_editor_state(indoc! {"
7862 fn main() {
7863 sample(param1, ˇparam2);
7864 }
7865
7866 fn sample(param1: u8, param2: u8) {}
7867 "});
7868
7869 let mocked_response = lsp::SignatureHelp {
7870 signatures: vec![lsp::SignatureInformation {
7871 label: "fn sample(param1: u8, param2: u8)".to_string(),
7872 documentation: None,
7873 parameters: Some(vec![
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7876 documentation: None,
7877 },
7878 lsp::ParameterInformation {
7879 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7880 documentation: None,
7881 },
7882 ]),
7883 active_parameter: None,
7884 }],
7885 active_signature: Some(0),
7886 active_parameter: Some(1),
7887 };
7888 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7889 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7890 .await;
7891 cx.update_editor(|editor, cx| {
7892 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7893 });
7894 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7895 .await;
7896 cx.update_editor(|editor, cx| {
7897 editor.change_selections(None, cx, |s| {
7898 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7899 })
7900 });
7901 cx.assert_editor_state(indoc! {"
7902 fn main() {
7903 sample(param1, «ˇparam2»);
7904 }
7905
7906 fn sample(param1: u8, param2: u8) {}
7907 "});
7908 cx.update_editor(|editor, cx| {
7909 editor.change_selections(None, cx, |s| {
7910 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7911 })
7912 });
7913 cx.assert_editor_state(indoc! {"
7914 fn main() {
7915 sample(param1, ˇparam2);
7916 }
7917
7918 fn sample(param1: u8, param2: u8) {}
7919 "});
7920 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7921 .await;
7922}
7923
7924#[gpui::test]
7925async fn test_completion(cx: &mut gpui::TestAppContext) {
7926 init_test(cx, |_| {});
7927
7928 let mut cx = EditorLspTestContext::new_rust(
7929 lsp::ServerCapabilities {
7930 completion_provider: Some(lsp::CompletionOptions {
7931 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7932 resolve_provider: Some(true),
7933 ..Default::default()
7934 }),
7935 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7936 ..Default::default()
7937 },
7938 cx,
7939 )
7940 .await;
7941 let counter = Arc::new(AtomicUsize::new(0));
7942
7943 cx.set_state(indoc! {"
7944 oneˇ
7945 two
7946 three
7947 "});
7948 cx.simulate_keystroke(".");
7949 handle_completion_request(
7950 &mut cx,
7951 indoc! {"
7952 one.|<>
7953 two
7954 three
7955 "},
7956 vec!["first_completion", "second_completion"],
7957 counter.clone(),
7958 )
7959 .await;
7960 cx.condition(|editor, _| editor.context_menu_visible())
7961 .await;
7962 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7963
7964 let _handler = handle_signature_help_request(
7965 &mut cx,
7966 lsp::SignatureHelp {
7967 signatures: vec![lsp::SignatureInformation {
7968 label: "test signature".to_string(),
7969 documentation: None,
7970 parameters: Some(vec![lsp::ParameterInformation {
7971 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7972 documentation: None,
7973 }]),
7974 active_parameter: None,
7975 }],
7976 active_signature: None,
7977 active_parameter: None,
7978 },
7979 );
7980 cx.update_editor(|editor, cx| {
7981 assert!(
7982 !editor.signature_help_state.is_shown(),
7983 "No signature help was called for"
7984 );
7985 editor.show_signature_help(&ShowSignatureHelp, cx);
7986 });
7987 cx.run_until_parked();
7988 cx.update_editor(|editor, _| {
7989 assert!(
7990 !editor.signature_help_state.is_shown(),
7991 "No signature help should be shown when completions menu is open"
7992 );
7993 });
7994
7995 let apply_additional_edits = cx.update_editor(|editor, cx| {
7996 editor.context_menu_next(&Default::default(), cx);
7997 editor
7998 .confirm_completion(&ConfirmCompletion::default(), cx)
7999 .unwrap()
8000 });
8001 cx.assert_editor_state(indoc! {"
8002 one.second_completionˇ
8003 two
8004 three
8005 "});
8006
8007 handle_resolve_completion_request(
8008 &mut cx,
8009 Some(vec![
8010 (
8011 //This overlaps with the primary completion edit which is
8012 //misbehavior from the LSP spec, test that we filter it out
8013 indoc! {"
8014 one.second_ˇcompletion
8015 two
8016 threeˇ
8017 "},
8018 "overlapping additional edit",
8019 ),
8020 (
8021 indoc! {"
8022 one.second_completion
8023 two
8024 threeˇ
8025 "},
8026 "\nadditional edit",
8027 ),
8028 ]),
8029 )
8030 .await;
8031 apply_additional_edits.await.unwrap();
8032 cx.assert_editor_state(indoc! {"
8033 one.second_completionˇ
8034 two
8035 three
8036 additional edit
8037 "});
8038
8039 cx.set_state(indoc! {"
8040 one.second_completion
8041 twoˇ
8042 threeˇ
8043 additional edit
8044 "});
8045 cx.simulate_keystroke(" ");
8046 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8047 cx.simulate_keystroke("s");
8048 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8049
8050 cx.assert_editor_state(indoc! {"
8051 one.second_completion
8052 two sˇ
8053 three sˇ
8054 additional edit
8055 "});
8056 handle_completion_request(
8057 &mut cx,
8058 indoc! {"
8059 one.second_completion
8060 two s
8061 three <s|>
8062 additional edit
8063 "},
8064 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8065 counter.clone(),
8066 )
8067 .await;
8068 cx.condition(|editor, _| editor.context_menu_visible())
8069 .await;
8070 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8071
8072 cx.simulate_keystroke("i");
8073
8074 handle_completion_request(
8075 &mut cx,
8076 indoc! {"
8077 one.second_completion
8078 two si
8079 three <si|>
8080 additional edit
8081 "},
8082 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8083 counter.clone(),
8084 )
8085 .await;
8086 cx.condition(|editor, _| editor.context_menu_visible())
8087 .await;
8088 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8089
8090 let apply_additional_edits = cx.update_editor(|editor, cx| {
8091 editor
8092 .confirm_completion(&ConfirmCompletion::default(), cx)
8093 .unwrap()
8094 });
8095 cx.assert_editor_state(indoc! {"
8096 one.second_completion
8097 two sixth_completionˇ
8098 three sixth_completionˇ
8099 additional edit
8100 "});
8101
8102 handle_resolve_completion_request(&mut cx, None).await;
8103 apply_additional_edits.await.unwrap();
8104
8105 cx.update(|cx| {
8106 cx.update_global::<SettingsStore, _>(|settings, cx| {
8107 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8108 settings.show_completions_on_input = Some(false);
8109 });
8110 })
8111 });
8112 cx.set_state("editorˇ");
8113 cx.simulate_keystroke(".");
8114 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8115 cx.simulate_keystroke("c");
8116 cx.simulate_keystroke("l");
8117 cx.simulate_keystroke("o");
8118 cx.assert_editor_state("editor.cloˇ");
8119 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8120 cx.update_editor(|editor, cx| {
8121 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8122 });
8123 handle_completion_request(
8124 &mut cx,
8125 "editor.<clo|>",
8126 vec!["close", "clobber"],
8127 counter.clone(),
8128 )
8129 .await;
8130 cx.condition(|editor, _| editor.context_menu_visible())
8131 .await;
8132 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8133
8134 let apply_additional_edits = cx.update_editor(|editor, cx| {
8135 editor
8136 .confirm_completion(&ConfirmCompletion::default(), cx)
8137 .unwrap()
8138 });
8139 cx.assert_editor_state("editor.closeˇ");
8140 handle_resolve_completion_request(&mut cx, None).await;
8141 apply_additional_edits.await.unwrap();
8142}
8143
8144#[gpui::test]
8145async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8146 init_test(cx, |_| {});
8147 let mut cx = EditorLspTestContext::new_rust(
8148 lsp::ServerCapabilities {
8149 completion_provider: Some(lsp::CompletionOptions {
8150 trigger_characters: Some(vec![".".to_string()]),
8151 ..Default::default()
8152 }),
8153 ..Default::default()
8154 },
8155 cx,
8156 )
8157 .await;
8158 cx.lsp
8159 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8160 Ok(Some(lsp::CompletionResponse::Array(vec![
8161 lsp::CompletionItem {
8162 label: "first".into(),
8163 ..Default::default()
8164 },
8165 lsp::CompletionItem {
8166 label: "last".into(),
8167 ..Default::default()
8168 },
8169 ])))
8170 });
8171 cx.set_state("variableˇ");
8172 cx.simulate_keystroke(".");
8173 cx.executor().run_until_parked();
8174
8175 cx.update_editor(|editor, _| {
8176 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8177 assert_eq!(
8178 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8179 &["first", "last"]
8180 );
8181 } else {
8182 panic!("expected completion menu to be open");
8183 }
8184 });
8185
8186 cx.update_editor(|editor, cx| {
8187 editor.move_page_down(&MovePageDown::default(), cx);
8188 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8189 assert!(
8190 menu.selected_item == 1,
8191 "expected PageDown to select the last item from the context menu"
8192 );
8193 } else {
8194 panic!("expected completion menu to stay open after PageDown");
8195 }
8196 });
8197
8198 cx.update_editor(|editor, cx| {
8199 editor.move_page_up(&MovePageUp::default(), cx);
8200 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8201 assert!(
8202 menu.selected_item == 0,
8203 "expected PageUp to select the first item from the context menu"
8204 );
8205 } else {
8206 panic!("expected completion menu to stay open after PageUp");
8207 }
8208 });
8209}
8210
8211#[gpui::test]
8212async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8213 init_test(cx, |_| {});
8214
8215 let mut cx = EditorLspTestContext::new_rust(
8216 lsp::ServerCapabilities {
8217 completion_provider: Some(lsp::CompletionOptions {
8218 trigger_characters: Some(vec![".".to_string()]),
8219 resolve_provider: Some(true),
8220 ..Default::default()
8221 }),
8222 ..Default::default()
8223 },
8224 cx,
8225 )
8226 .await;
8227
8228 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8229 cx.simulate_keystroke(".");
8230 let completion_item = lsp::CompletionItem {
8231 label: "Some".into(),
8232 kind: Some(lsp::CompletionItemKind::SNIPPET),
8233 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8234 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8235 kind: lsp::MarkupKind::Markdown,
8236 value: "```rust\nSome(2)\n```".to_string(),
8237 })),
8238 deprecated: Some(false),
8239 sort_text: Some("Some".to_string()),
8240 filter_text: Some("Some".to_string()),
8241 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8242 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8243 range: lsp::Range {
8244 start: lsp::Position {
8245 line: 0,
8246 character: 22,
8247 },
8248 end: lsp::Position {
8249 line: 0,
8250 character: 22,
8251 },
8252 },
8253 new_text: "Some(2)".to_string(),
8254 })),
8255 additional_text_edits: Some(vec![lsp::TextEdit {
8256 range: lsp::Range {
8257 start: lsp::Position {
8258 line: 0,
8259 character: 20,
8260 },
8261 end: lsp::Position {
8262 line: 0,
8263 character: 22,
8264 },
8265 },
8266 new_text: "".to_string(),
8267 }]),
8268 ..Default::default()
8269 };
8270
8271 let closure_completion_item = completion_item.clone();
8272 let counter = Arc::new(AtomicUsize::new(0));
8273 let counter_clone = counter.clone();
8274 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8275 let task_completion_item = closure_completion_item.clone();
8276 counter_clone.fetch_add(1, atomic::Ordering::Release);
8277 async move {
8278 Ok(Some(lsp::CompletionResponse::Array(vec![
8279 task_completion_item,
8280 ])))
8281 }
8282 });
8283
8284 cx.condition(|editor, _| editor.context_menu_visible())
8285 .await;
8286 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8287 assert!(request.next().await.is_some());
8288 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8289
8290 cx.simulate_keystroke("S");
8291 cx.simulate_keystroke("o");
8292 cx.simulate_keystroke("m");
8293 cx.condition(|editor, _| editor.context_menu_visible())
8294 .await;
8295 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8296 assert!(request.next().await.is_some());
8297 assert!(request.next().await.is_some());
8298 assert!(request.next().await.is_some());
8299 request.close();
8300 assert!(request.next().await.is_none());
8301 assert_eq!(
8302 counter.load(atomic::Ordering::Acquire),
8303 4,
8304 "With the completions menu open, only one LSP request should happen per input"
8305 );
8306}
8307
8308#[gpui::test]
8309async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8310 init_test(cx, |_| {});
8311 let mut cx = EditorTestContext::new(cx).await;
8312 let language = Arc::new(Language::new(
8313 LanguageConfig {
8314 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8315 ..Default::default()
8316 },
8317 Some(tree_sitter_rust::LANGUAGE.into()),
8318 ));
8319 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8320
8321 // If multiple selections intersect a line, the line is only toggled once.
8322 cx.set_state(indoc! {"
8323 fn a() {
8324 «//b();
8325 ˇ»// «c();
8326 //ˇ» d();
8327 }
8328 "});
8329
8330 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8331
8332 cx.assert_editor_state(indoc! {"
8333 fn a() {
8334 «b();
8335 c();
8336 ˇ» d();
8337 }
8338 "});
8339
8340 // The comment prefix is inserted at the same column for every line in a
8341 // selection.
8342 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8343
8344 cx.assert_editor_state(indoc! {"
8345 fn a() {
8346 // «b();
8347 // c();
8348 ˇ»// d();
8349 }
8350 "});
8351
8352 // If a selection ends at the beginning of a line, that line is not toggled.
8353 cx.set_selections_state(indoc! {"
8354 fn a() {
8355 // b();
8356 «// c();
8357 ˇ» // d();
8358 }
8359 "});
8360
8361 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8362
8363 cx.assert_editor_state(indoc! {"
8364 fn a() {
8365 // b();
8366 «c();
8367 ˇ» // d();
8368 }
8369 "});
8370
8371 // If a selection span a single line and is empty, the line is toggled.
8372 cx.set_state(indoc! {"
8373 fn a() {
8374 a();
8375 b();
8376 ˇ
8377 }
8378 "});
8379
8380 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8381
8382 cx.assert_editor_state(indoc! {"
8383 fn a() {
8384 a();
8385 b();
8386 //•ˇ
8387 }
8388 "});
8389
8390 // If a selection span multiple lines, empty lines are not toggled.
8391 cx.set_state(indoc! {"
8392 fn a() {
8393 «a();
8394
8395 c();ˇ»
8396 }
8397 "});
8398
8399 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8400
8401 cx.assert_editor_state(indoc! {"
8402 fn a() {
8403 // «a();
8404
8405 // c();ˇ»
8406 }
8407 "});
8408
8409 // If a selection includes multiple comment prefixes, all lines are uncommented.
8410 cx.set_state(indoc! {"
8411 fn a() {
8412 «// a();
8413 /// b();
8414 //! c();ˇ»
8415 }
8416 "});
8417
8418 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8419
8420 cx.assert_editor_state(indoc! {"
8421 fn a() {
8422 «a();
8423 b();
8424 c();ˇ»
8425 }
8426 "});
8427}
8428
8429#[gpui::test]
8430async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8431 init_test(cx, |_| {});
8432
8433 let language = Arc::new(Language::new(
8434 LanguageConfig {
8435 line_comments: vec!["// ".into()],
8436 ..Default::default()
8437 },
8438 Some(tree_sitter_rust::LANGUAGE.into()),
8439 ));
8440
8441 let mut cx = EditorTestContext::new(cx).await;
8442
8443 cx.language_registry().add(language.clone());
8444 cx.update_buffer(|buffer, cx| {
8445 buffer.set_language(Some(language), cx);
8446 });
8447
8448 let toggle_comments = &ToggleComments {
8449 advance_downwards: true,
8450 };
8451
8452 // Single cursor on one line -> advance
8453 // Cursor moves horizontally 3 characters as well on non-blank line
8454 cx.set_state(indoc!(
8455 "fn a() {
8456 ˇdog();
8457 cat();
8458 }"
8459 ));
8460 cx.update_editor(|editor, cx| {
8461 editor.toggle_comments(toggle_comments, cx);
8462 });
8463 cx.assert_editor_state(indoc!(
8464 "fn a() {
8465 // dog();
8466 catˇ();
8467 }"
8468 ));
8469
8470 // Single selection on one line -> don't advance
8471 cx.set_state(indoc!(
8472 "fn a() {
8473 «dog()ˇ»;
8474 cat();
8475 }"
8476 ));
8477 cx.update_editor(|editor, cx| {
8478 editor.toggle_comments(toggle_comments, cx);
8479 });
8480 cx.assert_editor_state(indoc!(
8481 "fn a() {
8482 // «dog()ˇ»;
8483 cat();
8484 }"
8485 ));
8486
8487 // Multiple cursors on one line -> advance
8488 cx.set_state(indoc!(
8489 "fn a() {
8490 ˇdˇog();
8491 cat();
8492 }"
8493 ));
8494 cx.update_editor(|editor, cx| {
8495 editor.toggle_comments(toggle_comments, cx);
8496 });
8497 cx.assert_editor_state(indoc!(
8498 "fn a() {
8499 // dog();
8500 catˇ(ˇ);
8501 }"
8502 ));
8503
8504 // Multiple cursors on one line, with selection -> don't advance
8505 cx.set_state(indoc!(
8506 "fn a() {
8507 ˇdˇog«()ˇ»;
8508 cat();
8509 }"
8510 ));
8511 cx.update_editor(|editor, cx| {
8512 editor.toggle_comments(toggle_comments, cx);
8513 });
8514 cx.assert_editor_state(indoc!(
8515 "fn a() {
8516 // ˇdˇog«()ˇ»;
8517 cat();
8518 }"
8519 ));
8520
8521 // Single cursor on one line -> advance
8522 // Cursor moves to column 0 on blank line
8523 cx.set_state(indoc!(
8524 "fn a() {
8525 ˇdog();
8526
8527 cat();
8528 }"
8529 ));
8530 cx.update_editor(|editor, cx| {
8531 editor.toggle_comments(toggle_comments, cx);
8532 });
8533 cx.assert_editor_state(indoc!(
8534 "fn a() {
8535 // dog();
8536 ˇ
8537 cat();
8538 }"
8539 ));
8540
8541 // Single cursor on one line -> advance
8542 // Cursor starts and ends at column 0
8543 cx.set_state(indoc!(
8544 "fn a() {
8545 ˇ dog();
8546 cat();
8547 }"
8548 ));
8549 cx.update_editor(|editor, cx| {
8550 editor.toggle_comments(toggle_comments, cx);
8551 });
8552 cx.assert_editor_state(indoc!(
8553 "fn a() {
8554 // dog();
8555 ˇ cat();
8556 }"
8557 ));
8558}
8559
8560#[gpui::test]
8561async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8562 init_test(cx, |_| {});
8563
8564 let mut cx = EditorTestContext::new(cx).await;
8565
8566 let html_language = Arc::new(
8567 Language::new(
8568 LanguageConfig {
8569 name: "HTML".into(),
8570 block_comment: Some(("<!-- ".into(), " -->".into())),
8571 ..Default::default()
8572 },
8573 Some(tree_sitter_html::language()),
8574 )
8575 .with_injection_query(
8576 r#"
8577 (script_element
8578 (raw_text) @content
8579 (#set! "language" "javascript"))
8580 "#,
8581 )
8582 .unwrap(),
8583 );
8584
8585 let javascript_language = Arc::new(Language::new(
8586 LanguageConfig {
8587 name: "JavaScript".into(),
8588 line_comments: vec!["// ".into()],
8589 ..Default::default()
8590 },
8591 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8592 ));
8593
8594 cx.language_registry().add(html_language.clone());
8595 cx.language_registry().add(javascript_language.clone());
8596 cx.update_buffer(|buffer, cx| {
8597 buffer.set_language(Some(html_language), cx);
8598 });
8599
8600 // Toggle comments for empty selections
8601 cx.set_state(
8602 &r#"
8603 <p>A</p>ˇ
8604 <p>B</p>ˇ
8605 <p>C</p>ˇ
8606 "#
8607 .unindent(),
8608 );
8609 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8610 cx.assert_editor_state(
8611 &r#"
8612 <!-- <p>A</p>ˇ -->
8613 <!-- <p>B</p>ˇ -->
8614 <!-- <p>C</p>ˇ -->
8615 "#
8616 .unindent(),
8617 );
8618 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8619 cx.assert_editor_state(
8620 &r#"
8621 <p>A</p>ˇ
8622 <p>B</p>ˇ
8623 <p>C</p>ˇ
8624 "#
8625 .unindent(),
8626 );
8627
8628 // Toggle comments for mixture of empty and non-empty selections, where
8629 // multiple selections occupy a given line.
8630 cx.set_state(
8631 &r#"
8632 <p>A«</p>
8633 <p>ˇ»B</p>ˇ
8634 <p>C«</p>
8635 <p>ˇ»D</p>ˇ
8636 "#
8637 .unindent(),
8638 );
8639
8640 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8641 cx.assert_editor_state(
8642 &r#"
8643 <!-- <p>A«</p>
8644 <p>ˇ»B</p>ˇ -->
8645 <!-- <p>C«</p>
8646 <p>ˇ»D</p>ˇ -->
8647 "#
8648 .unindent(),
8649 );
8650 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8651 cx.assert_editor_state(
8652 &r#"
8653 <p>A«</p>
8654 <p>ˇ»B</p>ˇ
8655 <p>C«</p>
8656 <p>ˇ»D</p>ˇ
8657 "#
8658 .unindent(),
8659 );
8660
8661 // Toggle comments when different languages are active for different
8662 // selections.
8663 cx.set_state(
8664 &r#"
8665 ˇ<script>
8666 ˇvar x = new Y();
8667 ˇ</script>
8668 "#
8669 .unindent(),
8670 );
8671 cx.executor().run_until_parked();
8672 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8673 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8674 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8675 cx.assert_editor_state(
8676 &r#"
8677 <!-- ˇ<script> -->
8678 // ˇvar x = new Y();
8679 // ˇ</script>
8680 "#
8681 .unindent(),
8682 );
8683}
8684
8685#[gpui::test]
8686fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8687 init_test(cx, |_| {});
8688
8689 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8690 let multibuffer = cx.new_model(|cx| {
8691 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8692 multibuffer.push_excerpts(
8693 buffer.clone(),
8694 [
8695 ExcerptRange {
8696 context: Point::new(0, 0)..Point::new(0, 4),
8697 primary: None,
8698 },
8699 ExcerptRange {
8700 context: Point::new(1, 0)..Point::new(1, 4),
8701 primary: None,
8702 },
8703 ],
8704 cx,
8705 );
8706 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8707 multibuffer
8708 });
8709
8710 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8711 view.update(cx, |view, cx| {
8712 assert_eq!(view.text(cx), "aaaa\nbbbb");
8713 view.change_selections(None, cx, |s| {
8714 s.select_ranges([
8715 Point::new(0, 0)..Point::new(0, 0),
8716 Point::new(1, 0)..Point::new(1, 0),
8717 ])
8718 });
8719
8720 view.handle_input("X", cx);
8721 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8722 assert_eq!(
8723 view.selections.ranges(cx),
8724 [
8725 Point::new(0, 1)..Point::new(0, 1),
8726 Point::new(1, 1)..Point::new(1, 1),
8727 ]
8728 );
8729
8730 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8731 view.change_selections(None, cx, |s| {
8732 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8733 });
8734 view.backspace(&Default::default(), cx);
8735 assert_eq!(view.text(cx), "Xa\nbbb");
8736 assert_eq!(
8737 view.selections.ranges(cx),
8738 [Point::new(1, 0)..Point::new(1, 0)]
8739 );
8740
8741 view.change_selections(None, cx, |s| {
8742 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8743 });
8744 view.backspace(&Default::default(), cx);
8745 assert_eq!(view.text(cx), "X\nbb");
8746 assert_eq!(
8747 view.selections.ranges(cx),
8748 [Point::new(0, 1)..Point::new(0, 1)]
8749 );
8750 });
8751}
8752
8753#[gpui::test]
8754fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8755 init_test(cx, |_| {});
8756
8757 let markers = vec![('[', ']').into(), ('(', ')').into()];
8758 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8759 indoc! {"
8760 [aaaa
8761 (bbbb]
8762 cccc)",
8763 },
8764 markers.clone(),
8765 );
8766 let excerpt_ranges = markers.into_iter().map(|marker| {
8767 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8768 ExcerptRange {
8769 context,
8770 primary: None,
8771 }
8772 });
8773 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8774 let multibuffer = cx.new_model(|cx| {
8775 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8776 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8777 multibuffer
8778 });
8779
8780 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8781 view.update(cx, |view, cx| {
8782 let (expected_text, selection_ranges) = marked_text_ranges(
8783 indoc! {"
8784 aaaa
8785 bˇbbb
8786 bˇbbˇb
8787 cccc"
8788 },
8789 true,
8790 );
8791 assert_eq!(view.text(cx), expected_text);
8792 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8793
8794 view.handle_input("X", cx);
8795
8796 let (expected_text, expected_selections) = marked_text_ranges(
8797 indoc! {"
8798 aaaa
8799 bXˇbbXb
8800 bXˇbbXˇb
8801 cccc"
8802 },
8803 false,
8804 );
8805 assert_eq!(view.text(cx), expected_text);
8806 assert_eq!(view.selections.ranges(cx), expected_selections);
8807
8808 view.newline(&Newline, cx);
8809 let (expected_text, expected_selections) = marked_text_ranges(
8810 indoc! {"
8811 aaaa
8812 bX
8813 ˇbbX
8814 b
8815 bX
8816 ˇbbX
8817 ˇb
8818 cccc"
8819 },
8820 false,
8821 );
8822 assert_eq!(view.text(cx), expected_text);
8823 assert_eq!(view.selections.ranges(cx), expected_selections);
8824 });
8825}
8826
8827#[gpui::test]
8828fn test_refresh_selections(cx: &mut TestAppContext) {
8829 init_test(cx, |_| {});
8830
8831 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8832 let mut excerpt1_id = None;
8833 let multibuffer = cx.new_model(|cx| {
8834 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8835 excerpt1_id = multibuffer
8836 .push_excerpts(
8837 buffer.clone(),
8838 [
8839 ExcerptRange {
8840 context: Point::new(0, 0)..Point::new(1, 4),
8841 primary: None,
8842 },
8843 ExcerptRange {
8844 context: Point::new(1, 0)..Point::new(2, 4),
8845 primary: None,
8846 },
8847 ],
8848 cx,
8849 )
8850 .into_iter()
8851 .next();
8852 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8853 multibuffer
8854 });
8855
8856 let editor = cx.add_window(|cx| {
8857 let mut editor = build_editor(multibuffer.clone(), cx);
8858 let snapshot = editor.snapshot(cx);
8859 editor.change_selections(None, cx, |s| {
8860 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8861 });
8862 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8863 assert_eq!(
8864 editor.selections.ranges(cx),
8865 [
8866 Point::new(1, 3)..Point::new(1, 3),
8867 Point::new(2, 1)..Point::new(2, 1),
8868 ]
8869 );
8870 editor
8871 });
8872
8873 // Refreshing selections is a no-op when excerpts haven't changed.
8874 _ = editor.update(cx, |editor, cx| {
8875 editor.change_selections(None, cx, |s| s.refresh());
8876 assert_eq!(
8877 editor.selections.ranges(cx),
8878 [
8879 Point::new(1, 3)..Point::new(1, 3),
8880 Point::new(2, 1)..Point::new(2, 1),
8881 ]
8882 );
8883 });
8884
8885 multibuffer.update(cx, |multibuffer, cx| {
8886 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8887 });
8888 _ = editor.update(cx, |editor, cx| {
8889 // Removing an excerpt causes the first selection to become degenerate.
8890 assert_eq!(
8891 editor.selections.ranges(cx),
8892 [
8893 Point::new(0, 0)..Point::new(0, 0),
8894 Point::new(0, 1)..Point::new(0, 1)
8895 ]
8896 );
8897
8898 // Refreshing selections will relocate the first selection to the original buffer
8899 // location.
8900 editor.change_selections(None, cx, |s| s.refresh());
8901 assert_eq!(
8902 editor.selections.ranges(cx),
8903 [
8904 Point::new(0, 1)..Point::new(0, 1),
8905 Point::new(0, 3)..Point::new(0, 3)
8906 ]
8907 );
8908 assert!(editor.selections.pending_anchor().is_some());
8909 });
8910}
8911
8912#[gpui::test]
8913fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8914 init_test(cx, |_| {});
8915
8916 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8917 let mut excerpt1_id = None;
8918 let multibuffer = cx.new_model(|cx| {
8919 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8920 excerpt1_id = multibuffer
8921 .push_excerpts(
8922 buffer.clone(),
8923 [
8924 ExcerptRange {
8925 context: Point::new(0, 0)..Point::new(1, 4),
8926 primary: None,
8927 },
8928 ExcerptRange {
8929 context: Point::new(1, 0)..Point::new(2, 4),
8930 primary: None,
8931 },
8932 ],
8933 cx,
8934 )
8935 .into_iter()
8936 .next();
8937 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8938 multibuffer
8939 });
8940
8941 let editor = cx.add_window(|cx| {
8942 let mut editor = build_editor(multibuffer.clone(), cx);
8943 let snapshot = editor.snapshot(cx);
8944 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8945 assert_eq!(
8946 editor.selections.ranges(cx),
8947 [Point::new(1, 3)..Point::new(1, 3)]
8948 );
8949 editor
8950 });
8951
8952 multibuffer.update(cx, |multibuffer, cx| {
8953 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8954 });
8955 _ = editor.update(cx, |editor, cx| {
8956 assert_eq!(
8957 editor.selections.ranges(cx),
8958 [Point::new(0, 0)..Point::new(0, 0)]
8959 );
8960
8961 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8962 editor.change_selections(None, cx, |s| s.refresh());
8963 assert_eq!(
8964 editor.selections.ranges(cx),
8965 [Point::new(0, 3)..Point::new(0, 3)]
8966 );
8967 assert!(editor.selections.pending_anchor().is_some());
8968 });
8969}
8970
8971#[gpui::test]
8972async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8973 init_test(cx, |_| {});
8974
8975 let language = Arc::new(
8976 Language::new(
8977 LanguageConfig {
8978 brackets: BracketPairConfig {
8979 pairs: vec![
8980 BracketPair {
8981 start: "{".to_string(),
8982 end: "}".to_string(),
8983 close: true,
8984 surround: true,
8985 newline: true,
8986 },
8987 BracketPair {
8988 start: "/* ".to_string(),
8989 end: " */".to_string(),
8990 close: true,
8991 surround: true,
8992 newline: true,
8993 },
8994 ],
8995 ..Default::default()
8996 },
8997 ..Default::default()
8998 },
8999 Some(tree_sitter_rust::LANGUAGE.into()),
9000 )
9001 .with_indents_query("")
9002 .unwrap(),
9003 );
9004
9005 let text = concat!(
9006 "{ }\n", //
9007 " x\n", //
9008 " /* */\n", //
9009 "x\n", //
9010 "{{} }\n", //
9011 );
9012
9013 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9014 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9015 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9016 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9017 .await;
9018
9019 view.update(cx, |view, cx| {
9020 view.change_selections(None, cx, |s| {
9021 s.select_display_ranges([
9022 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9023 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9024 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9025 ])
9026 });
9027 view.newline(&Newline, cx);
9028
9029 assert_eq!(
9030 view.buffer().read(cx).read(cx).text(),
9031 concat!(
9032 "{ \n", // Suppress rustfmt
9033 "\n", //
9034 "}\n", //
9035 " x\n", //
9036 " /* \n", //
9037 " \n", //
9038 " */\n", //
9039 "x\n", //
9040 "{{} \n", //
9041 "}\n", //
9042 )
9043 );
9044 });
9045}
9046
9047#[gpui::test]
9048fn test_highlighted_ranges(cx: &mut TestAppContext) {
9049 init_test(cx, |_| {});
9050
9051 let editor = cx.add_window(|cx| {
9052 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9053 build_editor(buffer.clone(), cx)
9054 });
9055
9056 _ = editor.update(cx, |editor, cx| {
9057 struct Type1;
9058 struct Type2;
9059
9060 let buffer = editor.buffer.read(cx).snapshot(cx);
9061
9062 let anchor_range =
9063 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9064
9065 editor.highlight_background::<Type1>(
9066 &[
9067 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9068 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9069 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9070 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9071 ],
9072 |_| Hsla::red(),
9073 cx,
9074 );
9075 editor.highlight_background::<Type2>(
9076 &[
9077 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9078 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9079 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9080 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9081 ],
9082 |_| Hsla::green(),
9083 cx,
9084 );
9085
9086 let snapshot = editor.snapshot(cx);
9087 let mut highlighted_ranges = editor.background_highlights_in_range(
9088 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9089 &snapshot,
9090 cx.theme().colors(),
9091 );
9092 // Enforce a consistent ordering based on color without relying on the ordering of the
9093 // highlight's `TypeId` which is non-executor.
9094 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9095 assert_eq!(
9096 highlighted_ranges,
9097 &[
9098 (
9099 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9100 Hsla::red(),
9101 ),
9102 (
9103 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9104 Hsla::red(),
9105 ),
9106 (
9107 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9108 Hsla::green(),
9109 ),
9110 (
9111 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9112 Hsla::green(),
9113 ),
9114 ]
9115 );
9116 assert_eq!(
9117 editor.background_highlights_in_range(
9118 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9119 &snapshot,
9120 cx.theme().colors(),
9121 ),
9122 &[(
9123 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9124 Hsla::red(),
9125 )]
9126 );
9127 });
9128}
9129
9130#[gpui::test]
9131async fn test_following(cx: &mut gpui::TestAppContext) {
9132 init_test(cx, |_| {});
9133
9134 let fs = FakeFs::new(cx.executor());
9135 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9136
9137 let buffer = project.update(cx, |project, cx| {
9138 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9139 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9140 });
9141 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9142 let follower = cx.update(|cx| {
9143 cx.open_window(
9144 WindowOptions {
9145 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9146 gpui::Point::new(px(0.), px(0.)),
9147 gpui::Point::new(px(10.), px(80.)),
9148 ))),
9149 ..Default::default()
9150 },
9151 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9152 )
9153 .unwrap()
9154 });
9155
9156 let is_still_following = Rc::new(RefCell::new(true));
9157 let follower_edit_event_count = Rc::new(RefCell::new(0));
9158 let pending_update = Rc::new(RefCell::new(None));
9159 _ = follower.update(cx, {
9160 let update = pending_update.clone();
9161 let is_still_following = is_still_following.clone();
9162 let follower_edit_event_count = follower_edit_event_count.clone();
9163 |_, cx| {
9164 cx.subscribe(
9165 &leader.root_view(cx).unwrap(),
9166 move |_, leader, event, cx| {
9167 leader
9168 .read(cx)
9169 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9170 },
9171 )
9172 .detach();
9173
9174 cx.subscribe(
9175 &follower.root_view(cx).unwrap(),
9176 move |_, _, event: &EditorEvent, _cx| {
9177 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9178 *is_still_following.borrow_mut() = false;
9179 }
9180
9181 if let EditorEvent::BufferEdited = event {
9182 *follower_edit_event_count.borrow_mut() += 1;
9183 }
9184 },
9185 )
9186 .detach();
9187 }
9188 });
9189
9190 // Update the selections only
9191 _ = leader.update(cx, |leader, cx| {
9192 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9193 });
9194 follower
9195 .update(cx, |follower, cx| {
9196 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9197 })
9198 .unwrap()
9199 .await
9200 .unwrap();
9201 _ = follower.update(cx, |follower, cx| {
9202 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9203 });
9204 assert!(*is_still_following.borrow());
9205 assert_eq!(*follower_edit_event_count.borrow(), 0);
9206
9207 // Update the scroll position only
9208 _ = leader.update(cx, |leader, cx| {
9209 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9210 });
9211 follower
9212 .update(cx, |follower, cx| {
9213 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9214 })
9215 .unwrap()
9216 .await
9217 .unwrap();
9218 assert_eq!(
9219 follower
9220 .update(cx, |follower, cx| follower.scroll_position(cx))
9221 .unwrap(),
9222 gpui::Point::new(1.5, 3.5)
9223 );
9224 assert!(*is_still_following.borrow());
9225 assert_eq!(*follower_edit_event_count.borrow(), 0);
9226
9227 // Update the selections and scroll position. The follower's scroll position is updated
9228 // via autoscroll, not via the leader's exact scroll position.
9229 _ = leader.update(cx, |leader, cx| {
9230 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9231 leader.request_autoscroll(Autoscroll::newest(), cx);
9232 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9233 });
9234 follower
9235 .update(cx, |follower, cx| {
9236 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9237 })
9238 .unwrap()
9239 .await
9240 .unwrap();
9241 _ = follower.update(cx, |follower, cx| {
9242 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9243 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9244 });
9245 assert!(*is_still_following.borrow());
9246
9247 // Creating a pending selection that precedes another selection
9248 _ = leader.update(cx, |leader, cx| {
9249 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9250 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9251 });
9252 follower
9253 .update(cx, |follower, cx| {
9254 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9255 })
9256 .unwrap()
9257 .await
9258 .unwrap();
9259 _ = follower.update(cx, |follower, cx| {
9260 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9261 });
9262 assert!(*is_still_following.borrow());
9263
9264 // Extend the pending selection so that it surrounds another selection
9265 _ = leader.update(cx, |leader, cx| {
9266 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9267 });
9268 follower
9269 .update(cx, |follower, cx| {
9270 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9271 })
9272 .unwrap()
9273 .await
9274 .unwrap();
9275 _ = follower.update(cx, |follower, cx| {
9276 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9277 });
9278
9279 // Scrolling locally breaks the follow
9280 _ = follower.update(cx, |follower, cx| {
9281 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9282 follower.set_scroll_anchor(
9283 ScrollAnchor {
9284 anchor: top_anchor,
9285 offset: gpui::Point::new(0.0, 0.5),
9286 },
9287 cx,
9288 );
9289 });
9290 assert!(!(*is_still_following.borrow()));
9291}
9292
9293#[gpui::test]
9294async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9295 init_test(cx, |_| {});
9296
9297 let fs = FakeFs::new(cx.executor());
9298 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9299 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9300 let pane = workspace
9301 .update(cx, |workspace, _| workspace.active_pane().clone())
9302 .unwrap();
9303
9304 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9305
9306 let leader = pane.update(cx, |_, cx| {
9307 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
9308 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9309 });
9310
9311 // Start following the editor when it has no excerpts.
9312 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9313 let follower_1 = cx
9314 .update_window(*workspace.deref(), |_, cx| {
9315 Editor::from_state_proto(
9316 workspace.root_view(cx).unwrap(),
9317 ViewId {
9318 creator: Default::default(),
9319 id: 0,
9320 },
9321 &mut state_message,
9322 cx,
9323 )
9324 })
9325 .unwrap()
9326 .unwrap()
9327 .await
9328 .unwrap();
9329
9330 let update_message = Rc::new(RefCell::new(None));
9331 follower_1.update(cx, {
9332 let update = update_message.clone();
9333 |_, cx| {
9334 cx.subscribe(&leader, move |_, leader, event, cx| {
9335 leader
9336 .read(cx)
9337 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9338 })
9339 .detach();
9340 }
9341 });
9342
9343 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9344 (
9345 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9346 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9347 )
9348 });
9349
9350 // Insert some excerpts.
9351 leader.update(cx, |leader, cx| {
9352 leader.buffer.update(cx, |multibuffer, cx| {
9353 let excerpt_ids = multibuffer.push_excerpts(
9354 buffer_1.clone(),
9355 [
9356 ExcerptRange {
9357 context: 1..6,
9358 primary: None,
9359 },
9360 ExcerptRange {
9361 context: 12..15,
9362 primary: None,
9363 },
9364 ExcerptRange {
9365 context: 0..3,
9366 primary: None,
9367 },
9368 ],
9369 cx,
9370 );
9371 multibuffer.insert_excerpts_after(
9372 excerpt_ids[0],
9373 buffer_2.clone(),
9374 [
9375 ExcerptRange {
9376 context: 8..12,
9377 primary: None,
9378 },
9379 ExcerptRange {
9380 context: 0..6,
9381 primary: None,
9382 },
9383 ],
9384 cx,
9385 );
9386 });
9387 });
9388
9389 // Apply the update of adding the excerpts.
9390 follower_1
9391 .update(cx, |follower, cx| {
9392 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9393 })
9394 .await
9395 .unwrap();
9396 assert_eq!(
9397 follower_1.update(cx, |editor, cx| editor.text(cx)),
9398 leader.update(cx, |editor, cx| editor.text(cx))
9399 );
9400 update_message.borrow_mut().take();
9401
9402 // Start following separately after it already has excerpts.
9403 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9404 let follower_2 = cx
9405 .update_window(*workspace.deref(), |_, cx| {
9406 Editor::from_state_proto(
9407 workspace.root_view(cx).unwrap().clone(),
9408 ViewId {
9409 creator: Default::default(),
9410 id: 0,
9411 },
9412 &mut state_message,
9413 cx,
9414 )
9415 })
9416 .unwrap()
9417 .unwrap()
9418 .await
9419 .unwrap();
9420 assert_eq!(
9421 follower_2.update(cx, |editor, cx| editor.text(cx)),
9422 leader.update(cx, |editor, cx| editor.text(cx))
9423 );
9424
9425 // Remove some excerpts.
9426 leader.update(cx, |leader, cx| {
9427 leader.buffer.update(cx, |multibuffer, cx| {
9428 let excerpt_ids = multibuffer.excerpt_ids();
9429 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9430 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9431 });
9432 });
9433
9434 // Apply the update of removing the excerpts.
9435 follower_1
9436 .update(cx, |follower, cx| {
9437 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9438 })
9439 .await
9440 .unwrap();
9441 follower_2
9442 .update(cx, |follower, cx| {
9443 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9444 })
9445 .await
9446 .unwrap();
9447 update_message.borrow_mut().take();
9448 assert_eq!(
9449 follower_1.update(cx, |editor, cx| editor.text(cx)),
9450 leader.update(cx, |editor, cx| editor.text(cx))
9451 );
9452}
9453
9454#[gpui::test]
9455async fn go_to_prev_overlapping_diagnostic(
9456 executor: BackgroundExecutor,
9457 cx: &mut gpui::TestAppContext,
9458) {
9459 init_test(cx, |_| {});
9460
9461 let mut cx = EditorTestContext::new(cx).await;
9462 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9463
9464 cx.set_state(indoc! {"
9465 ˇfn func(abc def: i32) -> u32 {
9466 }
9467 "});
9468
9469 cx.update(|cx| {
9470 project.update(cx, |project, cx| {
9471 project
9472 .update_diagnostics(
9473 LanguageServerId(0),
9474 lsp::PublishDiagnosticsParams {
9475 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9476 version: None,
9477 diagnostics: vec![
9478 lsp::Diagnostic {
9479 range: lsp::Range::new(
9480 lsp::Position::new(0, 11),
9481 lsp::Position::new(0, 12),
9482 ),
9483 severity: Some(lsp::DiagnosticSeverity::ERROR),
9484 ..Default::default()
9485 },
9486 lsp::Diagnostic {
9487 range: lsp::Range::new(
9488 lsp::Position::new(0, 12),
9489 lsp::Position::new(0, 15),
9490 ),
9491 severity: Some(lsp::DiagnosticSeverity::ERROR),
9492 ..Default::default()
9493 },
9494 lsp::Diagnostic {
9495 range: lsp::Range::new(
9496 lsp::Position::new(0, 25),
9497 lsp::Position::new(0, 28),
9498 ),
9499 severity: Some(lsp::DiagnosticSeverity::ERROR),
9500 ..Default::default()
9501 },
9502 ],
9503 },
9504 &[],
9505 cx,
9506 )
9507 .unwrap()
9508 });
9509 });
9510
9511 executor.run_until_parked();
9512
9513 cx.update_editor(|editor, cx| {
9514 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9515 });
9516
9517 cx.assert_editor_state(indoc! {"
9518 fn func(abc def: i32) -> ˇu32 {
9519 }
9520 "});
9521
9522 cx.update_editor(|editor, cx| {
9523 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9524 });
9525
9526 cx.assert_editor_state(indoc! {"
9527 fn func(abc ˇdef: i32) -> u32 {
9528 }
9529 "});
9530
9531 cx.update_editor(|editor, cx| {
9532 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9533 });
9534
9535 cx.assert_editor_state(indoc! {"
9536 fn func(abcˇ def: i32) -> u32 {
9537 }
9538 "});
9539
9540 cx.update_editor(|editor, cx| {
9541 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9542 });
9543
9544 cx.assert_editor_state(indoc! {"
9545 fn func(abc def: i32) -> ˇu32 {
9546 }
9547 "});
9548}
9549
9550#[gpui::test]
9551async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9552 init_test(cx, |_| {});
9553
9554 let mut cx = EditorTestContext::new(cx).await;
9555
9556 cx.set_state(indoc! {"
9557 fn func(abˇc def: i32) -> u32 {
9558 }
9559 "});
9560 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9561
9562 cx.update(|cx| {
9563 project.update(cx, |project, cx| {
9564 project.update_diagnostics(
9565 LanguageServerId(0),
9566 lsp::PublishDiagnosticsParams {
9567 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9568 version: None,
9569 diagnostics: vec![lsp::Diagnostic {
9570 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9571 severity: Some(lsp::DiagnosticSeverity::ERROR),
9572 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9573 ..Default::default()
9574 }],
9575 },
9576 &[],
9577 cx,
9578 )
9579 })
9580 }).unwrap();
9581 cx.run_until_parked();
9582 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9583 cx.run_until_parked();
9584 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9585}
9586
9587#[gpui::test]
9588async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9589 init_test(cx, |_| {});
9590
9591 let mut cx = EditorTestContext::new(cx).await;
9592
9593 let diff_base = r#"
9594 use some::mod;
9595
9596 const A: u32 = 42;
9597
9598 fn main() {
9599 println!("hello");
9600
9601 println!("world");
9602 }
9603 "#
9604 .unindent();
9605
9606 // Edits are modified, removed, modified, added
9607 cx.set_state(
9608 &r#"
9609 use some::modified;
9610
9611 ˇ
9612 fn main() {
9613 println!("hello there");
9614
9615 println!("around the");
9616 println!("world");
9617 }
9618 "#
9619 .unindent(),
9620 );
9621
9622 cx.set_diff_base(Some(&diff_base));
9623 executor.run_until_parked();
9624
9625 cx.update_editor(|editor, cx| {
9626 //Wrap around the bottom of the buffer
9627 for _ in 0..3 {
9628 editor.go_to_hunk(&GoToHunk, cx);
9629 }
9630 });
9631
9632 cx.assert_editor_state(
9633 &r#"
9634 ˇuse some::modified;
9635
9636
9637 fn main() {
9638 println!("hello there");
9639
9640 println!("around the");
9641 println!("world");
9642 }
9643 "#
9644 .unindent(),
9645 );
9646
9647 cx.update_editor(|editor, cx| {
9648 //Wrap around the top of the buffer
9649 for _ in 0..2 {
9650 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9651 }
9652 });
9653
9654 cx.assert_editor_state(
9655 &r#"
9656 use some::modified;
9657
9658
9659 fn main() {
9660 ˇ println!("hello there");
9661
9662 println!("around the");
9663 println!("world");
9664 }
9665 "#
9666 .unindent(),
9667 );
9668
9669 cx.update_editor(|editor, cx| {
9670 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9671 });
9672
9673 cx.assert_editor_state(
9674 &r#"
9675 use some::modified;
9676
9677 ˇ
9678 fn main() {
9679 println!("hello there");
9680
9681 println!("around the");
9682 println!("world");
9683 }
9684 "#
9685 .unindent(),
9686 );
9687
9688 cx.update_editor(|editor, cx| {
9689 for _ in 0..3 {
9690 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9691 }
9692 });
9693
9694 cx.assert_editor_state(
9695 &r#"
9696 use some::modified;
9697
9698
9699 fn main() {
9700 ˇ println!("hello there");
9701
9702 println!("around the");
9703 println!("world");
9704 }
9705 "#
9706 .unindent(),
9707 );
9708
9709 cx.update_editor(|editor, cx| {
9710 editor.fold(&Fold, cx);
9711
9712 //Make sure that the fold only gets one hunk
9713 for _ in 0..4 {
9714 editor.go_to_hunk(&GoToHunk, cx);
9715 }
9716 });
9717
9718 cx.assert_editor_state(
9719 &r#"
9720 ˇuse some::modified;
9721
9722
9723 fn main() {
9724 println!("hello there");
9725
9726 println!("around the");
9727 println!("world");
9728 }
9729 "#
9730 .unindent(),
9731 );
9732}
9733
9734#[test]
9735fn test_split_words() {
9736 fn split(text: &str) -> Vec<&str> {
9737 split_words(text).collect()
9738 }
9739
9740 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9741 assert_eq!(split("hello_world"), &["hello_", "world"]);
9742 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9743 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9744 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9745 assert_eq!(split("helloworld"), &["helloworld"]);
9746
9747 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9748}
9749
9750#[gpui::test]
9751async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9752 init_test(cx, |_| {});
9753
9754 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9755 let mut assert = |before, after| {
9756 let _state_context = cx.set_state(before);
9757 cx.update_editor(|editor, cx| {
9758 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9759 });
9760 cx.assert_editor_state(after);
9761 };
9762
9763 // Outside bracket jumps to outside of matching bracket
9764 assert("console.logˇ(var);", "console.log(var)ˇ;");
9765 assert("console.log(var)ˇ;", "console.logˇ(var);");
9766
9767 // Inside bracket jumps to inside of matching bracket
9768 assert("console.log(ˇvar);", "console.log(varˇ);");
9769 assert("console.log(varˇ);", "console.log(ˇvar);");
9770
9771 // When outside a bracket and inside, favor jumping to the inside bracket
9772 assert(
9773 "console.log('foo', [1, 2, 3]ˇ);",
9774 "console.log(ˇ'foo', [1, 2, 3]);",
9775 );
9776 assert(
9777 "console.log(ˇ'foo', [1, 2, 3]);",
9778 "console.log('foo', [1, 2, 3]ˇ);",
9779 );
9780
9781 // Bias forward if two options are equally likely
9782 assert(
9783 "let result = curried_fun()ˇ();",
9784 "let result = curried_fun()()ˇ;",
9785 );
9786
9787 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9788 assert(
9789 indoc! {"
9790 function test() {
9791 console.log('test')ˇ
9792 }"},
9793 indoc! {"
9794 function test() {
9795 console.logˇ('test')
9796 }"},
9797 );
9798}
9799
9800#[gpui::test]
9801async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9802 init_test(cx, |_| {});
9803
9804 let fs = FakeFs::new(cx.executor());
9805 fs.insert_tree(
9806 "/a",
9807 json!({
9808 "main.rs": "fn main() { let a = 5; }",
9809 "other.rs": "// Test file",
9810 }),
9811 )
9812 .await;
9813 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9814
9815 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9816 language_registry.add(Arc::new(Language::new(
9817 LanguageConfig {
9818 name: "Rust".into(),
9819 matcher: LanguageMatcher {
9820 path_suffixes: vec!["rs".to_string()],
9821 ..Default::default()
9822 },
9823 brackets: BracketPairConfig {
9824 pairs: vec![BracketPair {
9825 start: "{".to_string(),
9826 end: "}".to_string(),
9827 close: true,
9828 surround: true,
9829 newline: true,
9830 }],
9831 disabled_scopes_by_bracket_ix: Vec::new(),
9832 },
9833 ..Default::default()
9834 },
9835 Some(tree_sitter_rust::LANGUAGE.into()),
9836 )));
9837 let mut fake_servers = language_registry.register_fake_lsp(
9838 "Rust",
9839 FakeLspAdapter {
9840 capabilities: lsp::ServerCapabilities {
9841 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9842 first_trigger_character: "{".to_string(),
9843 more_trigger_character: None,
9844 }),
9845 ..Default::default()
9846 },
9847 ..Default::default()
9848 },
9849 );
9850
9851 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9852
9853 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9854
9855 let worktree_id = workspace
9856 .update(cx, |workspace, cx| {
9857 workspace.project().update(cx, |project, cx| {
9858 project.worktrees(cx).next().unwrap().read(cx).id()
9859 })
9860 })
9861 .unwrap();
9862
9863 let buffer = project
9864 .update(cx, |project, cx| {
9865 project.open_local_buffer("/a/main.rs", cx)
9866 })
9867 .await
9868 .unwrap();
9869 cx.executor().run_until_parked();
9870 cx.executor().start_waiting();
9871 let fake_server = fake_servers.next().await.unwrap();
9872 let editor_handle = workspace
9873 .update(cx, |workspace, cx| {
9874 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9875 })
9876 .unwrap()
9877 .await
9878 .unwrap()
9879 .downcast::<Editor>()
9880 .unwrap();
9881
9882 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9883 assert_eq!(
9884 params.text_document_position.text_document.uri,
9885 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9886 );
9887 assert_eq!(
9888 params.text_document_position.position,
9889 lsp::Position::new(0, 21),
9890 );
9891
9892 Ok(Some(vec![lsp::TextEdit {
9893 new_text: "]".to_string(),
9894 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9895 }]))
9896 });
9897
9898 editor_handle.update(cx, |editor, cx| {
9899 editor.focus(cx);
9900 editor.change_selections(None, cx, |s| {
9901 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9902 });
9903 editor.handle_input("{", cx);
9904 });
9905
9906 cx.executor().run_until_parked();
9907
9908 buffer.update(cx, |buffer, _| {
9909 assert_eq!(
9910 buffer.text(),
9911 "fn main() { let a = {5}; }",
9912 "No extra braces from on type formatting should appear in the buffer"
9913 )
9914 });
9915}
9916
9917#[gpui::test]
9918async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9919 init_test(cx, |_| {});
9920
9921 let fs = FakeFs::new(cx.executor());
9922 fs.insert_tree(
9923 "/a",
9924 json!({
9925 "main.rs": "fn main() { let a = 5; }",
9926 "other.rs": "// Test file",
9927 }),
9928 )
9929 .await;
9930
9931 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9932
9933 let server_restarts = Arc::new(AtomicUsize::new(0));
9934 let closure_restarts = Arc::clone(&server_restarts);
9935 let language_server_name = "test language server";
9936 let language_name: LanguageName = "Rust".into();
9937
9938 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9939 language_registry.add(Arc::new(Language::new(
9940 LanguageConfig {
9941 name: language_name.clone(),
9942 matcher: LanguageMatcher {
9943 path_suffixes: vec!["rs".to_string()],
9944 ..Default::default()
9945 },
9946 ..Default::default()
9947 },
9948 Some(tree_sitter_rust::LANGUAGE.into()),
9949 )));
9950 let mut fake_servers = language_registry.register_fake_lsp(
9951 "Rust",
9952 FakeLspAdapter {
9953 name: language_server_name,
9954 initialization_options: Some(json!({
9955 "testOptionValue": true
9956 })),
9957 initializer: Some(Box::new(move |fake_server| {
9958 let task_restarts = Arc::clone(&closure_restarts);
9959 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9960 task_restarts.fetch_add(1, atomic::Ordering::Release);
9961 futures::future::ready(Ok(()))
9962 });
9963 })),
9964 ..Default::default()
9965 },
9966 );
9967
9968 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9969 let _buffer = project
9970 .update(cx, |project, cx| {
9971 project.open_local_buffer("/a/main.rs", cx)
9972 })
9973 .await
9974 .unwrap();
9975 let _fake_server = fake_servers.next().await.unwrap();
9976 update_test_language_settings(cx, |language_settings| {
9977 language_settings.languages.insert(
9978 language_name.clone(),
9979 LanguageSettingsContent {
9980 tab_size: NonZeroU32::new(8),
9981 ..Default::default()
9982 },
9983 );
9984 });
9985 cx.executor().run_until_parked();
9986 assert_eq!(
9987 server_restarts.load(atomic::Ordering::Acquire),
9988 0,
9989 "Should not restart LSP server on an unrelated change"
9990 );
9991
9992 update_test_project_settings(cx, |project_settings| {
9993 project_settings.lsp.insert(
9994 "Some other server name".into(),
9995 LspSettings {
9996 binary: None,
9997 settings: None,
9998 initialization_options: Some(json!({
9999 "some other init value": false
10000 })),
10001 },
10002 );
10003 });
10004 cx.executor().run_until_parked();
10005 assert_eq!(
10006 server_restarts.load(atomic::Ordering::Acquire),
10007 0,
10008 "Should not restart LSP server on an unrelated LSP settings change"
10009 );
10010
10011 update_test_project_settings(cx, |project_settings| {
10012 project_settings.lsp.insert(
10013 language_server_name.into(),
10014 LspSettings {
10015 binary: None,
10016 settings: None,
10017 initialization_options: Some(json!({
10018 "anotherInitValue": false
10019 })),
10020 },
10021 );
10022 });
10023 cx.executor().run_until_parked();
10024 assert_eq!(
10025 server_restarts.load(atomic::Ordering::Acquire),
10026 1,
10027 "Should restart LSP server on a related LSP settings change"
10028 );
10029
10030 update_test_project_settings(cx, |project_settings| {
10031 project_settings.lsp.insert(
10032 language_server_name.into(),
10033 LspSettings {
10034 binary: None,
10035 settings: None,
10036 initialization_options: Some(json!({
10037 "anotherInitValue": false
10038 })),
10039 },
10040 );
10041 });
10042 cx.executor().run_until_parked();
10043 assert_eq!(
10044 server_restarts.load(atomic::Ordering::Acquire),
10045 1,
10046 "Should not restart LSP server on a related LSP settings change that is the same"
10047 );
10048
10049 update_test_project_settings(cx, |project_settings| {
10050 project_settings.lsp.insert(
10051 language_server_name.into(),
10052 LspSettings {
10053 binary: None,
10054 settings: None,
10055 initialization_options: None,
10056 },
10057 );
10058 });
10059 cx.executor().run_until_parked();
10060 assert_eq!(
10061 server_restarts.load(atomic::Ordering::Acquire),
10062 2,
10063 "Should restart LSP server on another related LSP settings change"
10064 );
10065}
10066
10067#[gpui::test]
10068async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10069 init_test(cx, |_| {});
10070
10071 let mut cx = EditorLspTestContext::new_rust(
10072 lsp::ServerCapabilities {
10073 completion_provider: Some(lsp::CompletionOptions {
10074 trigger_characters: Some(vec![".".to_string()]),
10075 resolve_provider: Some(true),
10076 ..Default::default()
10077 }),
10078 ..Default::default()
10079 },
10080 cx,
10081 )
10082 .await;
10083
10084 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10085 cx.simulate_keystroke(".");
10086 let completion_item = lsp::CompletionItem {
10087 label: "some".into(),
10088 kind: Some(lsp::CompletionItemKind::SNIPPET),
10089 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10090 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10091 kind: lsp::MarkupKind::Markdown,
10092 value: "```rust\nSome(2)\n```".to_string(),
10093 })),
10094 deprecated: Some(false),
10095 sort_text: Some("fffffff2".to_string()),
10096 filter_text: Some("some".to_string()),
10097 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10098 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10099 range: lsp::Range {
10100 start: lsp::Position {
10101 line: 0,
10102 character: 22,
10103 },
10104 end: lsp::Position {
10105 line: 0,
10106 character: 22,
10107 },
10108 },
10109 new_text: "Some(2)".to_string(),
10110 })),
10111 additional_text_edits: Some(vec![lsp::TextEdit {
10112 range: lsp::Range {
10113 start: lsp::Position {
10114 line: 0,
10115 character: 20,
10116 },
10117 end: lsp::Position {
10118 line: 0,
10119 character: 22,
10120 },
10121 },
10122 new_text: "".to_string(),
10123 }]),
10124 ..Default::default()
10125 };
10126
10127 let closure_completion_item = completion_item.clone();
10128 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10129 let task_completion_item = closure_completion_item.clone();
10130 async move {
10131 Ok(Some(lsp::CompletionResponse::Array(vec![
10132 task_completion_item,
10133 ])))
10134 }
10135 });
10136
10137 request.next().await;
10138
10139 cx.condition(|editor, _| editor.context_menu_visible())
10140 .await;
10141 let apply_additional_edits = cx.update_editor(|editor, cx| {
10142 editor
10143 .confirm_completion(&ConfirmCompletion::default(), cx)
10144 .unwrap()
10145 });
10146 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10147
10148 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10149 let task_completion_item = completion_item.clone();
10150 async move { Ok(task_completion_item) }
10151 })
10152 .next()
10153 .await
10154 .unwrap();
10155 apply_additional_edits.await.unwrap();
10156 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10157}
10158
10159#[gpui::test]
10160async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10161 init_test(cx, |_| {});
10162
10163 let mut cx = EditorLspTestContext::new(
10164 Language::new(
10165 LanguageConfig {
10166 matcher: LanguageMatcher {
10167 path_suffixes: vec!["jsx".into()],
10168 ..Default::default()
10169 },
10170 overrides: [(
10171 "element".into(),
10172 LanguageConfigOverride {
10173 word_characters: Override::Set(['-'].into_iter().collect()),
10174 ..Default::default()
10175 },
10176 )]
10177 .into_iter()
10178 .collect(),
10179 ..Default::default()
10180 },
10181 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10182 )
10183 .with_override_query("(jsx_self_closing_element) @element")
10184 .unwrap(),
10185 lsp::ServerCapabilities {
10186 completion_provider: Some(lsp::CompletionOptions {
10187 trigger_characters: Some(vec![":".to_string()]),
10188 ..Default::default()
10189 }),
10190 ..Default::default()
10191 },
10192 cx,
10193 )
10194 .await;
10195
10196 cx.lsp
10197 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10198 Ok(Some(lsp::CompletionResponse::Array(vec![
10199 lsp::CompletionItem {
10200 label: "bg-blue".into(),
10201 ..Default::default()
10202 },
10203 lsp::CompletionItem {
10204 label: "bg-red".into(),
10205 ..Default::default()
10206 },
10207 lsp::CompletionItem {
10208 label: "bg-yellow".into(),
10209 ..Default::default()
10210 },
10211 ])))
10212 });
10213
10214 cx.set_state(r#"<p class="bgˇ" />"#);
10215
10216 // Trigger completion when typing a dash, because the dash is an extra
10217 // word character in the 'element' scope, which contains the cursor.
10218 cx.simulate_keystroke("-");
10219 cx.executor().run_until_parked();
10220 cx.update_editor(|editor, _| {
10221 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10222 assert_eq!(
10223 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10224 &["bg-red", "bg-blue", "bg-yellow"]
10225 );
10226 } else {
10227 panic!("expected completion menu to be open");
10228 }
10229 });
10230
10231 cx.simulate_keystroke("l");
10232 cx.executor().run_until_parked();
10233 cx.update_editor(|editor, _| {
10234 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10235 assert_eq!(
10236 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10237 &["bg-blue", "bg-yellow"]
10238 );
10239 } else {
10240 panic!("expected completion menu to be open");
10241 }
10242 });
10243
10244 // When filtering completions, consider the character after the '-' to
10245 // be the start of a subword.
10246 cx.set_state(r#"<p class="yelˇ" />"#);
10247 cx.simulate_keystroke("l");
10248 cx.executor().run_until_parked();
10249 cx.update_editor(|editor, _| {
10250 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10251 assert_eq!(
10252 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10253 &["bg-yellow"]
10254 );
10255 } else {
10256 panic!("expected completion menu to be open");
10257 }
10258 });
10259}
10260
10261#[gpui::test]
10262async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10263 init_test(cx, |settings| {
10264 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10265 FormatterList(vec![Formatter::Prettier].into()),
10266 ))
10267 });
10268
10269 let fs = FakeFs::new(cx.executor());
10270 fs.insert_file("/file.ts", Default::default()).await;
10271
10272 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10273 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10274
10275 language_registry.add(Arc::new(Language::new(
10276 LanguageConfig {
10277 name: "TypeScript".into(),
10278 matcher: LanguageMatcher {
10279 path_suffixes: vec!["ts".to_string()],
10280 ..Default::default()
10281 },
10282 ..Default::default()
10283 },
10284 Some(tree_sitter_rust::LANGUAGE.into()),
10285 )));
10286 update_test_language_settings(cx, |settings| {
10287 settings.defaults.prettier = Some(PrettierSettings {
10288 allowed: true,
10289 ..PrettierSettings::default()
10290 });
10291 });
10292
10293 let test_plugin = "test_plugin";
10294 let _ = language_registry.register_fake_lsp(
10295 "TypeScript",
10296 FakeLspAdapter {
10297 prettier_plugins: vec![test_plugin],
10298 ..Default::default()
10299 },
10300 );
10301
10302 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10303 let buffer = project
10304 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10305 .await
10306 .unwrap();
10307
10308 let buffer_text = "one\ntwo\nthree\n";
10309 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10310 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10311 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10312
10313 editor
10314 .update(cx, |editor, cx| {
10315 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10316 })
10317 .unwrap()
10318 .await;
10319 assert_eq!(
10320 editor.update(cx, |editor, cx| editor.text(cx)),
10321 buffer_text.to_string() + prettier_format_suffix,
10322 "Test prettier formatting was not applied to the original buffer text",
10323 );
10324
10325 update_test_language_settings(cx, |settings| {
10326 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10327 });
10328 let format = editor.update(cx, |editor, cx| {
10329 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
10330 });
10331 format.await.unwrap();
10332 assert_eq!(
10333 editor.update(cx, |editor, cx| editor.text(cx)),
10334 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10335 "Autoformatting (via test prettier) was not applied to the original buffer text",
10336 );
10337}
10338
10339#[gpui::test]
10340async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10341 init_test(cx, |_| {});
10342 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10343 let base_text = indoc! {r#"struct Row;
10344struct Row1;
10345struct Row2;
10346
10347struct Row4;
10348struct Row5;
10349struct Row6;
10350
10351struct Row8;
10352struct Row9;
10353struct Row10;"#};
10354
10355 // When addition hunks are not adjacent to carets, no hunk revert is performed
10356 assert_hunk_revert(
10357 indoc! {r#"struct Row;
10358 struct Row1;
10359 struct Row1.1;
10360 struct Row1.2;
10361 struct Row2;ˇ
10362
10363 struct Row4;
10364 struct Row5;
10365 struct Row6;
10366
10367 struct Row8;
10368 ˇstruct Row9;
10369 struct Row9.1;
10370 struct Row9.2;
10371 struct Row9.3;
10372 struct Row10;"#},
10373 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10374 indoc! {r#"struct Row;
10375 struct Row1;
10376 struct Row1.1;
10377 struct Row1.2;
10378 struct Row2;ˇ
10379
10380 struct Row4;
10381 struct Row5;
10382 struct Row6;
10383
10384 struct Row8;
10385 ˇstruct Row9;
10386 struct Row9.1;
10387 struct Row9.2;
10388 struct Row9.3;
10389 struct Row10;"#},
10390 base_text,
10391 &mut cx,
10392 );
10393 // Same for selections
10394 assert_hunk_revert(
10395 indoc! {r#"struct Row;
10396 struct Row1;
10397 struct Row2;
10398 struct Row2.1;
10399 struct Row2.2;
10400 «ˇ
10401 struct Row4;
10402 struct» Row5;
10403 «struct Row6;
10404 ˇ»
10405 struct Row9.1;
10406 struct Row9.2;
10407 struct Row9.3;
10408 struct Row8;
10409 struct Row9;
10410 struct Row10;"#},
10411 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10412 indoc! {r#"struct Row;
10413 struct Row1;
10414 struct Row2;
10415 struct Row2.1;
10416 struct Row2.2;
10417 «ˇ
10418 struct Row4;
10419 struct» Row5;
10420 «struct Row6;
10421 ˇ»
10422 struct Row9.1;
10423 struct Row9.2;
10424 struct Row9.3;
10425 struct Row8;
10426 struct Row9;
10427 struct Row10;"#},
10428 base_text,
10429 &mut cx,
10430 );
10431
10432 // When carets and selections intersect the addition hunks, those are reverted.
10433 // Adjacent carets got merged.
10434 assert_hunk_revert(
10435 indoc! {r#"struct Row;
10436 ˇ// something on the top
10437 struct Row1;
10438 struct Row2;
10439 struct Roˇw3.1;
10440 struct Row2.2;
10441 struct Row2.3;ˇ
10442
10443 struct Row4;
10444 struct ˇRow5.1;
10445 struct Row5.2;
10446 struct «Rowˇ»5.3;
10447 struct Row5;
10448 struct Row6;
10449 ˇ
10450 struct Row9.1;
10451 struct «Rowˇ»9.2;
10452 struct «ˇRow»9.3;
10453 struct Row8;
10454 struct Row9;
10455 «ˇ// something on bottom»
10456 struct Row10;"#},
10457 vec![
10458 DiffHunkStatus::Added,
10459 DiffHunkStatus::Added,
10460 DiffHunkStatus::Added,
10461 DiffHunkStatus::Added,
10462 DiffHunkStatus::Added,
10463 ],
10464 indoc! {r#"struct Row;
10465 ˇstruct Row1;
10466 struct Row2;
10467 ˇ
10468 struct Row4;
10469 ˇstruct Row5;
10470 struct Row6;
10471 ˇ
10472 ˇstruct Row8;
10473 struct Row9;
10474 ˇstruct Row10;"#},
10475 base_text,
10476 &mut cx,
10477 );
10478}
10479
10480#[gpui::test]
10481async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10482 init_test(cx, |_| {});
10483 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10484 let base_text = indoc! {r#"struct Row;
10485struct Row1;
10486struct Row2;
10487
10488struct Row4;
10489struct Row5;
10490struct Row6;
10491
10492struct Row8;
10493struct Row9;
10494struct Row10;"#};
10495
10496 // Modification hunks behave the same as the addition ones.
10497 assert_hunk_revert(
10498 indoc! {r#"struct Row;
10499 struct Row1;
10500 struct Row33;
10501 ˇ
10502 struct Row4;
10503 struct Row5;
10504 struct Row6;
10505 ˇ
10506 struct Row99;
10507 struct Row9;
10508 struct Row10;"#},
10509 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10510 indoc! {r#"struct Row;
10511 struct Row1;
10512 struct Row33;
10513 ˇ
10514 struct Row4;
10515 struct Row5;
10516 struct Row6;
10517 ˇ
10518 struct Row99;
10519 struct Row9;
10520 struct Row10;"#},
10521 base_text,
10522 &mut cx,
10523 );
10524 assert_hunk_revert(
10525 indoc! {r#"struct Row;
10526 struct Row1;
10527 struct Row33;
10528 «ˇ
10529 struct Row4;
10530 struct» Row5;
10531 «struct Row6;
10532 ˇ»
10533 struct Row99;
10534 struct Row9;
10535 struct Row10;"#},
10536 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10537 indoc! {r#"struct Row;
10538 struct Row1;
10539 struct Row33;
10540 «ˇ
10541 struct Row4;
10542 struct» Row5;
10543 «struct Row6;
10544 ˇ»
10545 struct Row99;
10546 struct Row9;
10547 struct Row10;"#},
10548 base_text,
10549 &mut cx,
10550 );
10551
10552 assert_hunk_revert(
10553 indoc! {r#"ˇstruct Row1.1;
10554 struct Row1;
10555 «ˇstr»uct Row22;
10556
10557 struct ˇRow44;
10558 struct Row5;
10559 struct «Rˇ»ow66;ˇ
10560
10561 «struˇ»ct Row88;
10562 struct Row9;
10563 struct Row1011;ˇ"#},
10564 vec![
10565 DiffHunkStatus::Modified,
10566 DiffHunkStatus::Modified,
10567 DiffHunkStatus::Modified,
10568 DiffHunkStatus::Modified,
10569 DiffHunkStatus::Modified,
10570 DiffHunkStatus::Modified,
10571 ],
10572 indoc! {r#"struct Row;
10573 ˇstruct Row1;
10574 struct Row2;
10575 ˇ
10576 struct Row4;
10577 ˇstruct Row5;
10578 struct Row6;
10579 ˇ
10580 struct Row8;
10581 ˇstruct Row9;
10582 struct Row10;ˇ"#},
10583 base_text,
10584 &mut cx,
10585 );
10586}
10587
10588#[gpui::test]
10589async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10590 init_test(cx, |_| {});
10591 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10592 let base_text = indoc! {r#"struct Row;
10593struct Row1;
10594struct Row2;
10595
10596struct Row4;
10597struct Row5;
10598struct Row6;
10599
10600struct Row8;
10601struct Row9;
10602struct Row10;"#};
10603
10604 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10605 assert_hunk_revert(
10606 indoc! {r#"struct Row;
10607 struct Row2;
10608
10609 ˇstruct Row4;
10610 struct Row5;
10611 struct Row6;
10612 ˇ
10613 struct Row8;
10614 struct Row10;"#},
10615 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10616 indoc! {r#"struct Row;
10617 struct Row2;
10618
10619 ˇstruct Row4;
10620 struct Row5;
10621 struct Row6;
10622 ˇ
10623 struct Row8;
10624 struct Row10;"#},
10625 base_text,
10626 &mut cx,
10627 );
10628 assert_hunk_revert(
10629 indoc! {r#"struct Row;
10630 struct Row2;
10631
10632 «ˇstruct Row4;
10633 struct» Row5;
10634 «struct Row6;
10635 ˇ»
10636 struct Row8;
10637 struct Row10;"#},
10638 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10639 indoc! {r#"struct Row;
10640 struct Row2;
10641
10642 «ˇstruct Row4;
10643 struct» Row5;
10644 «struct Row6;
10645 ˇ»
10646 struct Row8;
10647 struct Row10;"#},
10648 base_text,
10649 &mut cx,
10650 );
10651
10652 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10653 assert_hunk_revert(
10654 indoc! {r#"struct Row;
10655 ˇstruct Row2;
10656
10657 struct Row4;
10658 struct Row5;
10659 struct Row6;
10660
10661 struct Row8;ˇ
10662 struct Row10;"#},
10663 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10664 indoc! {r#"struct Row;
10665 struct Row1;
10666 ˇstruct Row2;
10667
10668 struct Row4;
10669 struct Row5;
10670 struct Row6;
10671
10672 struct Row8;ˇ
10673 struct Row9;
10674 struct Row10;"#},
10675 base_text,
10676 &mut cx,
10677 );
10678 assert_hunk_revert(
10679 indoc! {r#"struct Row;
10680 struct Row2«ˇ;
10681 struct Row4;
10682 struct» Row5;
10683 «struct Row6;
10684
10685 struct Row8;ˇ»
10686 struct Row10;"#},
10687 vec![
10688 DiffHunkStatus::Removed,
10689 DiffHunkStatus::Removed,
10690 DiffHunkStatus::Removed,
10691 ],
10692 indoc! {r#"struct Row;
10693 struct Row1;
10694 struct Row2«ˇ;
10695
10696 struct Row4;
10697 struct» Row5;
10698 «struct Row6;
10699
10700 struct Row8;ˇ»
10701 struct Row9;
10702 struct Row10;"#},
10703 base_text,
10704 &mut cx,
10705 );
10706}
10707
10708#[gpui::test]
10709async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10710 init_test(cx, |_| {});
10711
10712 let cols = 4;
10713 let rows = 10;
10714 let sample_text_1 = sample_text(rows, cols, 'a');
10715 assert_eq!(
10716 sample_text_1,
10717 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10718 );
10719 let sample_text_2 = sample_text(rows, cols, 'l');
10720 assert_eq!(
10721 sample_text_2,
10722 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10723 );
10724 let sample_text_3 = sample_text(rows, cols, 'v');
10725 assert_eq!(
10726 sample_text_3,
10727 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10728 );
10729
10730 fn diff_every_buffer_row(
10731 buffer: &Model<Buffer>,
10732 sample_text: String,
10733 cols: usize,
10734 cx: &mut gpui::TestAppContext,
10735 ) {
10736 // revert first character in each row, creating one large diff hunk per buffer
10737 let is_first_char = |offset: usize| offset % cols == 0;
10738 buffer.update(cx, |buffer, cx| {
10739 buffer.set_text(
10740 sample_text
10741 .chars()
10742 .enumerate()
10743 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10744 .collect::<String>(),
10745 cx,
10746 );
10747 buffer.set_diff_base(Some(sample_text), cx);
10748 });
10749 cx.executor().run_until_parked();
10750 }
10751
10752 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10753 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10754
10755 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10756 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10757
10758 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10759 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10760
10761 let multibuffer = cx.new_model(|cx| {
10762 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10763 multibuffer.push_excerpts(
10764 buffer_1.clone(),
10765 [
10766 ExcerptRange {
10767 context: Point::new(0, 0)..Point::new(3, 0),
10768 primary: None,
10769 },
10770 ExcerptRange {
10771 context: Point::new(5, 0)..Point::new(7, 0),
10772 primary: None,
10773 },
10774 ExcerptRange {
10775 context: Point::new(9, 0)..Point::new(10, 4),
10776 primary: None,
10777 },
10778 ],
10779 cx,
10780 );
10781 multibuffer.push_excerpts(
10782 buffer_2.clone(),
10783 [
10784 ExcerptRange {
10785 context: Point::new(0, 0)..Point::new(3, 0),
10786 primary: None,
10787 },
10788 ExcerptRange {
10789 context: Point::new(5, 0)..Point::new(7, 0),
10790 primary: None,
10791 },
10792 ExcerptRange {
10793 context: Point::new(9, 0)..Point::new(10, 4),
10794 primary: None,
10795 },
10796 ],
10797 cx,
10798 );
10799 multibuffer.push_excerpts(
10800 buffer_3.clone(),
10801 [
10802 ExcerptRange {
10803 context: Point::new(0, 0)..Point::new(3, 0),
10804 primary: None,
10805 },
10806 ExcerptRange {
10807 context: Point::new(5, 0)..Point::new(7, 0),
10808 primary: None,
10809 },
10810 ExcerptRange {
10811 context: Point::new(9, 0)..Point::new(10, 4),
10812 primary: None,
10813 },
10814 ],
10815 cx,
10816 );
10817 multibuffer
10818 });
10819
10820 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10821 editor.update(cx, |editor, cx| {
10822 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");
10823 editor.select_all(&SelectAll, cx);
10824 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10825 });
10826 cx.executor().run_until_parked();
10827 // When all ranges are selected, all buffer hunks are reverted.
10828 editor.update(cx, |editor, cx| {
10829 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");
10830 });
10831 buffer_1.update(cx, |buffer, _| {
10832 assert_eq!(buffer.text(), sample_text_1);
10833 });
10834 buffer_2.update(cx, |buffer, _| {
10835 assert_eq!(buffer.text(), sample_text_2);
10836 });
10837 buffer_3.update(cx, |buffer, _| {
10838 assert_eq!(buffer.text(), sample_text_3);
10839 });
10840
10841 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10842 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10843 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10844 editor.update(cx, |editor, cx| {
10845 editor.change_selections(None, cx, |s| {
10846 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10847 });
10848 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10849 });
10850 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10851 // but not affect buffer_2 and its related excerpts.
10852 editor.update(cx, |editor, cx| {
10853 assert_eq!(
10854 editor.text(cx),
10855 "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"
10856 );
10857 });
10858 buffer_1.update(cx, |buffer, _| {
10859 assert_eq!(buffer.text(), sample_text_1);
10860 });
10861 buffer_2.update(cx, |buffer, _| {
10862 assert_eq!(
10863 buffer.text(),
10864 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10865 );
10866 });
10867 buffer_3.update(cx, |buffer, _| {
10868 assert_eq!(
10869 buffer.text(),
10870 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10871 );
10872 });
10873}
10874
10875#[gpui::test]
10876async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10877 init_test(cx, |_| {});
10878
10879 let cols = 4;
10880 let rows = 10;
10881 let sample_text_1 = sample_text(rows, cols, 'a');
10882 assert_eq!(
10883 sample_text_1,
10884 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10885 );
10886 let sample_text_2 = sample_text(rows, cols, 'l');
10887 assert_eq!(
10888 sample_text_2,
10889 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10890 );
10891 let sample_text_3 = sample_text(rows, cols, 'v');
10892 assert_eq!(
10893 sample_text_3,
10894 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10895 );
10896
10897 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10898 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10899 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10900
10901 let multi_buffer = cx.new_model(|cx| {
10902 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10903 multibuffer.push_excerpts(
10904 buffer_1.clone(),
10905 [
10906 ExcerptRange {
10907 context: Point::new(0, 0)..Point::new(3, 0),
10908 primary: None,
10909 },
10910 ExcerptRange {
10911 context: Point::new(5, 0)..Point::new(7, 0),
10912 primary: None,
10913 },
10914 ExcerptRange {
10915 context: Point::new(9, 0)..Point::new(10, 4),
10916 primary: None,
10917 },
10918 ],
10919 cx,
10920 );
10921 multibuffer.push_excerpts(
10922 buffer_2.clone(),
10923 [
10924 ExcerptRange {
10925 context: Point::new(0, 0)..Point::new(3, 0),
10926 primary: None,
10927 },
10928 ExcerptRange {
10929 context: Point::new(5, 0)..Point::new(7, 0),
10930 primary: None,
10931 },
10932 ExcerptRange {
10933 context: Point::new(9, 0)..Point::new(10, 4),
10934 primary: None,
10935 },
10936 ],
10937 cx,
10938 );
10939 multibuffer.push_excerpts(
10940 buffer_3.clone(),
10941 [
10942 ExcerptRange {
10943 context: Point::new(0, 0)..Point::new(3, 0),
10944 primary: None,
10945 },
10946 ExcerptRange {
10947 context: Point::new(5, 0)..Point::new(7, 0),
10948 primary: None,
10949 },
10950 ExcerptRange {
10951 context: Point::new(9, 0)..Point::new(10, 4),
10952 primary: None,
10953 },
10954 ],
10955 cx,
10956 );
10957 multibuffer
10958 });
10959
10960 let fs = FakeFs::new(cx.executor());
10961 fs.insert_tree(
10962 "/a",
10963 json!({
10964 "main.rs": sample_text_1,
10965 "other.rs": sample_text_2,
10966 "lib.rs": sample_text_3,
10967 }),
10968 )
10969 .await;
10970 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10971 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10972 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10973 let multi_buffer_editor = cx.new_view(|cx| {
10974 Editor::new(
10975 EditorMode::Full,
10976 multi_buffer,
10977 Some(project.clone()),
10978 true,
10979 cx,
10980 )
10981 });
10982 let multibuffer_item_id = workspace
10983 .update(cx, |workspace, cx| {
10984 assert!(
10985 workspace.active_item(cx).is_none(),
10986 "active item should be None before the first item is added"
10987 );
10988 workspace.add_item_to_active_pane(
10989 Box::new(multi_buffer_editor.clone()),
10990 None,
10991 true,
10992 cx,
10993 );
10994 let active_item = workspace
10995 .active_item(cx)
10996 .expect("should have an active item after adding the multi buffer");
10997 assert!(
10998 !active_item.is_singleton(cx),
10999 "A multi buffer was expected to active after adding"
11000 );
11001 active_item.item_id()
11002 })
11003 .unwrap();
11004 cx.executor().run_until_parked();
11005
11006 multi_buffer_editor.update(cx, |editor, cx| {
11007 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11008 editor.open_excerpts(&OpenExcerpts, cx);
11009 });
11010 cx.executor().run_until_parked();
11011 let first_item_id = workspace
11012 .update(cx, |workspace, cx| {
11013 let active_item = workspace
11014 .active_item(cx)
11015 .expect("should have an active item after navigating into the 1st buffer");
11016 let first_item_id = active_item.item_id();
11017 assert_ne!(
11018 first_item_id, multibuffer_item_id,
11019 "Should navigate into the 1st buffer and activate it"
11020 );
11021 assert!(
11022 active_item.is_singleton(cx),
11023 "New active item should be a singleton buffer"
11024 );
11025 assert_eq!(
11026 active_item
11027 .act_as::<Editor>(cx)
11028 .expect("should have navigated into an editor for the 1st buffer")
11029 .read(cx)
11030 .text(cx),
11031 sample_text_1
11032 );
11033
11034 workspace
11035 .go_back(workspace.active_pane().downgrade(), cx)
11036 .detach_and_log_err(cx);
11037
11038 first_item_id
11039 })
11040 .unwrap();
11041 cx.executor().run_until_parked();
11042 workspace
11043 .update(cx, |workspace, cx| {
11044 let active_item = workspace
11045 .active_item(cx)
11046 .expect("should have an active item after navigating back");
11047 assert_eq!(
11048 active_item.item_id(),
11049 multibuffer_item_id,
11050 "Should navigate back to the multi buffer"
11051 );
11052 assert!(!active_item.is_singleton(cx));
11053 })
11054 .unwrap();
11055
11056 multi_buffer_editor.update(cx, |editor, cx| {
11057 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11058 s.select_ranges(Some(39..40))
11059 });
11060 editor.open_excerpts(&OpenExcerpts, cx);
11061 });
11062 cx.executor().run_until_parked();
11063 let second_item_id = workspace
11064 .update(cx, |workspace, cx| {
11065 let active_item = workspace
11066 .active_item(cx)
11067 .expect("should have an active item after navigating into the 2nd buffer");
11068 let second_item_id = active_item.item_id();
11069 assert_ne!(
11070 second_item_id, multibuffer_item_id,
11071 "Should navigate away from the multibuffer"
11072 );
11073 assert_ne!(
11074 second_item_id, first_item_id,
11075 "Should navigate into the 2nd buffer and activate it"
11076 );
11077 assert!(
11078 active_item.is_singleton(cx),
11079 "New active item should be a singleton buffer"
11080 );
11081 assert_eq!(
11082 active_item
11083 .act_as::<Editor>(cx)
11084 .expect("should have navigated into an editor")
11085 .read(cx)
11086 .text(cx),
11087 sample_text_2
11088 );
11089
11090 workspace
11091 .go_back(workspace.active_pane().downgrade(), cx)
11092 .detach_and_log_err(cx);
11093
11094 second_item_id
11095 })
11096 .unwrap();
11097 cx.executor().run_until_parked();
11098 workspace
11099 .update(cx, |workspace, cx| {
11100 let active_item = workspace
11101 .active_item(cx)
11102 .expect("should have an active item after navigating back from the 2nd buffer");
11103 assert_eq!(
11104 active_item.item_id(),
11105 multibuffer_item_id,
11106 "Should navigate back from the 2nd buffer to the multi buffer"
11107 );
11108 assert!(!active_item.is_singleton(cx));
11109 })
11110 .unwrap();
11111
11112 multi_buffer_editor.update(cx, |editor, cx| {
11113 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11114 s.select_ranges(Some(60..70))
11115 });
11116 editor.open_excerpts(&OpenExcerpts, cx);
11117 });
11118 cx.executor().run_until_parked();
11119 workspace
11120 .update(cx, |workspace, cx| {
11121 let active_item = workspace
11122 .active_item(cx)
11123 .expect("should have an active item after navigating into the 3rd buffer");
11124 let third_item_id = active_item.item_id();
11125 assert_ne!(
11126 third_item_id, multibuffer_item_id,
11127 "Should navigate into the 3rd buffer and activate it"
11128 );
11129 assert_ne!(third_item_id, first_item_id);
11130 assert_ne!(third_item_id, second_item_id);
11131 assert!(
11132 active_item.is_singleton(cx),
11133 "New active item should be a singleton buffer"
11134 );
11135 assert_eq!(
11136 active_item
11137 .act_as::<Editor>(cx)
11138 .expect("should have navigated into an editor")
11139 .read(cx)
11140 .text(cx),
11141 sample_text_3
11142 );
11143
11144 workspace
11145 .go_back(workspace.active_pane().downgrade(), cx)
11146 .detach_and_log_err(cx);
11147 })
11148 .unwrap();
11149 cx.executor().run_until_parked();
11150 workspace
11151 .update(cx, |workspace, cx| {
11152 let active_item = workspace
11153 .active_item(cx)
11154 .expect("should have an active item after navigating back from the 3rd buffer");
11155 assert_eq!(
11156 active_item.item_id(),
11157 multibuffer_item_id,
11158 "Should navigate back from the 3rd buffer to the multi buffer"
11159 );
11160 assert!(!active_item.is_singleton(cx));
11161 })
11162 .unwrap();
11163}
11164
11165#[gpui::test]
11166async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11167 init_test(cx, |_| {});
11168
11169 let mut cx = EditorTestContext::new(cx).await;
11170
11171 let diff_base = r#"
11172 use some::mod;
11173
11174 const A: u32 = 42;
11175
11176 fn main() {
11177 println!("hello");
11178
11179 println!("world");
11180 }
11181 "#
11182 .unindent();
11183
11184 cx.set_state(
11185 &r#"
11186 use some::modified;
11187
11188 ˇ
11189 fn main() {
11190 println!("hello there");
11191
11192 println!("around the");
11193 println!("world");
11194 }
11195 "#
11196 .unindent(),
11197 );
11198
11199 cx.set_diff_base(Some(&diff_base));
11200 executor.run_until_parked();
11201 let unexpanded_hunks = vec![
11202 (
11203 "use some::mod;\n".to_string(),
11204 DiffHunkStatus::Modified,
11205 DisplayRow(0)..DisplayRow(1),
11206 ),
11207 (
11208 "const A: u32 = 42;\n".to_string(),
11209 DiffHunkStatus::Removed,
11210 DisplayRow(2)..DisplayRow(2),
11211 ),
11212 (
11213 " println!(\"hello\");\n".to_string(),
11214 DiffHunkStatus::Modified,
11215 DisplayRow(4)..DisplayRow(5),
11216 ),
11217 (
11218 "".to_string(),
11219 DiffHunkStatus::Added,
11220 DisplayRow(6)..DisplayRow(7),
11221 ),
11222 ];
11223 cx.update_editor(|editor, cx| {
11224 let snapshot = editor.snapshot(cx);
11225 let all_hunks = editor_hunks(editor, &snapshot, cx);
11226 assert_eq!(all_hunks, unexpanded_hunks);
11227 });
11228
11229 cx.update_editor(|editor, cx| {
11230 for _ in 0..4 {
11231 editor.go_to_hunk(&GoToHunk, cx);
11232 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11233 }
11234 });
11235 executor.run_until_parked();
11236 cx.assert_editor_state(
11237 &r#"
11238 use some::modified;
11239
11240 ˇ
11241 fn main() {
11242 println!("hello there");
11243
11244 println!("around the");
11245 println!("world");
11246 }
11247 "#
11248 .unindent(),
11249 );
11250 cx.update_editor(|editor, cx| {
11251 let snapshot = editor.snapshot(cx);
11252 let all_hunks = editor_hunks(editor, &snapshot, cx);
11253 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11254 assert_eq!(
11255 expanded_hunks_background_highlights(editor, cx),
11256 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
11257 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11258 );
11259 assert_eq!(
11260 all_hunks,
11261 vec![
11262 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
11263 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
11264 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
11265 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
11266 ],
11267 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11268 (from modified and removed hunks)"
11269 );
11270 assert_eq!(
11271 all_hunks, all_expanded_hunks,
11272 "Editor hunks should not change and all be expanded"
11273 );
11274 });
11275
11276 cx.update_editor(|editor, cx| {
11277 editor.cancel(&Cancel, cx);
11278
11279 let snapshot = editor.snapshot(cx);
11280 let all_hunks = editor_hunks(editor, &snapshot, cx);
11281 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11282 assert_eq!(
11283 expanded_hunks_background_highlights(editor, cx),
11284 Vec::new(),
11285 "After cancelling in editor, no git highlights should be left"
11286 );
11287 assert_eq!(
11288 all_expanded_hunks,
11289 Vec::new(),
11290 "After cancelling in editor, no hunks should be expanded"
11291 );
11292 assert_eq!(
11293 all_hunks, unexpanded_hunks,
11294 "After cancelling in editor, regular hunks' coordinates should get back to normal"
11295 );
11296 });
11297}
11298
11299#[gpui::test]
11300async fn test_toggled_diff_base_change(
11301 executor: BackgroundExecutor,
11302 cx: &mut gpui::TestAppContext,
11303) {
11304 init_test(cx, |_| {});
11305
11306 let mut cx = EditorTestContext::new(cx).await;
11307
11308 let diff_base = r#"
11309 use some::mod1;
11310 use some::mod2;
11311
11312 const A: u32 = 42;
11313 const B: u32 = 42;
11314 const C: u32 = 42;
11315
11316 fn main(ˇ) {
11317 println!("hello");
11318
11319 println!("world");
11320 }
11321 "#
11322 .unindent();
11323
11324 cx.set_state(
11325 &r#"
11326 use some::mod2;
11327
11328 const A: u32 = 42;
11329 const C: u32 = 42;
11330
11331 fn main(ˇ) {
11332 //println!("hello");
11333
11334 println!("world");
11335 //
11336 //
11337 }
11338 "#
11339 .unindent(),
11340 );
11341
11342 cx.set_diff_base(Some(&diff_base));
11343 executor.run_until_parked();
11344 cx.update_editor(|editor, cx| {
11345 let snapshot = editor.snapshot(cx);
11346 let all_hunks = editor_hunks(editor, &snapshot, cx);
11347 assert_eq!(
11348 all_hunks,
11349 vec![
11350 (
11351 "use some::mod1;\n".to_string(),
11352 DiffHunkStatus::Removed,
11353 DisplayRow(0)..DisplayRow(0)
11354 ),
11355 (
11356 "const B: u32 = 42;\n".to_string(),
11357 DiffHunkStatus::Removed,
11358 DisplayRow(3)..DisplayRow(3)
11359 ),
11360 (
11361 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11362 DiffHunkStatus::Modified,
11363 DisplayRow(5)..DisplayRow(7)
11364 ),
11365 (
11366 "".to_string(),
11367 DiffHunkStatus::Added,
11368 DisplayRow(9)..DisplayRow(11)
11369 ),
11370 ]
11371 );
11372 });
11373
11374 cx.update_editor(|editor, cx| {
11375 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11376 });
11377 executor.run_until_parked();
11378 cx.assert_editor_state(
11379 &r#"
11380 use some::mod2;
11381
11382 const A: u32 = 42;
11383 const C: u32 = 42;
11384
11385 fn main(ˇ) {
11386 //println!("hello");
11387
11388 println!("world");
11389 //
11390 //
11391 }
11392 "#
11393 .unindent(),
11394 );
11395 cx.update_editor(|editor, cx| {
11396 let snapshot = editor.snapshot(cx);
11397 let all_hunks = editor_hunks(editor, &snapshot, cx);
11398 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11399 assert_eq!(
11400 expanded_hunks_background_highlights(editor, cx),
11401 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
11402 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11403 );
11404 assert_eq!(
11405 all_hunks,
11406 vec![
11407 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
11408 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
11409 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
11410 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
11411 ],
11412 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11413 (from modified and removed hunks)"
11414 );
11415 assert_eq!(
11416 all_hunks, all_expanded_hunks,
11417 "Editor hunks should not change and all be expanded"
11418 );
11419 });
11420
11421 cx.set_diff_base(Some("new diff base!"));
11422 executor.run_until_parked();
11423
11424 cx.update_editor(|editor, cx| {
11425 let snapshot = editor.snapshot(cx);
11426 let all_hunks = editor_hunks(editor, &snapshot, cx);
11427 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11428 assert_eq!(
11429 expanded_hunks_background_highlights(editor, cx),
11430 Vec::new(),
11431 "After diff base is changed, old git highlights should be removed"
11432 );
11433 assert_eq!(
11434 all_expanded_hunks,
11435 Vec::new(),
11436 "After diff base is changed, old git hunk expansions should be removed"
11437 );
11438 assert_eq!(
11439 all_hunks,
11440 vec![(
11441 "new diff base!".to_string(),
11442 DiffHunkStatus::Modified,
11443 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
11444 )],
11445 "After diff base is changed, hunks should update"
11446 );
11447 });
11448}
11449
11450#[gpui::test]
11451async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11452 init_test(cx, |_| {});
11453
11454 let mut cx = EditorTestContext::new(cx).await;
11455
11456 let diff_base = r#"
11457 use some::mod1;
11458 use some::mod2;
11459
11460 const A: u32 = 42;
11461 const B: u32 = 42;
11462 const C: u32 = 42;
11463
11464 fn main(ˇ) {
11465 println!("hello");
11466
11467 println!("world");
11468 }
11469
11470 fn another() {
11471 println!("another");
11472 }
11473
11474 fn another2() {
11475 println!("another2");
11476 }
11477 "#
11478 .unindent();
11479
11480 cx.set_state(
11481 &r#"
11482 «use some::mod2;
11483
11484 const A: u32 = 42;
11485 const C: u32 = 42;
11486
11487 fn main() {
11488 //println!("hello");
11489
11490 println!("world");
11491 //
11492 //ˇ»
11493 }
11494
11495 fn another() {
11496 println!("another");
11497 println!("another");
11498 }
11499
11500 println!("another2");
11501 }
11502 "#
11503 .unindent(),
11504 );
11505
11506 cx.set_diff_base(Some(&diff_base));
11507 executor.run_until_parked();
11508 cx.update_editor(|editor, cx| {
11509 let snapshot = editor.snapshot(cx);
11510 let all_hunks = editor_hunks(editor, &snapshot, cx);
11511 assert_eq!(
11512 all_hunks,
11513 vec![
11514 (
11515 "use some::mod1;\n".to_string(),
11516 DiffHunkStatus::Removed,
11517 DisplayRow(0)..DisplayRow(0)
11518 ),
11519 (
11520 "const B: u32 = 42;\n".to_string(),
11521 DiffHunkStatus::Removed,
11522 DisplayRow(3)..DisplayRow(3)
11523 ),
11524 (
11525 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11526 DiffHunkStatus::Modified,
11527 DisplayRow(5)..DisplayRow(7)
11528 ),
11529 (
11530 "".to_string(),
11531 DiffHunkStatus::Added,
11532 DisplayRow(9)..DisplayRow(11)
11533 ),
11534 (
11535 "".to_string(),
11536 DiffHunkStatus::Added,
11537 DisplayRow(15)..DisplayRow(16)
11538 ),
11539 (
11540 "fn another2() {\n".to_string(),
11541 DiffHunkStatus::Removed,
11542 DisplayRow(18)..DisplayRow(18)
11543 ),
11544 ]
11545 );
11546 });
11547
11548 cx.update_editor(|editor, cx| {
11549 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11550 });
11551 executor.run_until_parked();
11552 cx.assert_editor_state(
11553 &r#"
11554 «use some::mod2;
11555
11556 const A: u32 = 42;
11557 const C: u32 = 42;
11558
11559 fn main() {
11560 //println!("hello");
11561
11562 println!("world");
11563 //
11564 //ˇ»
11565 }
11566
11567 fn another() {
11568 println!("another");
11569 println!("another");
11570 }
11571
11572 println!("another2");
11573 }
11574 "#
11575 .unindent(),
11576 );
11577 cx.update_editor(|editor, cx| {
11578 let snapshot = editor.snapshot(cx);
11579 let all_hunks = editor_hunks(editor, &snapshot, cx);
11580 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11581 assert_eq!(
11582 expanded_hunks_background_highlights(editor, cx),
11583 vec![
11584 DisplayRow(9)..=DisplayRow(10),
11585 DisplayRow(13)..=DisplayRow(14),
11586 DisplayRow(19)..=DisplayRow(19)
11587 ]
11588 );
11589 assert_eq!(
11590 all_hunks,
11591 vec![
11592 (
11593 "use some::mod1;\n".to_string(),
11594 DiffHunkStatus::Removed,
11595 DisplayRow(1)..DisplayRow(1)
11596 ),
11597 (
11598 "const B: u32 = 42;\n".to_string(),
11599 DiffHunkStatus::Removed,
11600 DisplayRow(5)..DisplayRow(5)
11601 ),
11602 (
11603 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11604 DiffHunkStatus::Modified,
11605 DisplayRow(9)..DisplayRow(11)
11606 ),
11607 (
11608 "".to_string(),
11609 DiffHunkStatus::Added,
11610 DisplayRow(13)..DisplayRow(15)
11611 ),
11612 (
11613 "".to_string(),
11614 DiffHunkStatus::Added,
11615 DisplayRow(19)..DisplayRow(20)
11616 ),
11617 (
11618 "fn another2() {\n".to_string(),
11619 DiffHunkStatus::Removed,
11620 DisplayRow(23)..DisplayRow(23)
11621 ),
11622 ],
11623 );
11624 assert_eq!(all_hunks, all_expanded_hunks);
11625 });
11626
11627 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11628 cx.executor().run_until_parked();
11629 cx.assert_editor_state(
11630 &r#"
11631 «use some::mod2;
11632
11633 const A: u32 = 42;
11634 const C: u32 = 42;
11635
11636 fn main() {
11637 //println!("hello");
11638
11639 println!("world");
11640 //
11641 //ˇ»
11642 }
11643
11644 fn another() {
11645 println!("another");
11646 println!("another");
11647 }
11648
11649 println!("another2");
11650 }
11651 "#
11652 .unindent(),
11653 );
11654 cx.update_editor(|editor, cx| {
11655 let snapshot = editor.snapshot(cx);
11656 let all_hunks = editor_hunks(editor, &snapshot, cx);
11657 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11658 assert_eq!(
11659 expanded_hunks_background_highlights(editor, cx),
11660 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11661 "Only one hunk is left not folded, its highlight should be visible"
11662 );
11663 assert_eq!(
11664 all_hunks,
11665 vec![
11666 (
11667 "use some::mod1;\n".to_string(),
11668 DiffHunkStatus::Removed,
11669 DisplayRow(0)..DisplayRow(0)
11670 ),
11671 (
11672 "const B: u32 = 42;\n".to_string(),
11673 DiffHunkStatus::Removed,
11674 DisplayRow(0)..DisplayRow(0)
11675 ),
11676 (
11677 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11678 DiffHunkStatus::Modified,
11679 DisplayRow(0)..DisplayRow(0)
11680 ),
11681 (
11682 "".to_string(),
11683 DiffHunkStatus::Added,
11684 DisplayRow(0)..DisplayRow(1)
11685 ),
11686 (
11687 "".to_string(),
11688 DiffHunkStatus::Added,
11689 DisplayRow(5)..DisplayRow(6)
11690 ),
11691 (
11692 "fn another2() {\n".to_string(),
11693 DiffHunkStatus::Removed,
11694 DisplayRow(9)..DisplayRow(9)
11695 ),
11696 ],
11697 "Hunk list should still return shifted folded hunks"
11698 );
11699 assert_eq!(
11700 all_expanded_hunks,
11701 vec![
11702 (
11703 "".to_string(),
11704 DiffHunkStatus::Added,
11705 DisplayRow(5)..DisplayRow(6)
11706 ),
11707 (
11708 "fn another2() {\n".to_string(),
11709 DiffHunkStatus::Removed,
11710 DisplayRow(9)..DisplayRow(9)
11711 ),
11712 ],
11713 "Only non-folded hunks should be left expanded"
11714 );
11715 });
11716
11717 cx.update_editor(|editor, cx| {
11718 editor.select_all(&SelectAll, cx);
11719 editor.unfold_lines(&UnfoldLines, cx);
11720 });
11721 cx.executor().run_until_parked();
11722 cx.assert_editor_state(
11723 &r#"
11724 «use some::mod2;
11725
11726 const A: u32 = 42;
11727 const C: u32 = 42;
11728
11729 fn main() {
11730 //println!("hello");
11731
11732 println!("world");
11733 //
11734 //
11735 }
11736
11737 fn another() {
11738 println!("another");
11739 println!("another");
11740 }
11741
11742 println!("another2");
11743 }
11744 ˇ»"#
11745 .unindent(),
11746 );
11747 cx.update_editor(|editor, cx| {
11748 let snapshot = editor.snapshot(cx);
11749 let all_hunks = editor_hunks(editor, &snapshot, cx);
11750 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11751 assert_eq!(
11752 expanded_hunks_background_highlights(editor, cx),
11753 vec![
11754 DisplayRow(9)..=DisplayRow(10),
11755 DisplayRow(13)..=DisplayRow(14),
11756 DisplayRow(19)..=DisplayRow(19)
11757 ],
11758 "After unfolding, all hunk diffs should be visible again"
11759 );
11760 assert_eq!(
11761 all_hunks,
11762 vec![
11763 (
11764 "use some::mod1;\n".to_string(),
11765 DiffHunkStatus::Removed,
11766 DisplayRow(1)..DisplayRow(1)
11767 ),
11768 (
11769 "const B: u32 = 42;\n".to_string(),
11770 DiffHunkStatus::Removed,
11771 DisplayRow(5)..DisplayRow(5)
11772 ),
11773 (
11774 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11775 DiffHunkStatus::Modified,
11776 DisplayRow(9)..DisplayRow(11)
11777 ),
11778 (
11779 "".to_string(),
11780 DiffHunkStatus::Added,
11781 DisplayRow(13)..DisplayRow(15)
11782 ),
11783 (
11784 "".to_string(),
11785 DiffHunkStatus::Added,
11786 DisplayRow(19)..DisplayRow(20)
11787 ),
11788 (
11789 "fn another2() {\n".to_string(),
11790 DiffHunkStatus::Removed,
11791 DisplayRow(23)..DisplayRow(23)
11792 ),
11793 ],
11794 );
11795 assert_eq!(all_hunks, all_expanded_hunks);
11796 });
11797}
11798
11799#[gpui::test]
11800async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11801 init_test(cx, |_| {});
11802
11803 let cols = 4;
11804 let rows = 10;
11805 let sample_text_1 = sample_text(rows, cols, 'a');
11806 assert_eq!(
11807 sample_text_1,
11808 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11809 );
11810 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11811 let sample_text_2 = sample_text(rows, cols, 'l');
11812 assert_eq!(
11813 sample_text_2,
11814 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11815 );
11816 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11817 let sample_text_3 = sample_text(rows, cols, 'v');
11818 assert_eq!(
11819 sample_text_3,
11820 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11821 );
11822 let modified_sample_text_3 =
11823 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11824 let buffer_1 = cx.new_model(|cx| {
11825 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11826 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11827 buffer
11828 });
11829 let buffer_2 = cx.new_model(|cx| {
11830 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11831 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11832 buffer
11833 });
11834 let buffer_3 = cx.new_model(|cx| {
11835 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11836 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11837 buffer
11838 });
11839
11840 let multi_buffer = cx.new_model(|cx| {
11841 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11842 multibuffer.push_excerpts(
11843 buffer_1.clone(),
11844 [
11845 ExcerptRange {
11846 context: Point::new(0, 0)..Point::new(3, 0),
11847 primary: None,
11848 },
11849 ExcerptRange {
11850 context: Point::new(5, 0)..Point::new(7, 0),
11851 primary: None,
11852 },
11853 ExcerptRange {
11854 context: Point::new(9, 0)..Point::new(10, 4),
11855 primary: None,
11856 },
11857 ],
11858 cx,
11859 );
11860 multibuffer.push_excerpts(
11861 buffer_2.clone(),
11862 [
11863 ExcerptRange {
11864 context: Point::new(0, 0)..Point::new(3, 0),
11865 primary: None,
11866 },
11867 ExcerptRange {
11868 context: Point::new(5, 0)..Point::new(7, 0),
11869 primary: None,
11870 },
11871 ExcerptRange {
11872 context: Point::new(9, 0)..Point::new(10, 4),
11873 primary: None,
11874 },
11875 ],
11876 cx,
11877 );
11878 multibuffer.push_excerpts(
11879 buffer_3.clone(),
11880 [
11881 ExcerptRange {
11882 context: Point::new(0, 0)..Point::new(3, 0),
11883 primary: None,
11884 },
11885 ExcerptRange {
11886 context: Point::new(5, 0)..Point::new(7, 0),
11887 primary: None,
11888 },
11889 ExcerptRange {
11890 context: Point::new(9, 0)..Point::new(10, 4),
11891 primary: None,
11892 },
11893 ],
11894 cx,
11895 );
11896 multibuffer
11897 });
11898
11899 let fs = FakeFs::new(cx.executor());
11900 fs.insert_tree(
11901 "/a",
11902 json!({
11903 "main.rs": modified_sample_text_1,
11904 "other.rs": modified_sample_text_2,
11905 "lib.rs": modified_sample_text_3,
11906 }),
11907 )
11908 .await;
11909
11910 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11911 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11912 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11913 let multi_buffer_editor = cx.new_view(|cx| {
11914 Editor::new(
11915 EditorMode::Full,
11916 multi_buffer,
11917 Some(project.clone()),
11918 true,
11919 cx,
11920 )
11921 });
11922 cx.executor().run_until_parked();
11923
11924 let expected_all_hunks = vec![
11925 (
11926 "bbbb\n".to_string(),
11927 DiffHunkStatus::Removed,
11928 DisplayRow(4)..DisplayRow(4),
11929 ),
11930 (
11931 "nnnn\n".to_string(),
11932 DiffHunkStatus::Modified,
11933 DisplayRow(21)..DisplayRow(22),
11934 ),
11935 (
11936 "".to_string(),
11937 DiffHunkStatus::Added,
11938 DisplayRow(41)..DisplayRow(42),
11939 ),
11940 ];
11941 let expected_all_hunks_shifted = vec![
11942 (
11943 "bbbb\n".to_string(),
11944 DiffHunkStatus::Removed,
11945 DisplayRow(5)..DisplayRow(5),
11946 ),
11947 (
11948 "nnnn\n".to_string(),
11949 DiffHunkStatus::Modified,
11950 DisplayRow(23)..DisplayRow(24),
11951 ),
11952 (
11953 "".to_string(),
11954 DiffHunkStatus::Added,
11955 DisplayRow(43)..DisplayRow(44),
11956 ),
11957 ];
11958
11959 multi_buffer_editor.update(cx, |editor, cx| {
11960 let snapshot = editor.snapshot(cx);
11961 let all_hunks = editor_hunks(editor, &snapshot, cx);
11962 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11963 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11964 assert_eq!(all_hunks, expected_all_hunks);
11965 assert_eq!(all_expanded_hunks, Vec::new());
11966 });
11967
11968 multi_buffer_editor.update(cx, |editor, cx| {
11969 editor.select_all(&SelectAll, cx);
11970 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11971 });
11972 cx.executor().run_until_parked();
11973 multi_buffer_editor.update(cx, |editor, cx| {
11974 let snapshot = editor.snapshot(cx);
11975 let all_hunks = editor_hunks(editor, &snapshot, cx);
11976 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11977 assert_eq!(
11978 expanded_hunks_background_highlights(editor, cx),
11979 vec![
11980 DisplayRow(23)..=DisplayRow(23),
11981 DisplayRow(43)..=DisplayRow(43)
11982 ],
11983 );
11984 assert_eq!(all_hunks, expected_all_hunks_shifted);
11985 assert_eq!(all_hunks, all_expanded_hunks);
11986 });
11987
11988 multi_buffer_editor.update(cx, |editor, cx| {
11989 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11990 });
11991 cx.executor().run_until_parked();
11992 multi_buffer_editor.update(cx, |editor, cx| {
11993 let snapshot = editor.snapshot(cx);
11994 let all_hunks = editor_hunks(editor, &snapshot, cx);
11995 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11996 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11997 assert_eq!(all_hunks, expected_all_hunks);
11998 assert_eq!(all_expanded_hunks, Vec::new());
11999 });
12000
12001 multi_buffer_editor.update(cx, |editor, cx| {
12002 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12003 });
12004 cx.executor().run_until_parked();
12005 multi_buffer_editor.update(cx, |editor, cx| {
12006 let snapshot = editor.snapshot(cx);
12007 let all_hunks = editor_hunks(editor, &snapshot, cx);
12008 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12009 assert_eq!(
12010 expanded_hunks_background_highlights(editor, cx),
12011 vec![
12012 DisplayRow(23)..=DisplayRow(23),
12013 DisplayRow(43)..=DisplayRow(43)
12014 ],
12015 );
12016 assert_eq!(all_hunks, expected_all_hunks_shifted);
12017 assert_eq!(all_hunks, all_expanded_hunks);
12018 });
12019
12020 multi_buffer_editor.update(cx, |editor, cx| {
12021 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12022 });
12023 cx.executor().run_until_parked();
12024 multi_buffer_editor.update(cx, |editor, cx| {
12025 let snapshot = editor.snapshot(cx);
12026 let all_hunks = editor_hunks(editor, &snapshot, cx);
12027 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12028 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12029 assert_eq!(all_hunks, expected_all_hunks);
12030 assert_eq!(all_expanded_hunks, Vec::new());
12031 });
12032}
12033
12034#[gpui::test]
12035async fn test_edits_around_toggled_additions(
12036 executor: BackgroundExecutor,
12037 cx: &mut gpui::TestAppContext,
12038) {
12039 init_test(cx, |_| {});
12040
12041 let mut cx = EditorTestContext::new(cx).await;
12042
12043 let diff_base = r#"
12044 use some::mod1;
12045 use some::mod2;
12046
12047 const A: u32 = 42;
12048
12049 fn main() {
12050 println!("hello");
12051
12052 println!("world");
12053 }
12054 "#
12055 .unindent();
12056 executor.run_until_parked();
12057 cx.set_state(
12058 &r#"
12059 use some::mod1;
12060 use some::mod2;
12061
12062 const A: u32 = 42;
12063 const B: u32 = 42;
12064 const C: u32 = 42;
12065 ˇ
12066
12067 fn main() {
12068 println!("hello");
12069
12070 println!("world");
12071 }
12072 "#
12073 .unindent(),
12074 );
12075
12076 cx.set_diff_base(Some(&diff_base));
12077 executor.run_until_parked();
12078 cx.update_editor(|editor, cx| {
12079 let snapshot = editor.snapshot(cx);
12080 let all_hunks = editor_hunks(editor, &snapshot, cx);
12081 assert_eq!(
12082 all_hunks,
12083 vec![(
12084 "".to_string(),
12085 DiffHunkStatus::Added,
12086 DisplayRow(4)..DisplayRow(7)
12087 )]
12088 );
12089 });
12090 cx.update_editor(|editor, cx| {
12091 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12092 });
12093 executor.run_until_parked();
12094 cx.assert_editor_state(
12095 &r#"
12096 use some::mod1;
12097 use some::mod2;
12098
12099 const A: u32 = 42;
12100 const B: u32 = 42;
12101 const C: u32 = 42;
12102 ˇ
12103
12104 fn main() {
12105 println!("hello");
12106
12107 println!("world");
12108 }
12109 "#
12110 .unindent(),
12111 );
12112 cx.update_editor(|editor, cx| {
12113 let snapshot = editor.snapshot(cx);
12114 let all_hunks = editor_hunks(editor, &snapshot, cx);
12115 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12116 assert_eq!(
12117 all_hunks,
12118 vec![(
12119 "".to_string(),
12120 DiffHunkStatus::Added,
12121 DisplayRow(4)..DisplayRow(7)
12122 )]
12123 );
12124 assert_eq!(
12125 expanded_hunks_background_highlights(editor, cx),
12126 vec![DisplayRow(4)..=DisplayRow(6)]
12127 );
12128 assert_eq!(all_hunks, all_expanded_hunks);
12129 });
12130
12131 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12132 executor.run_until_parked();
12133 cx.assert_editor_state(
12134 &r#"
12135 use some::mod1;
12136 use some::mod2;
12137
12138 const A: u32 = 42;
12139 const B: u32 = 42;
12140 const C: u32 = 42;
12141 const D: u32 = 42;
12142 ˇ
12143
12144 fn main() {
12145 println!("hello");
12146
12147 println!("world");
12148 }
12149 "#
12150 .unindent(),
12151 );
12152 cx.update_editor(|editor, cx| {
12153 let snapshot = editor.snapshot(cx);
12154 let all_hunks = editor_hunks(editor, &snapshot, cx);
12155 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12156 assert_eq!(
12157 all_hunks,
12158 vec![(
12159 "".to_string(),
12160 DiffHunkStatus::Added,
12161 DisplayRow(4)..DisplayRow(8)
12162 )]
12163 );
12164 assert_eq!(
12165 expanded_hunks_background_highlights(editor, cx),
12166 vec![DisplayRow(4)..=DisplayRow(6)],
12167 "Edited hunk should have one more line added"
12168 );
12169 assert_eq!(
12170 all_hunks, all_expanded_hunks,
12171 "Expanded hunk should also grow with the addition"
12172 );
12173 });
12174
12175 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12176 executor.run_until_parked();
12177 cx.assert_editor_state(
12178 &r#"
12179 use some::mod1;
12180 use some::mod2;
12181
12182 const A: u32 = 42;
12183 const B: u32 = 42;
12184 const C: u32 = 42;
12185 const D: u32 = 42;
12186 const E: u32 = 42;
12187 ˇ
12188
12189 fn main() {
12190 println!("hello");
12191
12192 println!("world");
12193 }
12194 "#
12195 .unindent(),
12196 );
12197 cx.update_editor(|editor, cx| {
12198 let snapshot = editor.snapshot(cx);
12199 let all_hunks = editor_hunks(editor, &snapshot, cx);
12200 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12201 assert_eq!(
12202 all_hunks,
12203 vec![(
12204 "".to_string(),
12205 DiffHunkStatus::Added,
12206 DisplayRow(4)..DisplayRow(9)
12207 )]
12208 );
12209 assert_eq!(
12210 expanded_hunks_background_highlights(editor, cx),
12211 vec![DisplayRow(4)..=DisplayRow(6)],
12212 "Edited hunk should have one more line added"
12213 );
12214 assert_eq!(all_hunks, all_expanded_hunks);
12215 });
12216
12217 cx.update_editor(|editor, cx| {
12218 editor.move_up(&MoveUp, cx);
12219 editor.delete_line(&DeleteLine, cx);
12220 });
12221 executor.run_until_parked();
12222 cx.assert_editor_state(
12223 &r#"
12224 use some::mod1;
12225 use some::mod2;
12226
12227 const A: u32 = 42;
12228 const B: u32 = 42;
12229 const C: u32 = 42;
12230 const D: u32 = 42;
12231 ˇ
12232
12233 fn main() {
12234 println!("hello");
12235
12236 println!("world");
12237 }
12238 "#
12239 .unindent(),
12240 );
12241 cx.update_editor(|editor, cx| {
12242 let snapshot = editor.snapshot(cx);
12243 let all_hunks = editor_hunks(editor, &snapshot, cx);
12244 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12245 assert_eq!(
12246 all_hunks,
12247 vec![(
12248 "".to_string(),
12249 DiffHunkStatus::Added,
12250 DisplayRow(4)..DisplayRow(8)
12251 )]
12252 );
12253 assert_eq!(
12254 expanded_hunks_background_highlights(editor, cx),
12255 vec![DisplayRow(4)..=DisplayRow(6)],
12256 "Deleting a line should shrint the hunk"
12257 );
12258 assert_eq!(
12259 all_hunks, all_expanded_hunks,
12260 "Expanded hunk should also shrink with the addition"
12261 );
12262 });
12263
12264 cx.update_editor(|editor, 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 editor.move_up(&MoveUp, cx);
12270 editor.delete_line(&DeleteLine, cx);
12271 });
12272 executor.run_until_parked();
12273 cx.assert_editor_state(
12274 &r#"
12275 use some::mod1;
12276 use some::mod2;
12277
12278 const A: u32 = 42;
12279 ˇ
12280
12281 fn main() {
12282 println!("hello");
12283
12284 println!("world");
12285 }
12286 "#
12287 .unindent(),
12288 );
12289 cx.update_editor(|editor, cx| {
12290 let snapshot = editor.snapshot(cx);
12291 let all_hunks = editor_hunks(editor, &snapshot, cx);
12292 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12293 assert_eq!(
12294 all_hunks,
12295 vec![(
12296 "".to_string(),
12297 DiffHunkStatus::Added,
12298 DisplayRow(5)..DisplayRow(6)
12299 )]
12300 );
12301 assert_eq!(
12302 expanded_hunks_background_highlights(editor, cx),
12303 vec![DisplayRow(5)..=DisplayRow(5)]
12304 );
12305 assert_eq!(all_hunks, all_expanded_hunks);
12306 });
12307
12308 cx.update_editor(|editor, cx| {
12309 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12310 editor.delete_line(&DeleteLine, cx);
12311 });
12312 executor.run_until_parked();
12313 cx.assert_editor_state(
12314 &r#"
12315 ˇ
12316
12317 fn main() {
12318 println!("hello");
12319
12320 println!("world");
12321 }
12322 "#
12323 .unindent(),
12324 );
12325 cx.update_editor(|editor, cx| {
12326 let snapshot = editor.snapshot(cx);
12327 let all_hunks = editor_hunks(editor, &snapshot, cx);
12328 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12329 assert_eq!(
12330 all_hunks,
12331 vec![
12332 (
12333 "use some::mod1;\nuse some::mod2;\n".to_string(),
12334 DiffHunkStatus::Removed,
12335 DisplayRow(0)..DisplayRow(0)
12336 ),
12337 (
12338 "const A: u32 = 42;\n".to_string(),
12339 DiffHunkStatus::Removed,
12340 DisplayRow(2)..DisplayRow(2)
12341 )
12342 ]
12343 );
12344 assert_eq!(
12345 expanded_hunks_background_highlights(editor, cx),
12346 Vec::new(),
12347 "Should close all stale expanded addition hunks"
12348 );
12349 assert_eq!(
12350 all_expanded_hunks,
12351 vec![(
12352 "const A: u32 = 42;\n".to_string(),
12353 DiffHunkStatus::Removed,
12354 DisplayRow(2)..DisplayRow(2)
12355 )],
12356 "Should open hunks that were adjacent to the stale addition one"
12357 );
12358 });
12359}
12360
12361#[gpui::test]
12362async fn test_edits_around_toggled_deletions(
12363 executor: BackgroundExecutor,
12364 cx: &mut gpui::TestAppContext,
12365) {
12366 init_test(cx, |_| {});
12367
12368 let mut cx = EditorTestContext::new(cx).await;
12369
12370 let diff_base = r#"
12371 use some::mod1;
12372 use some::mod2;
12373
12374 const A: u32 = 42;
12375 const B: u32 = 42;
12376 const C: u32 = 42;
12377
12378
12379 fn main() {
12380 println!("hello");
12381
12382 println!("world");
12383 }
12384 "#
12385 .unindent();
12386 executor.run_until_parked();
12387 cx.set_state(
12388 &r#"
12389 use some::mod1;
12390 use some::mod2;
12391
12392 ˇconst B: u32 = 42;
12393 const C: u32 = 42;
12394
12395
12396 fn main() {
12397 println!("hello");
12398
12399 println!("world");
12400 }
12401 "#
12402 .unindent(),
12403 );
12404
12405 cx.set_diff_base(Some(&diff_base));
12406 executor.run_until_parked();
12407 cx.update_editor(|editor, cx| {
12408 let snapshot = editor.snapshot(cx);
12409 let all_hunks = editor_hunks(editor, &snapshot, cx);
12410 assert_eq!(
12411 all_hunks,
12412 vec![(
12413 "const A: u32 = 42;\n".to_string(),
12414 DiffHunkStatus::Removed,
12415 DisplayRow(3)..DisplayRow(3)
12416 )]
12417 );
12418 });
12419 cx.update_editor(|editor, cx| {
12420 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12421 });
12422 executor.run_until_parked();
12423 cx.assert_editor_state(
12424 &r#"
12425 use some::mod1;
12426 use some::mod2;
12427
12428 ˇconst B: u32 = 42;
12429 const C: u32 = 42;
12430
12431
12432 fn main() {
12433 println!("hello");
12434
12435 println!("world");
12436 }
12437 "#
12438 .unindent(),
12439 );
12440 cx.update_editor(|editor, cx| {
12441 let snapshot = editor.snapshot(cx);
12442 let all_hunks = editor_hunks(editor, &snapshot, cx);
12443 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12444 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12445 assert_eq!(
12446 all_hunks,
12447 vec![(
12448 "const A: u32 = 42;\n".to_string(),
12449 DiffHunkStatus::Removed,
12450 DisplayRow(4)..DisplayRow(4)
12451 )]
12452 );
12453 assert_eq!(all_hunks, all_expanded_hunks);
12454 });
12455
12456 cx.update_editor(|editor, cx| {
12457 editor.delete_line(&DeleteLine, cx);
12458 });
12459 executor.run_until_parked();
12460 cx.assert_editor_state(
12461 &r#"
12462 use some::mod1;
12463 use some::mod2;
12464
12465 ˇconst C: u32 = 42;
12466
12467
12468 fn main() {
12469 println!("hello");
12470
12471 println!("world");
12472 }
12473 "#
12474 .unindent(),
12475 );
12476 cx.update_editor(|editor, cx| {
12477 let snapshot = editor.snapshot(cx);
12478 let all_hunks = editor_hunks(editor, &snapshot, cx);
12479 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12480 assert_eq!(
12481 expanded_hunks_background_highlights(editor, cx),
12482 Vec::new(),
12483 "Deleted hunks do not highlight current editor's background"
12484 );
12485 assert_eq!(
12486 all_hunks,
12487 vec![(
12488 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12489 DiffHunkStatus::Removed,
12490 DisplayRow(5)..DisplayRow(5)
12491 )]
12492 );
12493 assert_eq!(all_hunks, all_expanded_hunks);
12494 });
12495
12496 cx.update_editor(|editor, cx| {
12497 editor.delete_line(&DeleteLine, cx);
12498 });
12499 executor.run_until_parked();
12500 cx.assert_editor_state(
12501 &r#"
12502 use some::mod1;
12503 use some::mod2;
12504
12505 ˇ
12506
12507 fn main() {
12508 println!("hello");
12509
12510 println!("world");
12511 }
12512 "#
12513 .unindent(),
12514 );
12515 cx.update_editor(|editor, cx| {
12516 let snapshot = editor.snapshot(cx);
12517 let all_hunks = editor_hunks(editor, &snapshot, cx);
12518 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12519 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12520 assert_eq!(
12521 all_hunks,
12522 vec![(
12523 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12524 DiffHunkStatus::Removed,
12525 DisplayRow(6)..DisplayRow(6)
12526 )]
12527 );
12528 assert_eq!(all_hunks, all_expanded_hunks);
12529 });
12530
12531 cx.update_editor(|editor, cx| {
12532 editor.handle_input("replacement", cx);
12533 });
12534 executor.run_until_parked();
12535 cx.assert_editor_state(
12536 &r#"
12537 use some::mod1;
12538 use some::mod2;
12539
12540 replacementˇ
12541
12542 fn main() {
12543 println!("hello");
12544
12545 println!("world");
12546 }
12547 "#
12548 .unindent(),
12549 );
12550 cx.update_editor(|editor, cx| {
12551 let snapshot = editor.snapshot(cx);
12552 let all_hunks = editor_hunks(editor, &snapshot, cx);
12553 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12554 assert_eq!(
12555 all_hunks,
12556 vec![(
12557 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12558 DiffHunkStatus::Modified,
12559 DisplayRow(7)..DisplayRow(8)
12560 )]
12561 );
12562 assert_eq!(
12563 expanded_hunks_background_highlights(editor, cx),
12564 vec![DisplayRow(7)..=DisplayRow(7)],
12565 "Modified expanded hunks should display additions and highlight their background"
12566 );
12567 assert_eq!(all_hunks, all_expanded_hunks);
12568 });
12569}
12570
12571#[gpui::test]
12572async fn test_edits_around_toggled_modifications(
12573 executor: BackgroundExecutor,
12574 cx: &mut gpui::TestAppContext,
12575) {
12576 init_test(cx, |_| {});
12577
12578 let mut cx = EditorTestContext::new(cx).await;
12579
12580 let diff_base = r#"
12581 use some::mod1;
12582 use some::mod2;
12583
12584 const A: u32 = 42;
12585 const B: u32 = 42;
12586 const C: u32 = 42;
12587 const D: u32 = 42;
12588
12589
12590 fn main() {
12591 println!("hello");
12592
12593 println!("world");
12594 }"#
12595 .unindent();
12596 executor.run_until_parked();
12597 cx.set_state(
12598 &r#"
12599 use some::mod1;
12600 use some::mod2;
12601
12602 const A: u32 = 42;
12603 const B: u32 = 42;
12604 const C: u32 = 43ˇ
12605 const D: u32 = 42;
12606
12607
12608 fn main() {
12609 println!("hello");
12610
12611 println!("world");
12612 }"#
12613 .unindent(),
12614 );
12615
12616 cx.set_diff_base(Some(&diff_base));
12617 executor.run_until_parked();
12618 cx.update_editor(|editor, cx| {
12619 let snapshot = editor.snapshot(cx);
12620 let all_hunks = editor_hunks(editor, &snapshot, cx);
12621 assert_eq!(
12622 all_hunks,
12623 vec![(
12624 "const C: u32 = 42;\n".to_string(),
12625 DiffHunkStatus::Modified,
12626 DisplayRow(5)..DisplayRow(6)
12627 )]
12628 );
12629 });
12630 cx.update_editor(|editor, cx| {
12631 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12632 });
12633 executor.run_until_parked();
12634 cx.assert_editor_state(
12635 &r#"
12636 use some::mod1;
12637 use some::mod2;
12638
12639 const A: u32 = 42;
12640 const B: u32 = 42;
12641 const C: u32 = 43ˇ
12642 const D: u32 = 42;
12643
12644
12645 fn main() {
12646 println!("hello");
12647
12648 println!("world");
12649 }"#
12650 .unindent(),
12651 );
12652 cx.update_editor(|editor, cx| {
12653 let snapshot = editor.snapshot(cx);
12654 let all_hunks = editor_hunks(editor, &snapshot, cx);
12655 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12656 assert_eq!(
12657 expanded_hunks_background_highlights(editor, cx),
12658 vec![DisplayRow(6)..=DisplayRow(6)],
12659 );
12660 assert_eq!(
12661 all_hunks,
12662 vec![(
12663 "const C: u32 = 42;\n".to_string(),
12664 DiffHunkStatus::Modified,
12665 DisplayRow(6)..DisplayRow(7)
12666 )]
12667 );
12668 assert_eq!(all_hunks, all_expanded_hunks);
12669 });
12670
12671 cx.update_editor(|editor, cx| {
12672 editor.handle_input("\nnew_line\n", cx);
12673 });
12674 executor.run_until_parked();
12675 cx.assert_editor_state(
12676 &r#"
12677 use some::mod1;
12678 use some::mod2;
12679
12680 const A: u32 = 42;
12681 const B: u32 = 42;
12682 const C: u32 = 43
12683 new_line
12684 ˇ
12685 const D: u32 = 42;
12686
12687
12688 fn main() {
12689 println!("hello");
12690
12691 println!("world");
12692 }"#
12693 .unindent(),
12694 );
12695 cx.update_editor(|editor, cx| {
12696 let snapshot = editor.snapshot(cx);
12697 let all_hunks = editor_hunks(editor, &snapshot, cx);
12698 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12699 assert_eq!(
12700 expanded_hunks_background_highlights(editor, cx),
12701 vec![DisplayRow(6)..=DisplayRow(6)],
12702 "Modified hunk should grow highlighted lines on more text additions"
12703 );
12704 assert_eq!(
12705 all_hunks,
12706 vec![(
12707 "const C: u32 = 42;\n".to_string(),
12708 DiffHunkStatus::Modified,
12709 DisplayRow(6)..DisplayRow(9)
12710 )]
12711 );
12712 assert_eq!(all_hunks, all_expanded_hunks);
12713 });
12714
12715 cx.update_editor(|editor, cx| {
12716 editor.move_up(&MoveUp, cx);
12717 editor.move_up(&MoveUp, cx);
12718 editor.move_up(&MoveUp, cx);
12719 editor.delete_line(&DeleteLine, cx);
12720 });
12721 executor.run_until_parked();
12722 cx.assert_editor_state(
12723 &r#"
12724 use some::mod1;
12725 use some::mod2;
12726
12727 const A: u32 = 42;
12728 ˇconst C: u32 = 43
12729 new_line
12730
12731 const D: u32 = 42;
12732
12733
12734 fn main() {
12735 println!("hello");
12736
12737 println!("world");
12738 }"#
12739 .unindent(),
12740 );
12741 cx.update_editor(|editor, cx| {
12742 let snapshot = editor.snapshot(cx);
12743 let all_hunks = editor_hunks(editor, &snapshot, cx);
12744 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12745 assert_eq!(
12746 expanded_hunks_background_highlights(editor, cx),
12747 vec![DisplayRow(6)..=DisplayRow(8)],
12748 );
12749 assert_eq!(
12750 all_hunks,
12751 vec![(
12752 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12753 DiffHunkStatus::Modified,
12754 DisplayRow(6)..DisplayRow(9)
12755 )],
12756 "Modified hunk should grow deleted lines on text deletions above"
12757 );
12758 assert_eq!(all_hunks, all_expanded_hunks);
12759 });
12760
12761 cx.update_editor(|editor, cx| {
12762 editor.move_up(&MoveUp, cx);
12763 editor.handle_input("v", cx);
12764 });
12765 executor.run_until_parked();
12766 cx.assert_editor_state(
12767 &r#"
12768 use some::mod1;
12769 use some::mod2;
12770
12771 vˇconst A: u32 = 42;
12772 const C: u32 = 43
12773 new_line
12774
12775 const D: u32 = 42;
12776
12777
12778 fn main() {
12779 println!("hello");
12780
12781 println!("world");
12782 }"#
12783 .unindent(),
12784 );
12785 cx.update_editor(|editor, cx| {
12786 let snapshot = editor.snapshot(cx);
12787 let all_hunks = editor_hunks(editor, &snapshot, cx);
12788 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12789 assert_eq!(
12790 expanded_hunks_background_highlights(editor, cx),
12791 vec![DisplayRow(6)..=DisplayRow(9)],
12792 "Modified hunk should grow deleted lines on text modifications above"
12793 );
12794 assert_eq!(
12795 all_hunks,
12796 vec![(
12797 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12798 DiffHunkStatus::Modified,
12799 DisplayRow(6)..DisplayRow(10)
12800 )]
12801 );
12802 assert_eq!(all_hunks, all_expanded_hunks);
12803 });
12804
12805 cx.update_editor(|editor, cx| {
12806 editor.move_down(&MoveDown, cx);
12807 editor.move_down(&MoveDown, cx);
12808 editor.delete_line(&DeleteLine, cx)
12809 });
12810 executor.run_until_parked();
12811 cx.assert_editor_state(
12812 &r#"
12813 use some::mod1;
12814 use some::mod2;
12815
12816 vconst A: u32 = 42;
12817 const C: u32 = 43
12818 ˇ
12819 const D: u32 = 42;
12820
12821
12822 fn main() {
12823 println!("hello");
12824
12825 println!("world");
12826 }"#
12827 .unindent(),
12828 );
12829 cx.update_editor(|editor, cx| {
12830 let snapshot = editor.snapshot(cx);
12831 let all_hunks = editor_hunks(editor, &snapshot, cx);
12832 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12833 assert_eq!(
12834 expanded_hunks_background_highlights(editor, cx),
12835 vec![DisplayRow(6)..=DisplayRow(8)],
12836 "Modified hunk should grow shrink lines on modification lines removal"
12837 );
12838 assert_eq!(
12839 all_hunks,
12840 vec![(
12841 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12842 DiffHunkStatus::Modified,
12843 DisplayRow(6)..DisplayRow(9)
12844 )]
12845 );
12846 assert_eq!(all_hunks, all_expanded_hunks);
12847 });
12848
12849 cx.update_editor(|editor, cx| {
12850 editor.move_up(&MoveUp, cx);
12851 editor.move_up(&MoveUp, cx);
12852 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12853 editor.delete_line(&DeleteLine, cx)
12854 });
12855 executor.run_until_parked();
12856 cx.assert_editor_state(
12857 &r#"
12858 use some::mod1;
12859 use some::mod2;
12860
12861 ˇ
12862
12863 fn main() {
12864 println!("hello");
12865
12866 println!("world");
12867 }"#
12868 .unindent(),
12869 );
12870 cx.update_editor(|editor, cx| {
12871 let snapshot = editor.snapshot(cx);
12872 let all_hunks = editor_hunks(editor, &snapshot, cx);
12873 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12874 assert_eq!(
12875 expanded_hunks_background_highlights(editor, cx),
12876 Vec::new(),
12877 "Modified hunk should turn into a removed one on all modified lines removal"
12878 );
12879 assert_eq!(
12880 all_hunks,
12881 vec![(
12882 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12883 .to_string(),
12884 DiffHunkStatus::Removed,
12885 DisplayRow(7)..DisplayRow(7)
12886 )]
12887 );
12888 assert_eq!(all_hunks, all_expanded_hunks);
12889 });
12890}
12891
12892#[gpui::test]
12893async fn test_multiple_expanded_hunks_merge(
12894 executor: BackgroundExecutor,
12895 cx: &mut gpui::TestAppContext,
12896) {
12897 init_test(cx, |_| {});
12898
12899 let mut cx = EditorTestContext::new(cx).await;
12900
12901 let diff_base = r#"
12902 use some::mod1;
12903 use some::mod2;
12904
12905 const A: u32 = 42;
12906 const B: u32 = 42;
12907 const C: u32 = 42;
12908 const D: u32 = 42;
12909
12910
12911 fn main() {
12912 println!("hello");
12913
12914 println!("world");
12915 }"#
12916 .unindent();
12917 executor.run_until_parked();
12918 cx.set_state(
12919 &r#"
12920 use some::mod1;
12921 use some::mod2;
12922
12923 const A: u32 = 42;
12924 const B: u32 = 42;
12925 const C: u32 = 43ˇ
12926 const D: u32 = 42;
12927
12928
12929 fn main() {
12930 println!("hello");
12931
12932 println!("world");
12933 }"#
12934 .unindent(),
12935 );
12936
12937 cx.set_diff_base(Some(&diff_base));
12938 executor.run_until_parked();
12939 cx.update_editor(|editor, cx| {
12940 let snapshot = editor.snapshot(cx);
12941 let all_hunks = editor_hunks(editor, &snapshot, cx);
12942 assert_eq!(
12943 all_hunks,
12944 vec![(
12945 "const C: u32 = 42;\n".to_string(),
12946 DiffHunkStatus::Modified,
12947 DisplayRow(5)..DisplayRow(6)
12948 )]
12949 );
12950 });
12951 cx.update_editor(|editor, cx| {
12952 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12953 });
12954 executor.run_until_parked();
12955 cx.assert_editor_state(
12956 &r#"
12957 use some::mod1;
12958 use some::mod2;
12959
12960 const A: u32 = 42;
12961 const B: u32 = 42;
12962 const C: u32 = 43ˇ
12963 const D: u32 = 42;
12964
12965
12966 fn main() {
12967 println!("hello");
12968
12969 println!("world");
12970 }"#
12971 .unindent(),
12972 );
12973 cx.update_editor(|editor, cx| {
12974 let snapshot = editor.snapshot(cx);
12975 let all_hunks = editor_hunks(editor, &snapshot, cx);
12976 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12977 assert_eq!(
12978 expanded_hunks_background_highlights(editor, cx),
12979 vec![DisplayRow(6)..=DisplayRow(6)],
12980 );
12981 assert_eq!(
12982 all_hunks,
12983 vec![(
12984 "const C: u32 = 42;\n".to_string(),
12985 DiffHunkStatus::Modified,
12986 DisplayRow(6)..DisplayRow(7)
12987 )]
12988 );
12989 assert_eq!(all_hunks, all_expanded_hunks);
12990 });
12991
12992 cx.update_editor(|editor, cx| {
12993 editor.handle_input("\nnew_line\n", cx);
12994 });
12995 executor.run_until_parked();
12996 cx.assert_editor_state(
12997 &r#"
12998 use some::mod1;
12999 use some::mod2;
13000
13001 const A: u32 = 42;
13002 const B: u32 = 42;
13003 const C: u32 = 43
13004 new_line
13005 ˇ
13006 const D: u32 = 42;
13007
13008
13009 fn main() {
13010 println!("hello");
13011
13012 println!("world");
13013 }"#
13014 .unindent(),
13015 );
13016}
13017
13018async fn setup_indent_guides_editor(
13019 text: &str,
13020 cx: &mut gpui::TestAppContext,
13021) -> (BufferId, EditorTestContext) {
13022 init_test(cx, |_| {});
13023
13024 let mut cx = EditorTestContext::new(cx).await;
13025
13026 let buffer_id = cx.update_editor(|editor, cx| {
13027 editor.set_text(text, cx);
13028 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13029
13030 buffer_ids[0]
13031 });
13032
13033 (buffer_id, cx)
13034}
13035
13036fn assert_indent_guides(
13037 range: Range<u32>,
13038 expected: Vec<IndentGuide>,
13039 active_indices: Option<Vec<usize>>,
13040 cx: &mut EditorTestContext,
13041) {
13042 let indent_guides = cx.update_editor(|editor, cx| {
13043 let snapshot = editor.snapshot(cx).display_snapshot;
13044 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13045 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13046 true,
13047 &snapshot,
13048 cx,
13049 );
13050
13051 indent_guides.sort_by(|a, b| {
13052 a.depth.cmp(&b.depth).then(
13053 a.start_row
13054 .cmp(&b.start_row)
13055 .then(a.end_row.cmp(&b.end_row)),
13056 )
13057 });
13058 indent_guides
13059 });
13060
13061 if let Some(expected) = active_indices {
13062 let active_indices = cx.update_editor(|editor, cx| {
13063 let snapshot = editor.snapshot(cx).display_snapshot;
13064 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13065 });
13066
13067 assert_eq!(
13068 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13069 expected,
13070 "Active indent guide indices do not match"
13071 );
13072 }
13073
13074 let expected: Vec<_> = expected
13075 .into_iter()
13076 .map(|guide| MultiBufferIndentGuide {
13077 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13078 buffer: guide,
13079 })
13080 .collect();
13081
13082 assert_eq!(indent_guides, expected, "Indent guides do not match");
13083}
13084
13085fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13086 IndentGuide {
13087 buffer_id,
13088 start_row,
13089 end_row,
13090 depth,
13091 tab_size: 4,
13092 settings: IndentGuideSettings {
13093 enabled: true,
13094 line_width: 1,
13095 active_line_width: 1,
13096 ..Default::default()
13097 },
13098 }
13099}
13100
13101#[gpui::test]
13102async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13103 let (buffer_id, mut cx) = setup_indent_guides_editor(
13104 &"
13105 fn main() {
13106 let a = 1;
13107 }"
13108 .unindent(),
13109 cx,
13110 )
13111 .await;
13112
13113 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13114}
13115
13116#[gpui::test]
13117async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13118 let (buffer_id, mut cx) = setup_indent_guides_editor(
13119 &"
13120 fn main() {
13121 let a = 1;
13122 let b = 2;
13123 }"
13124 .unindent(),
13125 cx,
13126 )
13127 .await;
13128
13129 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13130}
13131
13132#[gpui::test]
13133async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13134 let (buffer_id, mut cx) = setup_indent_guides_editor(
13135 &"
13136 fn main() {
13137 let a = 1;
13138 if a == 3 {
13139 let b = 2;
13140 } else {
13141 let c = 3;
13142 }
13143 }"
13144 .unindent(),
13145 cx,
13146 )
13147 .await;
13148
13149 assert_indent_guides(
13150 0..8,
13151 vec![
13152 indent_guide(buffer_id, 1, 6, 0),
13153 indent_guide(buffer_id, 3, 3, 1),
13154 indent_guide(buffer_id, 5, 5, 1),
13155 ],
13156 None,
13157 &mut cx,
13158 );
13159}
13160
13161#[gpui::test]
13162async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13163 let (buffer_id, mut cx) = setup_indent_guides_editor(
13164 &"
13165 fn main() {
13166 let a = 1;
13167 let b = 2;
13168 let c = 3;
13169 }"
13170 .unindent(),
13171 cx,
13172 )
13173 .await;
13174
13175 assert_indent_guides(
13176 0..5,
13177 vec![
13178 indent_guide(buffer_id, 1, 3, 0),
13179 indent_guide(buffer_id, 2, 2, 1),
13180 ],
13181 None,
13182 &mut cx,
13183 );
13184}
13185
13186#[gpui::test]
13187async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13188 let (buffer_id, mut cx) = setup_indent_guides_editor(
13189 &"
13190 fn main() {
13191 let a = 1;
13192
13193 let c = 3;
13194 }"
13195 .unindent(),
13196 cx,
13197 )
13198 .await;
13199
13200 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13201}
13202
13203#[gpui::test]
13204async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13205 let (buffer_id, mut cx) = setup_indent_guides_editor(
13206 &"
13207 fn main() {
13208 let a = 1;
13209
13210 let c = 3;
13211
13212 if a == 3 {
13213 let b = 2;
13214 } else {
13215 let c = 3;
13216 }
13217 }"
13218 .unindent(),
13219 cx,
13220 )
13221 .await;
13222
13223 assert_indent_guides(
13224 0..11,
13225 vec![
13226 indent_guide(buffer_id, 1, 9, 0),
13227 indent_guide(buffer_id, 6, 6, 1),
13228 indent_guide(buffer_id, 8, 8, 1),
13229 ],
13230 None,
13231 &mut cx,
13232 );
13233}
13234
13235#[gpui::test]
13236async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13237 let (buffer_id, mut cx) = setup_indent_guides_editor(
13238 &"
13239 fn main() {
13240 let a = 1;
13241
13242 let c = 3;
13243
13244 if a == 3 {
13245 let b = 2;
13246 } else {
13247 let c = 3;
13248 }
13249 }"
13250 .unindent(),
13251 cx,
13252 )
13253 .await;
13254
13255 assert_indent_guides(
13256 1..11,
13257 vec![
13258 indent_guide(buffer_id, 1, 9, 0),
13259 indent_guide(buffer_id, 6, 6, 1),
13260 indent_guide(buffer_id, 8, 8, 1),
13261 ],
13262 None,
13263 &mut cx,
13264 );
13265}
13266
13267#[gpui::test]
13268async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13269 let (buffer_id, mut cx) = setup_indent_guides_editor(
13270 &"
13271 fn main() {
13272 let a = 1;
13273
13274 let c = 3;
13275
13276 if a == 3 {
13277 let b = 2;
13278 } else {
13279 let c = 3;
13280 }
13281 }"
13282 .unindent(),
13283 cx,
13284 )
13285 .await;
13286
13287 assert_indent_guides(
13288 1..10,
13289 vec![
13290 indent_guide(buffer_id, 1, 9, 0),
13291 indent_guide(buffer_id, 6, 6, 1),
13292 indent_guide(buffer_id, 8, 8, 1),
13293 ],
13294 None,
13295 &mut cx,
13296 );
13297}
13298
13299#[gpui::test]
13300async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13301 let (buffer_id, mut cx) = setup_indent_guides_editor(
13302 &"
13303 block1
13304 block2
13305 block3
13306 block4
13307 block2
13308 block1
13309 block1"
13310 .unindent(),
13311 cx,
13312 )
13313 .await;
13314
13315 assert_indent_guides(
13316 1..10,
13317 vec![
13318 indent_guide(buffer_id, 1, 4, 0),
13319 indent_guide(buffer_id, 2, 3, 1),
13320 indent_guide(buffer_id, 3, 3, 2),
13321 ],
13322 None,
13323 &mut cx,
13324 );
13325}
13326
13327#[gpui::test]
13328async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13329 let (buffer_id, mut cx) = setup_indent_guides_editor(
13330 &"
13331 block1
13332 block2
13333 block3
13334
13335 block1
13336 block1"
13337 .unindent(),
13338 cx,
13339 )
13340 .await;
13341
13342 assert_indent_guides(
13343 0..6,
13344 vec![
13345 indent_guide(buffer_id, 1, 2, 0),
13346 indent_guide(buffer_id, 2, 2, 1),
13347 ],
13348 None,
13349 &mut cx,
13350 );
13351}
13352
13353#[gpui::test]
13354async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13355 let (buffer_id, mut cx) = setup_indent_guides_editor(
13356 &"
13357 block1
13358
13359
13360
13361 block2
13362 "
13363 .unindent(),
13364 cx,
13365 )
13366 .await;
13367
13368 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13369}
13370
13371#[gpui::test]
13372async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13373 let (buffer_id, mut cx) = setup_indent_guides_editor(
13374 &"
13375 def a:
13376 \tb = 3
13377 \tif True:
13378 \t\tc = 4
13379 \t\td = 5
13380 \tprint(b)
13381 "
13382 .unindent(),
13383 cx,
13384 )
13385 .await;
13386
13387 assert_indent_guides(
13388 0..6,
13389 vec![
13390 indent_guide(buffer_id, 1, 6, 0),
13391 indent_guide(buffer_id, 3, 4, 1),
13392 ],
13393 None,
13394 &mut cx,
13395 );
13396}
13397
13398#[gpui::test]
13399async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13400 let (buffer_id, mut cx) = setup_indent_guides_editor(
13401 &"
13402 fn main() {
13403 let a = 1;
13404 }"
13405 .unindent(),
13406 cx,
13407 )
13408 .await;
13409
13410 cx.update_editor(|editor, cx| {
13411 editor.change_selections(None, cx, |s| {
13412 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13413 });
13414 });
13415
13416 assert_indent_guides(
13417 0..3,
13418 vec![indent_guide(buffer_id, 1, 1, 0)],
13419 Some(vec![0]),
13420 &mut cx,
13421 );
13422}
13423
13424#[gpui::test]
13425async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13426 let (buffer_id, mut cx) = setup_indent_guides_editor(
13427 &"
13428 fn main() {
13429 if 1 == 2 {
13430 let a = 1;
13431 }
13432 }"
13433 .unindent(),
13434 cx,
13435 )
13436 .await;
13437
13438 cx.update_editor(|editor, cx| {
13439 editor.change_selections(None, cx, |s| {
13440 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13441 });
13442 });
13443
13444 assert_indent_guides(
13445 0..4,
13446 vec![
13447 indent_guide(buffer_id, 1, 3, 0),
13448 indent_guide(buffer_id, 2, 2, 1),
13449 ],
13450 Some(vec![1]),
13451 &mut cx,
13452 );
13453
13454 cx.update_editor(|editor, cx| {
13455 editor.change_selections(None, cx, |s| {
13456 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13457 });
13458 });
13459
13460 assert_indent_guides(
13461 0..4,
13462 vec![
13463 indent_guide(buffer_id, 1, 3, 0),
13464 indent_guide(buffer_id, 2, 2, 1),
13465 ],
13466 Some(vec![1]),
13467 &mut cx,
13468 );
13469
13470 cx.update_editor(|editor, cx| {
13471 editor.change_selections(None, cx, |s| {
13472 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13473 });
13474 });
13475
13476 assert_indent_guides(
13477 0..4,
13478 vec![
13479 indent_guide(buffer_id, 1, 3, 0),
13480 indent_guide(buffer_id, 2, 2, 1),
13481 ],
13482 Some(vec![0]),
13483 &mut cx,
13484 );
13485}
13486
13487#[gpui::test]
13488async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13489 let (buffer_id, mut cx) = setup_indent_guides_editor(
13490 &"
13491 fn main() {
13492 let a = 1;
13493
13494 let b = 2;
13495 }"
13496 .unindent(),
13497 cx,
13498 )
13499 .await;
13500
13501 cx.update_editor(|editor, cx| {
13502 editor.change_selections(None, cx, |s| {
13503 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13504 });
13505 });
13506
13507 assert_indent_guides(
13508 0..5,
13509 vec![indent_guide(buffer_id, 1, 3, 0)],
13510 Some(vec![0]),
13511 &mut cx,
13512 );
13513}
13514
13515#[gpui::test]
13516async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13517 let (buffer_id, mut cx) = setup_indent_guides_editor(
13518 &"
13519 def m:
13520 a = 1
13521 pass"
13522 .unindent(),
13523 cx,
13524 )
13525 .await;
13526
13527 cx.update_editor(|editor, cx| {
13528 editor.change_selections(None, cx, |s| {
13529 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13530 });
13531 });
13532
13533 assert_indent_guides(
13534 0..3,
13535 vec![indent_guide(buffer_id, 1, 2, 0)],
13536 Some(vec![0]),
13537 &mut cx,
13538 );
13539}
13540
13541#[gpui::test]
13542fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13543 init_test(cx, |_| {});
13544
13545 let editor = cx.add_window(|cx| {
13546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13547 build_editor(buffer, cx)
13548 });
13549
13550 let render_args = Arc::new(Mutex::new(None));
13551 let snapshot = editor
13552 .update(cx, |editor, cx| {
13553 let snapshot = editor.buffer().read(cx).snapshot(cx);
13554 let range =
13555 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13556
13557 struct RenderArgs {
13558 row: MultiBufferRow,
13559 folded: bool,
13560 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13561 }
13562
13563 let crease = Crease::new(
13564 range,
13565 FoldPlaceholder::test(),
13566 {
13567 let toggle_callback = render_args.clone();
13568 move |row, folded, callback, _cx| {
13569 *toggle_callback.lock() = Some(RenderArgs {
13570 row,
13571 folded,
13572 callback,
13573 });
13574 div()
13575 }
13576 },
13577 |_row, _folded, _cx| div(),
13578 );
13579
13580 editor.insert_creases(Some(crease), cx);
13581 let snapshot = editor.snapshot(cx);
13582 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13583 snapshot
13584 })
13585 .unwrap();
13586
13587 let render_args = render_args.lock().take().unwrap();
13588 assert_eq!(render_args.row, MultiBufferRow(1));
13589 assert!(!render_args.folded);
13590 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13591
13592 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13593 .unwrap();
13594 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13595 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13596
13597 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13598 .unwrap();
13599 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13600 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13601}
13602
13603#[gpui::test]
13604async fn test_input_text(cx: &mut gpui::TestAppContext) {
13605 init_test(cx, |_| {});
13606 let mut cx = EditorTestContext::new(cx).await;
13607
13608 cx.set_state(
13609 &r#"ˇone
13610 two
13611
13612 three
13613 fourˇ
13614 five
13615
13616 siˇx"#
13617 .unindent(),
13618 );
13619
13620 cx.dispatch_action(HandleInput(String::new()));
13621 cx.assert_editor_state(
13622 &r#"ˇone
13623 two
13624
13625 three
13626 fourˇ
13627 five
13628
13629 siˇx"#
13630 .unindent(),
13631 );
13632
13633 cx.dispatch_action(HandleInput("AAAA".to_string()));
13634 cx.assert_editor_state(
13635 &r#"AAAAˇone
13636 two
13637
13638 three
13639 fourAAAAˇ
13640 five
13641
13642 siAAAAˇx"#
13643 .unindent(),
13644 );
13645}
13646
13647#[gpui::test]
13648async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13649 init_test(cx, |_| {});
13650
13651 let mut cx = EditorTestContext::new(cx).await;
13652 cx.set_state(
13653 r#"let foo = 1;
13654let foo = 2;
13655let foo = 3;
13656let fooˇ = 4;
13657let foo = 5;
13658let foo = 6;
13659let foo = 7;
13660let foo = 8;
13661let foo = 9;
13662let foo = 10;
13663let foo = 11;
13664let foo = 12;
13665let foo = 13;
13666let foo = 14;
13667let foo = 15;"#,
13668 );
13669
13670 cx.update_editor(|e, cx| {
13671 assert_eq!(
13672 e.next_scroll_position,
13673 NextScrollCursorCenterTopBottom::Center,
13674 "Default next scroll direction is center",
13675 );
13676
13677 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13678 assert_eq!(
13679 e.next_scroll_position,
13680 NextScrollCursorCenterTopBottom::Top,
13681 "After center, next scroll direction should be top",
13682 );
13683
13684 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13685 assert_eq!(
13686 e.next_scroll_position,
13687 NextScrollCursorCenterTopBottom::Bottom,
13688 "After top, next scroll direction should be bottom",
13689 );
13690
13691 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13692 assert_eq!(
13693 e.next_scroll_position,
13694 NextScrollCursorCenterTopBottom::Center,
13695 "After bottom, scrolling should start over",
13696 );
13697
13698 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13699 assert_eq!(
13700 e.next_scroll_position,
13701 NextScrollCursorCenterTopBottom::Top,
13702 "Scrolling continues if retriggered fast enough"
13703 );
13704 });
13705
13706 cx.executor()
13707 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13708 cx.executor().run_until_parked();
13709 cx.update_editor(|e, _| {
13710 assert_eq!(
13711 e.next_scroll_position,
13712 NextScrollCursorCenterTopBottom::Center,
13713 "If scrolling is not triggered fast enough, it should reset"
13714 );
13715 });
13716}
13717
13718#[gpui::test]
13719async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13720 init_test(cx, |_| {});
13721 let mut cx = EditorLspTestContext::new_rust(
13722 lsp::ServerCapabilities {
13723 definition_provider: Some(lsp::OneOf::Left(true)),
13724 references_provider: Some(lsp::OneOf::Left(true)),
13725 ..lsp::ServerCapabilities::default()
13726 },
13727 cx,
13728 )
13729 .await;
13730
13731 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13732 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13733 move |params, _| async move {
13734 if empty_go_to_definition {
13735 Ok(None)
13736 } else {
13737 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13738 uri: params.text_document_position_params.text_document.uri,
13739 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13740 })))
13741 }
13742 },
13743 );
13744 let references =
13745 cx.lsp
13746 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13747 Ok(Some(vec![lsp::Location {
13748 uri: params.text_document_position.text_document.uri,
13749 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13750 }]))
13751 });
13752 (go_to_definition, references)
13753 };
13754
13755 cx.set_state(
13756 &r#"fn one() {
13757 let mut a = ˇtwo();
13758 }
13759
13760 fn two() {}"#
13761 .unindent(),
13762 );
13763 set_up_lsp_handlers(false, &mut cx);
13764 let navigated = cx
13765 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13766 .await
13767 .expect("Failed to navigate to definition");
13768 assert_eq!(
13769 navigated,
13770 Navigated::Yes,
13771 "Should have navigated to definition from the GetDefinition response"
13772 );
13773 cx.assert_editor_state(
13774 &r#"fn one() {
13775 let mut a = two();
13776 }
13777
13778 fn «twoˇ»() {}"#
13779 .unindent(),
13780 );
13781
13782 let editors = cx.update_workspace(|workspace, cx| {
13783 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13784 });
13785 cx.update_editor(|_, test_editor_cx| {
13786 assert_eq!(
13787 editors.len(),
13788 1,
13789 "Initially, only one, test, editor should be open in the workspace"
13790 );
13791 assert_eq!(
13792 test_editor_cx.view(),
13793 editors.last().expect("Asserted len is 1")
13794 );
13795 });
13796
13797 set_up_lsp_handlers(true, &mut cx);
13798 let navigated = cx
13799 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13800 .await
13801 .expect("Failed to navigate to lookup references");
13802 assert_eq!(
13803 navigated,
13804 Navigated::Yes,
13805 "Should have navigated to references as a fallback after empty GoToDefinition response"
13806 );
13807 // We should not change the selections in the existing file,
13808 // if opening another milti buffer with the references
13809 cx.assert_editor_state(
13810 &r#"fn one() {
13811 let mut a = two();
13812 }
13813
13814 fn «twoˇ»() {}"#
13815 .unindent(),
13816 );
13817 let editors = cx.update_workspace(|workspace, cx| {
13818 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13819 });
13820 cx.update_editor(|_, test_editor_cx| {
13821 assert_eq!(
13822 editors.len(),
13823 2,
13824 "After falling back to references search, we open a new editor with the results"
13825 );
13826 let references_fallback_text = editors
13827 .into_iter()
13828 .find(|new_editor| new_editor != test_editor_cx.view())
13829 .expect("Should have one non-test editor now")
13830 .read(test_editor_cx)
13831 .text(test_editor_cx);
13832 assert_eq!(
13833 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13834 "Should use the range from the references response and not the GoToDefinition one"
13835 );
13836 });
13837}
13838
13839fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13840 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13841 point..point
13842}
13843
13844fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13845 let (text, ranges) = marked_text_ranges(marked_text, true);
13846 assert_eq!(view.text(cx), text);
13847 assert_eq!(
13848 view.selections.ranges(cx),
13849 ranges,
13850 "Assert selections are {}",
13851 marked_text
13852 );
13853}
13854
13855pub fn handle_signature_help_request(
13856 cx: &mut EditorLspTestContext,
13857 mocked_response: lsp::SignatureHelp,
13858) -> impl Future<Output = ()> {
13859 let mut request =
13860 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13861 let mocked_response = mocked_response.clone();
13862 async move { Ok(Some(mocked_response)) }
13863 });
13864
13865 async move {
13866 request.next().await;
13867 }
13868}
13869
13870/// Handle completion request passing a marked string specifying where the completion
13871/// should be triggered from using '|' character, what range should be replaced, and what completions
13872/// should be returned using '<' and '>' to delimit the range
13873pub fn handle_completion_request(
13874 cx: &mut EditorLspTestContext,
13875 marked_string: &str,
13876 completions: Vec<&'static str>,
13877 counter: Arc<AtomicUsize>,
13878) -> impl Future<Output = ()> {
13879 let complete_from_marker: TextRangeMarker = '|'.into();
13880 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13881 let (_, mut marked_ranges) = marked_text_ranges_by(
13882 marked_string,
13883 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13884 );
13885
13886 let complete_from_position =
13887 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13888 let replace_range =
13889 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13890
13891 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13892 let completions = completions.clone();
13893 counter.fetch_add(1, atomic::Ordering::Release);
13894 async move {
13895 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13896 assert_eq!(
13897 params.text_document_position.position,
13898 complete_from_position
13899 );
13900 Ok(Some(lsp::CompletionResponse::Array(
13901 completions
13902 .iter()
13903 .map(|completion_text| lsp::CompletionItem {
13904 label: completion_text.to_string(),
13905 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13906 range: replace_range,
13907 new_text: completion_text.to_string(),
13908 })),
13909 ..Default::default()
13910 })
13911 .collect(),
13912 )))
13913 }
13914 });
13915
13916 async move {
13917 request.next().await;
13918 }
13919}
13920
13921fn handle_resolve_completion_request(
13922 cx: &mut EditorLspTestContext,
13923 edits: Option<Vec<(&'static str, &'static str)>>,
13924) -> impl Future<Output = ()> {
13925 let edits = edits.map(|edits| {
13926 edits
13927 .iter()
13928 .map(|(marked_string, new_text)| {
13929 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13930 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13931 lsp::TextEdit::new(replace_range, new_text.to_string())
13932 })
13933 .collect::<Vec<_>>()
13934 });
13935
13936 let mut request =
13937 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13938 let edits = edits.clone();
13939 async move {
13940 Ok(lsp::CompletionItem {
13941 additional_text_edits: edits,
13942 ..Default::default()
13943 })
13944 }
13945 });
13946
13947 async move {
13948 request.next().await;
13949 }
13950}
13951
13952pub(crate) fn update_test_language_settings(
13953 cx: &mut TestAppContext,
13954 f: impl Fn(&mut AllLanguageSettingsContent),
13955) {
13956 cx.update(|cx| {
13957 SettingsStore::update_global(cx, |store, cx| {
13958 store.update_user_settings::<AllLanguageSettings>(cx, f);
13959 });
13960 });
13961}
13962
13963pub(crate) fn update_test_project_settings(
13964 cx: &mut TestAppContext,
13965 f: impl Fn(&mut ProjectSettings),
13966) {
13967 cx.update(|cx| {
13968 SettingsStore::update_global(cx, |store, cx| {
13969 store.update_user_settings::<ProjectSettings>(cx, f);
13970 });
13971 });
13972}
13973
13974pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13975 cx.update(|cx| {
13976 assets::Assets.load_test_fonts(cx);
13977 let store = SettingsStore::test(cx);
13978 cx.set_global(store);
13979 theme::init(theme::LoadThemes::JustBase, cx);
13980 release_channel::init(SemanticVersion::default(), cx);
13981 client::init_settings(cx);
13982 language::init(cx);
13983 Project::init_settings(cx);
13984 workspace::init_settings(cx);
13985 crate::init(cx);
13986 });
13987
13988 update_test_language_settings(cx, f);
13989}
13990
13991pub(crate) fn rust_lang() -> Arc<Language> {
13992 Arc::new(Language::new(
13993 LanguageConfig {
13994 name: "Rust".into(),
13995 matcher: LanguageMatcher {
13996 path_suffixes: vec!["rs".to_string()],
13997 ..Default::default()
13998 },
13999 ..Default::default()
14000 },
14001 Some(tree_sitter_rust::LANGUAGE.into()),
14002 ))
14003}
14004
14005#[track_caller]
14006fn assert_hunk_revert(
14007 not_reverted_text_with_selections: &str,
14008 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14009 expected_reverted_text_with_selections: &str,
14010 base_text: &str,
14011 cx: &mut EditorLspTestContext,
14012) {
14013 cx.set_state(not_reverted_text_with_selections);
14014 cx.update_editor(|editor, cx| {
14015 editor
14016 .buffer()
14017 .read(cx)
14018 .as_singleton()
14019 .unwrap()
14020 .update(cx, |buffer, cx| {
14021 buffer.set_diff_base(Some(base_text.into()), cx);
14022 });
14023 });
14024 cx.executor().run_until_parked();
14025
14026 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14027 let snapshot = editor.buffer().read(cx).snapshot(cx);
14028 let reverted_hunk_statuses = snapshot
14029 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
14030 .map(|hunk| hunk_status(&hunk))
14031 .collect::<Vec<_>>();
14032
14033 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14034 reverted_hunk_statuses
14035 });
14036 cx.executor().run_until_parked();
14037 cx.assert_editor_state(expected_reverted_text_with_selections);
14038 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14039}