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, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, 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, Override,
24 Point,
25};
26use language_settings::IndentGuideSettings;
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::project_settings::{LspSettings, ProjectSettings};
30use project::FakeFs;
31use serde_json::{self, json};
32use std::sync::atomic;
33use std::sync::atomic::AtomicUsize;
34use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
35use unindent::Unindent;
36use util::{
37 assert_set_eq,
38 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
39};
40use workspace::{
41 item::{FollowEvent, FollowableItem, Item, ItemHandle},
42 NavigationEntry, ViewId,
43};
44
45#[gpui::test]
46fn test_edit_events(cx: &mut TestAppContext) {
47 init_test(cx, |_| {});
48
49 let buffer = cx.new_model(|cx| {
50 let mut buffer = language::Buffer::local("123456", cx);
51 buffer.set_group_interval(Duration::from_secs(1));
52 buffer
53 });
54
55 let events = Rc::new(RefCell::new(Vec::new()));
56 let editor1 = cx.add_window({
57 let events = events.clone();
58 |cx| {
59 let view = cx.view().clone();
60 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
61 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
62 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
63 _ => {}
64 })
65 .detach();
66 Editor::for_buffer(buffer.clone(), None, cx)
67 }
68 });
69
70 let editor2 = cx.add_window({
71 let events = events.clone();
72 |cx| {
73 cx.subscribe(
74 &cx.view().clone(),
75 move |_, _, event: &EditorEvent, _| match event {
76 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
77 EditorEvent::BufferEdited => {
78 events.borrow_mut().push(("editor2", "buffer edited"))
79 }
80 _ => {}
81 },
82 )
83 .detach();
84 Editor::for_buffer(buffer.clone(), None, cx)
85 }
86 });
87
88 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
89
90 // Mutating editor 1 will emit an `Edited` event only for that editor.
91 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
92 assert_eq!(
93 mem::take(&mut *events.borrow_mut()),
94 [
95 ("editor1", "edited"),
96 ("editor1", "buffer edited"),
97 ("editor2", "buffer edited"),
98 ]
99 );
100
101 // Mutating editor 2 will emit an `Edited` event only for that editor.
102 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
103 assert_eq!(
104 mem::take(&mut *events.borrow_mut()),
105 [
106 ("editor2", "edited"),
107 ("editor1", "buffer edited"),
108 ("editor2", "buffer edited"),
109 ]
110 );
111
112 // Undoing on editor 1 will emit an `Edited` event only for that editor.
113 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
114 assert_eq!(
115 mem::take(&mut *events.borrow_mut()),
116 [
117 ("editor1", "edited"),
118 ("editor1", "buffer edited"),
119 ("editor2", "buffer edited"),
120 ]
121 );
122
123 // Redoing on editor 1 will emit an `Edited` event only for that editor.
124 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
125 assert_eq!(
126 mem::take(&mut *events.borrow_mut()),
127 [
128 ("editor1", "edited"),
129 ("editor1", "buffer edited"),
130 ("editor2", "buffer edited"),
131 ]
132 );
133
134 // Undoing on editor 2 will emit an `Edited` event only for that editor.
135 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
136 assert_eq!(
137 mem::take(&mut *events.borrow_mut()),
138 [
139 ("editor2", "edited"),
140 ("editor1", "buffer edited"),
141 ("editor2", "buffer edited"),
142 ]
143 );
144
145 // Redoing on editor 2 will emit an `Edited` event only for that editor.
146 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
147 assert_eq!(
148 mem::take(&mut *events.borrow_mut()),
149 [
150 ("editor2", "edited"),
151 ("editor1", "buffer edited"),
152 ("editor2", "buffer edited"),
153 ]
154 );
155
156 // No event is emitted when the mutation is a no-op.
157 _ = editor2.update(cx, |editor, cx| {
158 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
159
160 editor.backspace(&Backspace, cx);
161 });
162 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
163}
164
165#[gpui::test]
166fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
167 init_test(cx, |_| {});
168
169 let mut now = Instant::now();
170 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
171 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
172 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
173 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
174
175 _ = editor.update(cx, |editor, cx| {
176 editor.start_transaction_at(now, cx);
177 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
178
179 editor.insert("cd", cx);
180 editor.end_transaction_at(now, cx);
181 assert_eq!(editor.text(cx), "12cd56");
182 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
183
184 editor.start_transaction_at(now, cx);
185 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
186 editor.insert("e", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cde6");
189 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
190
191 now += group_interval + Duration::from_millis(1);
192 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
193
194 // Simulate an edit in another editor
195 _ = buffer.update(cx, |buffer, cx| {
196 buffer.start_transaction_at(now, cx);
197 buffer.edit([(0..1, "a")], None, cx);
198 buffer.edit([(1..1, "b")], None, cx);
199 buffer.end_transaction_at(now, cx);
200 });
201
202 assert_eq!(editor.text(cx), "ab2cde6");
203 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
204
205 // Last transaction happened past the group interval in a different editor.
206 // Undo it individually and don't restore selections.
207 editor.undo(&Undo, cx);
208 assert_eq!(editor.text(cx), "12cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
210
211 // First two transactions happened within the group interval in this editor.
212 // Undo them together and restore selections.
213 editor.undo(&Undo, cx);
214 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
215 assert_eq!(editor.text(cx), "123456");
216 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
217
218 // Redo the first two transactions together.
219 editor.redo(&Redo, cx);
220 assert_eq!(editor.text(cx), "12cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
222
223 // Redo the last transaction on its own.
224 editor.redo(&Redo, cx);
225 assert_eq!(editor.text(cx), "ab2cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
227
228 // Test empty transactions.
229 editor.start_transaction_at(now, cx);
230 editor.end_transaction_at(now, cx);
231 editor.undo(&Undo, cx);
232 assert_eq!(editor.text(cx), "12cde6");
233 });
234}
235
236#[gpui::test]
237fn test_ime_composition(cx: &mut TestAppContext) {
238 init_test(cx, |_| {});
239
240 let buffer = cx.new_model(|cx| {
241 let mut buffer = language::Buffer::local("abcde", cx);
242 // Ensure automatic grouping doesn't occur.
243 buffer.set_group_interval(Duration::ZERO);
244 buffer
245 });
246
247 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
248 cx.add_window(|cx| {
249 let mut editor = build_editor(buffer.clone(), cx);
250
251 // Start a new IME composition.
252 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
253 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
254 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
255 assert_eq!(editor.text(cx), "äbcde");
256 assert_eq!(
257 editor.marked_text_ranges(cx),
258 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
259 );
260
261 // Finalize IME composition.
262 editor.replace_text_in_range(None, "ā", cx);
263 assert_eq!(editor.text(cx), "ābcde");
264 assert_eq!(editor.marked_text_ranges(cx), None);
265
266 // IME composition edits are grouped and are undone/redone at once.
267 editor.undo(&Default::default(), cx);
268 assert_eq!(editor.text(cx), "abcde");
269 assert_eq!(editor.marked_text_ranges(cx), None);
270 editor.redo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "ābcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273
274 // Start a new IME composition.
275 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
276 assert_eq!(
277 editor.marked_text_ranges(cx),
278 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
279 );
280
281 // Undoing during an IME composition cancels it.
282 editor.undo(&Default::default(), cx);
283 assert_eq!(editor.text(cx), "ābcde");
284 assert_eq!(editor.marked_text_ranges(cx), None);
285
286 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
287 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
288 assert_eq!(editor.text(cx), "ābcdè");
289 assert_eq!(
290 editor.marked_text_ranges(cx),
291 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
292 );
293
294 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
295 editor.replace_text_in_range(Some(4..999), "ę", cx);
296 assert_eq!(editor.text(cx), "ābcdę");
297 assert_eq!(editor.marked_text_ranges(cx), None);
298
299 // Start a new IME composition with multiple cursors.
300 editor.change_selections(None, cx, |s| {
301 s.select_ranges([
302 OffsetUtf16(1)..OffsetUtf16(1),
303 OffsetUtf16(3)..OffsetUtf16(3),
304 OffsetUtf16(5)..OffsetUtf16(5),
305 ])
306 });
307 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
308 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
309 assert_eq!(
310 editor.marked_text_ranges(cx),
311 Some(vec![
312 OffsetUtf16(0)..OffsetUtf16(3),
313 OffsetUtf16(4)..OffsetUtf16(7),
314 OffsetUtf16(8)..OffsetUtf16(11)
315 ])
316 );
317
318 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
319 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
320 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
321 assert_eq!(
322 editor.marked_text_ranges(cx),
323 Some(vec![
324 OffsetUtf16(1)..OffsetUtf16(2),
325 OffsetUtf16(5)..OffsetUtf16(6),
326 OffsetUtf16(9)..OffsetUtf16(10)
327 ])
328 );
329
330 // Finalize IME composition with multiple cursors.
331 editor.replace_text_in_range(Some(9..10), "2", cx);
332 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
333 assert_eq!(editor.marked_text_ranges(cx), None);
334
335 editor
336 });
337}
338
339#[gpui::test]
340fn test_selection_with_mouse(cx: &mut TestAppContext) {
341 init_test(cx, |_| {});
342
343 let editor = cx.add_window(|cx| {
344 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
345 build_editor(buffer, cx)
346 });
347
348 _ = editor.update(cx, |view, cx| {
349 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
350 });
351 assert_eq!(
352 editor
353 .update(cx, |view, cx| view.selections.display_ranges(cx))
354 .unwrap(),
355 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
356 );
357
358 _ = editor.update(cx, |view, cx| {
359 view.update_selection(
360 DisplayPoint::new(DisplayRow(3), 3),
361 0,
362 gpui::Point::<f32>::default(),
363 cx,
364 );
365 });
366
367 assert_eq!(
368 editor
369 .update(cx, |view, cx| view.selections.display_ranges(cx))
370 .unwrap(),
371 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
372 );
373
374 _ = editor.update(cx, |view, cx| {
375 view.update_selection(
376 DisplayPoint::new(DisplayRow(1), 1),
377 0,
378 gpui::Point::<f32>::default(),
379 cx,
380 );
381 });
382
383 assert_eq!(
384 editor
385 .update(cx, |view, cx| view.selections.display_ranges(cx))
386 .unwrap(),
387 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
388 );
389
390 _ = editor.update(cx, |view, cx| {
391 view.end_selection(cx);
392 view.update_selection(
393 DisplayPoint::new(DisplayRow(3), 3),
394 0,
395 gpui::Point::<f32>::default(),
396 cx,
397 );
398 });
399
400 assert_eq!(
401 editor
402 .update(cx, |view, cx| view.selections.display_ranges(cx))
403 .unwrap(),
404 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
405 );
406
407 _ = editor.update(cx, |view, cx| {
408 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
409 view.update_selection(
410 DisplayPoint::new(DisplayRow(0), 0),
411 0,
412 gpui::Point::<f32>::default(),
413 cx,
414 );
415 });
416
417 assert_eq!(
418 editor
419 .update(cx, |view, cx| view.selections.display_ranges(cx))
420 .unwrap(),
421 [
422 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
423 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
424 ]
425 );
426
427 _ = editor.update(cx, |view, cx| {
428 view.end_selection(cx);
429 });
430
431 assert_eq!(
432 editor
433 .update(cx, |view, cx| view.selections.display_ranges(cx))
434 .unwrap(),
435 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
436 );
437}
438
439#[gpui::test]
440fn test_canceling_pending_selection(cx: &mut TestAppContext) {
441 init_test(cx, |_| {});
442
443 let view = cx.add_window(|cx| {
444 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
445 build_editor(buffer, cx)
446 });
447
448 _ = view.update(cx, |view, cx| {
449 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
450 assert_eq!(
451 view.selections.display_ranges(cx),
452 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
453 );
454 });
455
456 _ = view.update(cx, |view, cx| {
457 view.update_selection(
458 DisplayPoint::new(DisplayRow(3), 3),
459 0,
460 gpui::Point::<f32>::default(),
461 cx,
462 );
463 assert_eq!(
464 view.selections.display_ranges(cx),
465 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
466 );
467 });
468
469 _ = view.update(cx, |view, cx| {
470 view.cancel(&Cancel, cx);
471 view.update_selection(
472 DisplayPoint::new(DisplayRow(1), 1),
473 0,
474 gpui::Point::<f32>::default(),
475 cx,
476 );
477 assert_eq!(
478 view.selections.display_ranges(cx),
479 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
480 );
481 });
482}
483
484#[gpui::test]
485fn test_clone(cx: &mut TestAppContext) {
486 init_test(cx, |_| {});
487
488 let (text, selection_ranges) = marked_text_ranges(
489 indoc! {"
490 one
491 two
492 threeˇ
493 four
494 fiveˇ
495 "},
496 true,
497 );
498
499 let editor = cx.add_window(|cx| {
500 let buffer = MultiBuffer::build_simple(&text, cx);
501 build_editor(buffer, cx)
502 });
503
504 _ = editor.update(cx, |editor, cx| {
505 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
506 editor.fold_ranges(
507 [
508 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
509 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
510 ],
511 true,
512 cx,
513 );
514 });
515
516 let cloned_editor = editor
517 .update(cx, |editor, cx| {
518 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
519 })
520 .unwrap()
521 .unwrap();
522
523 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
524 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
525
526 assert_eq!(
527 cloned_editor
528 .update(cx, |e, cx| e.display_text(cx))
529 .unwrap(),
530 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
531 );
532 assert_eq!(
533 cloned_snapshot
534 .folds_in_range(0..text.len())
535 .collect::<Vec<_>>(),
536 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
537 );
538 assert_set_eq!(
539 cloned_editor
540 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
541 .unwrap(),
542 editor
543 .update(cx, |editor, cx| editor.selections.ranges(cx))
544 .unwrap()
545 );
546 assert_set_eq!(
547 cloned_editor
548 .update(cx, |e, cx| e.selections.display_ranges(cx))
549 .unwrap(),
550 editor
551 .update(cx, |e, cx| e.selections.display_ranges(cx))
552 .unwrap()
553 );
554}
555
556#[gpui::test]
557async fn test_navigation_history(cx: &mut TestAppContext) {
558 init_test(cx, |_| {});
559
560 use workspace::item::Item;
561
562 let fs = FakeFs::new(cx.executor());
563 let project = Project::test(fs, [], cx).await;
564 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
565 let pane = workspace
566 .update(cx, |workspace, _| workspace.active_pane().clone())
567 .unwrap();
568
569 _ = workspace.update(cx, |_v, cx| {
570 cx.new_view(|cx| {
571 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
572 let mut editor = build_editor(buffer.clone(), cx);
573 let handle = cx.view();
574 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
575
576 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
577 editor.nav_history.as_mut().unwrap().pop_backward(cx)
578 }
579
580 // Move the cursor a small distance.
581 // Nothing is added to the navigation history.
582 editor.change_selections(None, cx, |s| {
583 s.select_display_ranges([
584 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
585 ])
586 });
587 editor.change_selections(None, cx, |s| {
588 s.select_display_ranges([
589 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
590 ])
591 });
592 assert!(pop_history(&mut editor, cx).is_none());
593
594 // Move the cursor a large distance.
595 // The history can jump back to the previous position.
596 editor.change_selections(None, cx, |s| {
597 s.select_display_ranges([
598 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
599 ])
600 });
601 let nav_entry = pop_history(&mut editor, cx).unwrap();
602 editor.navigate(nav_entry.data.unwrap(), cx);
603 assert_eq!(nav_entry.item.id(), cx.entity_id());
604 assert_eq!(
605 editor.selections.display_ranges(cx),
606 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
607 );
608 assert!(pop_history(&mut editor, cx).is_none());
609
610 // Move the cursor a small distance via the mouse.
611 // Nothing is added to the navigation history.
612 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
613 editor.end_selection(cx);
614 assert_eq!(
615 editor.selections.display_ranges(cx),
616 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
617 );
618 assert!(pop_history(&mut editor, cx).is_none());
619
620 // Move the cursor a large distance via the mouse.
621 // The history can jump back to the previous position.
622 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
623 editor.end_selection(cx);
624 assert_eq!(
625 editor.selections.display_ranges(cx),
626 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
627 );
628 let nav_entry = pop_history(&mut editor, cx).unwrap();
629 editor.navigate(nav_entry.data.unwrap(), cx);
630 assert_eq!(nav_entry.item.id(), cx.entity_id());
631 assert_eq!(
632 editor.selections.display_ranges(cx),
633 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
634 );
635 assert!(pop_history(&mut editor, cx).is_none());
636
637 // Set scroll position to check later
638 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
639 let original_scroll_position = editor.scroll_manager.anchor();
640
641 // Jump to the end of the document and adjust scroll
642 editor.move_to_end(&MoveToEnd, cx);
643 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
644 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
645
646 let nav_entry = pop_history(&mut editor, cx).unwrap();
647 editor.navigate(nav_entry.data.unwrap(), cx);
648 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
649
650 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
651 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
652 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
653 let invalid_point = Point::new(9999, 0);
654 editor.navigate(
655 Box::new(NavigationData {
656 cursor_anchor: invalid_anchor,
657 cursor_position: invalid_point,
658 scroll_anchor: ScrollAnchor {
659 anchor: invalid_anchor,
660 offset: Default::default(),
661 },
662 scroll_top_row: invalid_point.row,
663 }),
664 cx,
665 );
666 assert_eq!(
667 editor.selections.display_ranges(cx),
668 &[editor.max_point(cx)..editor.max_point(cx)]
669 );
670 assert_eq!(
671 editor.scroll_position(cx),
672 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
673 );
674
675 editor
676 })
677 });
678}
679
680#[gpui::test]
681fn test_cancel(cx: &mut TestAppContext) {
682 init_test(cx, |_| {});
683
684 let view = cx.add_window(|cx| {
685 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
686 build_editor(buffer, cx)
687 });
688
689 _ = view.update(cx, |view, cx| {
690 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
691 view.update_selection(
692 DisplayPoint::new(DisplayRow(1), 1),
693 0,
694 gpui::Point::<f32>::default(),
695 cx,
696 );
697 view.end_selection(cx);
698
699 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
700 view.update_selection(
701 DisplayPoint::new(DisplayRow(0), 3),
702 0,
703 gpui::Point::<f32>::default(),
704 cx,
705 );
706 view.end_selection(cx);
707 assert_eq!(
708 view.selections.display_ranges(cx),
709 [
710 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
711 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
712 ]
713 );
714 });
715
716 _ = view.update(cx, |view, cx| {
717 view.cancel(&Cancel, cx);
718 assert_eq!(
719 view.selections.display_ranges(cx),
720 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
721 );
722 });
723
724 _ = view.update(cx, |view, cx| {
725 view.cancel(&Cancel, cx);
726 assert_eq!(
727 view.selections.display_ranges(cx),
728 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
729 );
730 });
731}
732
733#[gpui::test]
734fn test_fold_action(cx: &mut TestAppContext) {
735 init_test(cx, |_| {});
736
737 let view = cx.add_window(|cx| {
738 let buffer = MultiBuffer::build_simple(
739 &"
740 impl Foo {
741 // Hello!
742
743 fn a() {
744 1
745 }
746
747 fn b() {
748 2
749 }
750
751 fn c() {
752 3
753 }
754 }
755 "
756 .unindent(),
757 cx,
758 );
759 build_editor(buffer.clone(), cx)
760 });
761
762 _ = view.update(cx, |view, cx| {
763 view.change_selections(None, cx, |s| {
764 s.select_display_ranges([
765 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
766 ]);
767 });
768 view.fold(&Fold, cx);
769 assert_eq!(
770 view.display_text(cx),
771 "
772 impl Foo {
773 // Hello!
774
775 fn a() {
776 1
777 }
778
779 fn b() {⋯
780 }
781
782 fn c() {⋯
783 }
784 }
785 "
786 .unindent(),
787 );
788
789 view.fold(&Fold, cx);
790 assert_eq!(
791 view.display_text(cx),
792 "
793 impl Foo {⋯
794 }
795 "
796 .unindent(),
797 );
798
799 view.unfold_lines(&UnfoldLines, cx);
800 assert_eq!(
801 view.display_text(cx),
802 "
803 impl Foo {
804 // Hello!
805
806 fn a() {
807 1
808 }
809
810 fn b() {⋯
811 }
812
813 fn c() {⋯
814 }
815 }
816 "
817 .unindent(),
818 );
819
820 view.unfold_lines(&UnfoldLines, cx);
821 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
822 });
823}
824
825#[gpui::test]
826fn test_move_cursor(cx: &mut TestAppContext) {
827 init_test(cx, |_| {});
828
829 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
830 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
831
832 _ = buffer.update(cx, |buffer, cx| {
833 buffer.edit(
834 vec![
835 (Point::new(1, 0)..Point::new(1, 0), "\t"),
836 (Point::new(1, 1)..Point::new(1, 1), "\t"),
837 ],
838 None,
839 cx,
840 );
841 });
842 _ = view.update(cx, |view, cx| {
843 assert_eq!(
844 view.selections.display_ranges(cx),
845 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
846 );
847
848 view.move_down(&MoveDown, cx);
849 assert_eq!(
850 view.selections.display_ranges(cx),
851 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
852 );
853
854 view.move_right(&MoveRight, cx);
855 assert_eq!(
856 view.selections.display_ranges(cx),
857 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
858 );
859
860 view.move_left(&MoveLeft, cx);
861 assert_eq!(
862 view.selections.display_ranges(cx),
863 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
864 );
865
866 view.move_up(&MoveUp, cx);
867 assert_eq!(
868 view.selections.display_ranges(cx),
869 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
870 );
871
872 view.move_to_end(&MoveToEnd, cx);
873 assert_eq!(
874 view.selections.display_ranges(cx),
875 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
876 );
877
878 view.move_to_beginning(&MoveToBeginning, cx);
879 assert_eq!(
880 view.selections.display_ranges(cx),
881 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
882 );
883
884 view.change_selections(None, cx, |s| {
885 s.select_display_ranges([
886 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
887 ]);
888 });
889 view.select_to_beginning(&SelectToBeginning, cx);
890 assert_eq!(
891 view.selections.display_ranges(cx),
892 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
893 );
894
895 view.select_to_end(&SelectToEnd, cx);
896 assert_eq!(
897 view.selections.display_ranges(cx),
898 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
899 );
900 });
901}
902
903#[gpui::test]
904fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
905 init_test(cx, |_| {});
906
907 let view = cx.add_window(|cx| {
908 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
909 build_editor(buffer.clone(), cx)
910 });
911
912 assert_eq!('ⓐ'.len_utf8(), 3);
913 assert_eq!('α'.len_utf8(), 2);
914
915 _ = view.update(cx, |view, cx| {
916 view.fold_ranges(
917 vec![
918 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
919 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
920 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
921 ],
922 true,
923 cx,
924 );
925 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
926
927 view.move_right(&MoveRight, cx);
928 assert_eq!(
929 view.selections.display_ranges(cx),
930 &[empty_range(0, "ⓐ".len())]
931 );
932 view.move_right(&MoveRight, cx);
933 assert_eq!(
934 view.selections.display_ranges(cx),
935 &[empty_range(0, "ⓐⓑ".len())]
936 );
937 view.move_right(&MoveRight, cx);
938 assert_eq!(
939 view.selections.display_ranges(cx),
940 &[empty_range(0, "ⓐⓑ⋯".len())]
941 );
942
943 view.move_down(&MoveDown, cx);
944 assert_eq!(
945 view.selections.display_ranges(cx),
946 &[empty_range(1, "ab⋯e".len())]
947 );
948 view.move_left(&MoveLeft, cx);
949 assert_eq!(
950 view.selections.display_ranges(cx),
951 &[empty_range(1, "ab⋯".len())]
952 );
953 view.move_left(&MoveLeft, cx);
954 assert_eq!(
955 view.selections.display_ranges(cx),
956 &[empty_range(1, "ab".len())]
957 );
958 view.move_left(&MoveLeft, cx);
959 assert_eq!(
960 view.selections.display_ranges(cx),
961 &[empty_range(1, "a".len())]
962 );
963
964 view.move_down(&MoveDown, cx);
965 assert_eq!(
966 view.selections.display_ranges(cx),
967 &[empty_range(2, "α".len())]
968 );
969 view.move_right(&MoveRight, cx);
970 assert_eq!(
971 view.selections.display_ranges(cx),
972 &[empty_range(2, "αβ".len())]
973 );
974 view.move_right(&MoveRight, cx);
975 assert_eq!(
976 view.selections.display_ranges(cx),
977 &[empty_range(2, "αβ⋯".len())]
978 );
979 view.move_right(&MoveRight, cx);
980 assert_eq!(
981 view.selections.display_ranges(cx),
982 &[empty_range(2, "αβ⋯ε".len())]
983 );
984
985 view.move_up(&MoveUp, cx);
986 assert_eq!(
987 view.selections.display_ranges(cx),
988 &[empty_range(1, "ab⋯e".len())]
989 );
990 view.move_down(&MoveDown, cx);
991 assert_eq!(
992 view.selections.display_ranges(cx),
993 &[empty_range(2, "αβ⋯ε".len())]
994 );
995 view.move_up(&MoveUp, cx);
996 assert_eq!(
997 view.selections.display_ranges(cx),
998 &[empty_range(1, "ab⋯e".len())]
999 );
1000
1001 view.move_up(&MoveUp, cx);
1002 assert_eq!(
1003 view.selections.display_ranges(cx),
1004 &[empty_range(0, "ⓐⓑ".len())]
1005 );
1006 view.move_left(&MoveLeft, cx);
1007 assert_eq!(
1008 view.selections.display_ranges(cx),
1009 &[empty_range(0, "ⓐ".len())]
1010 );
1011 view.move_left(&MoveLeft, cx);
1012 assert_eq!(
1013 view.selections.display_ranges(cx),
1014 &[empty_range(0, "".len())]
1015 );
1016 });
1017}
1018
1019#[gpui::test]
1020fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1021 init_test(cx, |_| {});
1022
1023 let view = cx.add_window(|cx| {
1024 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1025 build_editor(buffer.clone(), cx)
1026 });
1027 _ = view.update(cx, |view, cx| {
1028 view.change_selections(None, cx, |s| {
1029 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1030 });
1031 view.move_down(&MoveDown, cx);
1032 assert_eq!(
1033 view.selections.display_ranges(cx),
1034 &[empty_range(1, "abcd".len())]
1035 );
1036
1037 view.move_down(&MoveDown, cx);
1038 assert_eq!(
1039 view.selections.display_ranges(cx),
1040 &[empty_range(2, "αβγ".len())]
1041 );
1042
1043 view.move_down(&MoveDown, cx);
1044 assert_eq!(
1045 view.selections.display_ranges(cx),
1046 &[empty_range(3, "abcd".len())]
1047 );
1048
1049 view.move_down(&MoveDown, cx);
1050 assert_eq!(
1051 view.selections.display_ranges(cx),
1052 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1053 );
1054
1055 view.move_up(&MoveUp, cx);
1056 assert_eq!(
1057 view.selections.display_ranges(cx),
1058 &[empty_range(3, "abcd".len())]
1059 );
1060
1061 view.move_up(&MoveUp, cx);
1062 assert_eq!(
1063 view.selections.display_ranges(cx),
1064 &[empty_range(2, "αβγ".len())]
1065 );
1066 });
1067}
1068
1069#[gpui::test]
1070fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1071 init_test(cx, |_| {});
1072 let move_to_beg = MoveToBeginningOfLine {
1073 stop_at_soft_wraps: true,
1074 };
1075
1076 let move_to_end = MoveToEndOfLine {
1077 stop_at_soft_wraps: true,
1078 };
1079
1080 let view = cx.add_window(|cx| {
1081 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1082 build_editor(buffer, cx)
1083 });
1084 _ = view.update(cx, |view, cx| {
1085 view.change_selections(None, cx, |s| {
1086 s.select_display_ranges([
1087 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1088 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1089 ]);
1090 });
1091 });
1092
1093 _ = view.update(cx, |view, cx| {
1094 view.move_to_beginning_of_line(&move_to_beg, cx);
1095 assert_eq!(
1096 view.selections.display_ranges(cx),
1097 &[
1098 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1099 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1100 ]
1101 );
1102 });
1103
1104 _ = view.update(cx, |view, cx| {
1105 view.move_to_beginning_of_line(&move_to_beg, cx);
1106 assert_eq!(
1107 view.selections.display_ranges(cx),
1108 &[
1109 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1110 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1111 ]
1112 );
1113 });
1114
1115 _ = view.update(cx, |view, cx| {
1116 view.move_to_beginning_of_line(&move_to_beg, cx);
1117 assert_eq!(
1118 view.selections.display_ranges(cx),
1119 &[
1120 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1121 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1122 ]
1123 );
1124 });
1125
1126 _ = view.update(cx, |view, cx| {
1127 view.move_to_end_of_line(&move_to_end, cx);
1128 assert_eq!(
1129 view.selections.display_ranges(cx),
1130 &[
1131 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1132 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1133 ]
1134 );
1135 });
1136
1137 // Moving to the end of line again is a no-op.
1138 _ = view.update(cx, |view, cx| {
1139 view.move_to_end_of_line(&move_to_end, cx);
1140 assert_eq!(
1141 view.selections.display_ranges(cx),
1142 &[
1143 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1144 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1145 ]
1146 );
1147 });
1148
1149 _ = view.update(cx, |view, cx| {
1150 view.move_left(&MoveLeft, cx);
1151 view.select_to_beginning_of_line(
1152 &SelectToBeginningOfLine {
1153 stop_at_soft_wraps: true,
1154 },
1155 cx,
1156 );
1157 assert_eq!(
1158 view.selections.display_ranges(cx),
1159 &[
1160 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1161 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1162 ]
1163 );
1164 });
1165
1166 _ = view.update(cx, |view, cx| {
1167 view.select_to_beginning_of_line(
1168 &SelectToBeginningOfLine {
1169 stop_at_soft_wraps: true,
1170 },
1171 cx,
1172 );
1173 assert_eq!(
1174 view.selections.display_ranges(cx),
1175 &[
1176 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1177 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1178 ]
1179 );
1180 });
1181
1182 _ = view.update(cx, |view, cx| {
1183 view.select_to_beginning_of_line(
1184 &SelectToBeginningOfLine {
1185 stop_at_soft_wraps: true,
1186 },
1187 cx,
1188 );
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[
1192 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1193 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1194 ]
1195 );
1196 });
1197
1198 _ = view.update(cx, |view, cx| {
1199 view.select_to_end_of_line(
1200 &SelectToEndOfLine {
1201 stop_at_soft_wraps: true,
1202 },
1203 cx,
1204 );
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[
1208 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1209 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1210 ]
1211 );
1212 });
1213
1214 _ = view.update(cx, |view, cx| {
1215 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1216 assert_eq!(view.display_text(cx), "ab\n de");
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[
1220 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1221 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1222 ]
1223 );
1224 });
1225
1226 _ = view.update(cx, |view, cx| {
1227 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1228 assert_eq!(view.display_text(cx), "\n");
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[
1232 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1233 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1234 ]
1235 );
1236 });
1237}
1238
1239#[gpui::test]
1240fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1241 init_test(cx, |_| {});
1242 let move_to_beg = MoveToBeginningOfLine {
1243 stop_at_soft_wraps: false,
1244 };
1245
1246 let move_to_end = MoveToEndOfLine {
1247 stop_at_soft_wraps: false,
1248 };
1249
1250 let view = cx.add_window(|cx| {
1251 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1252 build_editor(buffer, cx)
1253 });
1254
1255 _ = view.update(cx, |view, cx| {
1256 view.set_wrap_width(Some(140.0.into()), cx);
1257
1258 // We expect the following lines after wrapping
1259 // ```
1260 // thequickbrownfox
1261 // jumpedoverthelazydo
1262 // gs
1263 // ```
1264 // The final `gs` was soft-wrapped onto a new line.
1265 assert_eq!(
1266 "thequickbrownfox\njumpedoverthelaz\nydogs",
1267 view.display_text(cx),
1268 );
1269
1270 // First, let's assert behavior on the first line, that was not soft-wrapped.
1271 // Start the cursor at the `k` on the first line
1272 view.change_selections(None, cx, |s| {
1273 s.select_display_ranges([
1274 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1275 ]);
1276 });
1277
1278 // Moving to the beginning of the line should put us at the beginning of the line.
1279 view.move_to_beginning_of_line(&move_to_beg, cx);
1280 assert_eq!(
1281 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1282 view.selections.display_ranges(cx)
1283 );
1284
1285 // Moving to the end of the line should put us at the end of the line.
1286 view.move_to_end_of_line(&move_to_end, cx);
1287 assert_eq!(
1288 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1289 view.selections.display_ranges(cx)
1290 );
1291
1292 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1293 // Start the cursor at the last line (`y` that was wrapped to a new line)
1294 view.change_selections(None, cx, |s| {
1295 s.select_display_ranges([
1296 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1297 ]);
1298 });
1299
1300 // Moving to the beginning of the line should put us at the start of the second line of
1301 // display text, i.e., the `j`.
1302 view.move_to_beginning_of_line(&move_to_beg, cx);
1303 assert_eq!(
1304 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1305 view.selections.display_ranges(cx)
1306 );
1307
1308 // Moving to the beginning of the line again should be a no-op.
1309 view.move_to_beginning_of_line(&move_to_beg, cx);
1310 assert_eq!(
1311 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1312 view.selections.display_ranges(cx)
1313 );
1314
1315 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1316 // next display line.
1317 view.move_to_end_of_line(&move_to_end, cx);
1318 assert_eq!(
1319 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1320 view.selections.display_ranges(cx)
1321 );
1322
1323 // Moving to the end of the line again should be a no-op.
1324 view.move_to_end_of_line(&move_to_end, cx);
1325 assert_eq!(
1326 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1327 view.selections.display_ranges(cx)
1328 );
1329 });
1330}
1331
1332#[gpui::test]
1333fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1334 init_test(cx, |_| {});
1335
1336 let view = cx.add_window(|cx| {
1337 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1338 build_editor(buffer, cx)
1339 });
1340 _ = view.update(cx, |view, cx| {
1341 view.change_selections(None, cx, |s| {
1342 s.select_display_ranges([
1343 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1344 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1345 ])
1346 });
1347
1348 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1349 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1350
1351 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1352 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1353
1354 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1355 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1356
1357 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1358 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1359
1360 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1361 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1362
1363 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1364 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1365
1366 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1367 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1368
1369 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1370 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1371
1372 view.move_right(&MoveRight, cx);
1373 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1374 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1375
1376 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1377 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1378
1379 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1380 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1381 });
1382}
1383
1384#[gpui::test]
1385fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1386 init_test(cx, |_| {});
1387
1388 let view = cx.add_window(|cx| {
1389 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1390 build_editor(buffer, cx)
1391 });
1392
1393 _ = view.update(cx, |view, cx| {
1394 view.set_wrap_width(Some(140.0.into()), cx);
1395 assert_eq!(
1396 view.display_text(cx),
1397 "use one::{\n two::three::\n four::five\n};"
1398 );
1399
1400 view.change_selections(None, cx, |s| {
1401 s.select_display_ranges([
1402 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1403 ]);
1404 });
1405
1406 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1407 assert_eq!(
1408 view.selections.display_ranges(cx),
1409 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1410 );
1411
1412 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1413 assert_eq!(
1414 view.selections.display_ranges(cx),
1415 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1416 );
1417
1418 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1419 assert_eq!(
1420 view.selections.display_ranges(cx),
1421 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1422 );
1423
1424 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1425 assert_eq!(
1426 view.selections.display_ranges(cx),
1427 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1428 );
1429
1430 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1431 assert_eq!(
1432 view.selections.display_ranges(cx),
1433 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1434 );
1435
1436 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1440 );
1441 });
1442}
1443
1444#[gpui::test]
1445async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1446 init_test(cx, |_| {});
1447 let mut cx = EditorTestContext::new(cx).await;
1448
1449 let line_height = cx.editor(|editor, cx| {
1450 editor
1451 .style()
1452 .unwrap()
1453 .text
1454 .line_height_in_pixels(cx.rem_size())
1455 });
1456 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1457
1458 cx.set_state(
1459 &r#"ˇone
1460 two
1461
1462 three
1463 fourˇ
1464 five
1465
1466 six"#
1467 .unindent(),
1468 );
1469
1470 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1471 cx.assert_editor_state(
1472 &r#"one
1473 two
1474 ˇ
1475 three
1476 four
1477 five
1478 ˇ
1479 six"#
1480 .unindent(),
1481 );
1482
1483 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1484 cx.assert_editor_state(
1485 &r#"one
1486 two
1487
1488 three
1489 four
1490 five
1491 ˇ
1492 sixˇ"#
1493 .unindent(),
1494 );
1495
1496 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1497 cx.assert_editor_state(
1498 &r#"one
1499 two
1500
1501 three
1502 four
1503 five
1504
1505 sixˇ"#
1506 .unindent(),
1507 );
1508
1509 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1510 cx.assert_editor_state(
1511 &r#"one
1512 two
1513
1514 three
1515 four
1516 five
1517 ˇ
1518 six"#
1519 .unindent(),
1520 );
1521
1522 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1523 cx.assert_editor_state(
1524 &r#"one
1525 two
1526 ˇ
1527 three
1528 four
1529 five
1530
1531 six"#
1532 .unindent(),
1533 );
1534
1535 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1536 cx.assert_editor_state(
1537 &r#"ˇone
1538 two
1539
1540 three
1541 four
1542 five
1543
1544 six"#
1545 .unindent(),
1546 );
1547}
1548
1549#[gpui::test]
1550async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1551 init_test(cx, |_| {});
1552 let mut cx = EditorTestContext::new(cx).await;
1553 let line_height = cx.editor(|editor, cx| {
1554 editor
1555 .style()
1556 .unwrap()
1557 .text
1558 .line_height_in_pixels(cx.rem_size())
1559 });
1560 let window = cx.window;
1561 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1562
1563 cx.set_state(
1564 &r#"ˇone
1565 two
1566 three
1567 four
1568 five
1569 six
1570 seven
1571 eight
1572 nine
1573 ten
1574 "#,
1575 );
1576
1577 cx.update_editor(|editor, cx| {
1578 assert_eq!(
1579 editor.snapshot(cx).scroll_position(),
1580 gpui::Point::new(0., 0.)
1581 );
1582 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1583 assert_eq!(
1584 editor.snapshot(cx).scroll_position(),
1585 gpui::Point::new(0., 3.)
1586 );
1587 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1588 assert_eq!(
1589 editor.snapshot(cx).scroll_position(),
1590 gpui::Point::new(0., 6.)
1591 );
1592 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1593 assert_eq!(
1594 editor.snapshot(cx).scroll_position(),
1595 gpui::Point::new(0., 3.)
1596 );
1597
1598 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1599 assert_eq!(
1600 editor.snapshot(cx).scroll_position(),
1601 gpui::Point::new(0., 1.)
1602 );
1603 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1604 assert_eq!(
1605 editor.snapshot(cx).scroll_position(),
1606 gpui::Point::new(0., 3.)
1607 );
1608 });
1609}
1610
1611#[gpui::test]
1612async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1613 init_test(cx, |_| {});
1614 let mut cx = EditorTestContext::new(cx).await;
1615
1616 let line_height = cx.update_editor(|editor, cx| {
1617 editor.set_vertical_scroll_margin(2, cx);
1618 editor
1619 .style()
1620 .unwrap()
1621 .text
1622 .line_height_in_pixels(cx.rem_size())
1623 });
1624 let window = cx.window;
1625 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1626
1627 cx.set_state(
1628 &r#"ˇone
1629 two
1630 three
1631 four
1632 five
1633 six
1634 seven
1635 eight
1636 nine
1637 ten
1638 "#,
1639 );
1640 cx.update_editor(|editor, cx| {
1641 assert_eq!(
1642 editor.snapshot(cx).scroll_position(),
1643 gpui::Point::new(0., 0.0)
1644 );
1645 });
1646
1647 // Add a cursor below the visible area. Since both cursors cannot fit
1648 // on screen, the editor autoscrolls to reveal the newest cursor, and
1649 // allows the vertical scroll margin below that cursor.
1650 cx.update_editor(|editor, cx| {
1651 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1652 selections.select_ranges([
1653 Point::new(0, 0)..Point::new(0, 0),
1654 Point::new(6, 0)..Point::new(6, 0),
1655 ]);
1656 })
1657 });
1658 cx.update_editor(|editor, cx| {
1659 assert_eq!(
1660 editor.snapshot(cx).scroll_position(),
1661 gpui::Point::new(0., 3.0)
1662 );
1663 });
1664
1665 // Move down. The editor cursor scrolls down to track the newest cursor.
1666 cx.update_editor(|editor, cx| {
1667 editor.move_down(&Default::default(), cx);
1668 });
1669 cx.update_editor(|editor, cx| {
1670 assert_eq!(
1671 editor.snapshot(cx).scroll_position(),
1672 gpui::Point::new(0., 4.0)
1673 );
1674 });
1675
1676 // Add a cursor above the visible area. Since both cursors fit on screen,
1677 // the editor scrolls to show both.
1678 cx.update_editor(|editor, cx| {
1679 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1680 selections.select_ranges([
1681 Point::new(1, 0)..Point::new(1, 0),
1682 Point::new(6, 0)..Point::new(6, 0),
1683 ]);
1684 })
1685 });
1686 cx.update_editor(|editor, cx| {
1687 assert_eq!(
1688 editor.snapshot(cx).scroll_position(),
1689 gpui::Point::new(0., 1.0)
1690 );
1691 });
1692}
1693
1694#[gpui::test]
1695async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1696 init_test(cx, |_| {});
1697 let mut cx = EditorTestContext::new(cx).await;
1698
1699 let line_height = cx.editor(|editor, cx| {
1700 editor
1701 .style()
1702 .unwrap()
1703 .text
1704 .line_height_in_pixels(cx.rem_size())
1705 });
1706 let window = cx.window;
1707 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1708 cx.set_state(
1709 &r#"
1710 ˇone
1711 two
1712 threeˇ
1713 four
1714 five
1715 six
1716 seven
1717 eight
1718 nine
1719 ten
1720 "#
1721 .unindent(),
1722 );
1723
1724 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1725 cx.assert_editor_state(
1726 &r#"
1727 one
1728 two
1729 three
1730 ˇfour
1731 five
1732 sixˇ
1733 seven
1734 eight
1735 nine
1736 ten
1737 "#
1738 .unindent(),
1739 );
1740
1741 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1742 cx.assert_editor_state(
1743 &r#"
1744 one
1745 two
1746 three
1747 four
1748 five
1749 six
1750 ˇseven
1751 eight
1752 nineˇ
1753 ten
1754 "#
1755 .unindent(),
1756 );
1757
1758 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1759 cx.assert_editor_state(
1760 &r#"
1761 one
1762 two
1763 three
1764 ˇfour
1765 five
1766 sixˇ
1767 seven
1768 eight
1769 nine
1770 ten
1771 "#
1772 .unindent(),
1773 );
1774
1775 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1776 cx.assert_editor_state(
1777 &r#"
1778 ˇone
1779 two
1780 threeˇ
1781 four
1782 five
1783 six
1784 seven
1785 eight
1786 nine
1787 ten
1788 "#
1789 .unindent(),
1790 );
1791
1792 // Test select collapsing
1793 cx.update_editor(|editor, cx| {
1794 editor.move_page_down(&MovePageDown::default(), cx);
1795 editor.move_page_down(&MovePageDown::default(), cx);
1796 editor.move_page_down(&MovePageDown::default(), cx);
1797 });
1798 cx.assert_editor_state(
1799 &r#"
1800 one
1801 two
1802 three
1803 four
1804 five
1805 six
1806 seven
1807 eight
1808 nine
1809 ˇten
1810 ˇ"#
1811 .unindent(),
1812 );
1813}
1814
1815#[gpui::test]
1816async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1817 init_test(cx, |_| {});
1818 let mut cx = EditorTestContext::new(cx).await;
1819 cx.set_state("one «two threeˇ» four");
1820 cx.update_editor(|editor, cx| {
1821 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1822 assert_eq!(editor.text(cx), " four");
1823 });
1824}
1825
1826#[gpui::test]
1827fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1828 init_test(cx, |_| {});
1829
1830 let view = cx.add_window(|cx| {
1831 let buffer = MultiBuffer::build_simple("one two three four", cx);
1832 build_editor(buffer.clone(), cx)
1833 });
1834
1835 _ = view.update(cx, |view, cx| {
1836 view.change_selections(None, cx, |s| {
1837 s.select_display_ranges([
1838 // an empty selection - the preceding word fragment is deleted
1839 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1840 // characters selected - they are deleted
1841 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
1842 ])
1843 });
1844 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1845 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1846 });
1847
1848 _ = view.update(cx, |view, cx| {
1849 view.change_selections(None, cx, |s| {
1850 s.select_display_ranges([
1851 // an empty selection - the following word fragment is deleted
1852 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1853 // characters selected - they are deleted
1854 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
1855 ])
1856 });
1857 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1858 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1859 });
1860}
1861
1862#[gpui::test]
1863fn test_newline(cx: &mut TestAppContext) {
1864 init_test(cx, |_| {});
1865
1866 let view = cx.add_window(|cx| {
1867 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1868 build_editor(buffer.clone(), cx)
1869 });
1870
1871 _ = view.update(cx, |view, cx| {
1872 view.change_selections(None, cx, |s| {
1873 s.select_display_ranges([
1874 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1875 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1876 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
1877 ])
1878 });
1879
1880 view.newline(&Newline, cx);
1881 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1882 });
1883}
1884
1885#[gpui::test]
1886fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1887 init_test(cx, |_| {});
1888
1889 let editor = cx.add_window(|cx| {
1890 let buffer = MultiBuffer::build_simple(
1891 "
1892 a
1893 b(
1894 X
1895 )
1896 c(
1897 X
1898 )
1899 "
1900 .unindent()
1901 .as_str(),
1902 cx,
1903 );
1904 let mut editor = build_editor(buffer.clone(), cx);
1905 editor.change_selections(None, cx, |s| {
1906 s.select_ranges([
1907 Point::new(2, 4)..Point::new(2, 5),
1908 Point::new(5, 4)..Point::new(5, 5),
1909 ])
1910 });
1911 editor
1912 });
1913
1914 _ = editor.update(cx, |editor, cx| {
1915 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1916 editor.buffer.update(cx, |buffer, cx| {
1917 buffer.edit(
1918 [
1919 (Point::new(1, 2)..Point::new(3, 0), ""),
1920 (Point::new(4, 2)..Point::new(6, 0), ""),
1921 ],
1922 None,
1923 cx,
1924 );
1925 assert_eq!(
1926 buffer.read(cx).text(),
1927 "
1928 a
1929 b()
1930 c()
1931 "
1932 .unindent()
1933 );
1934 });
1935 assert_eq!(
1936 editor.selections.ranges(cx),
1937 &[
1938 Point::new(1, 2)..Point::new(1, 2),
1939 Point::new(2, 2)..Point::new(2, 2),
1940 ],
1941 );
1942
1943 editor.newline(&Newline, cx);
1944 assert_eq!(
1945 editor.text(cx),
1946 "
1947 a
1948 b(
1949 )
1950 c(
1951 )
1952 "
1953 .unindent()
1954 );
1955
1956 // The selections are moved after the inserted newlines
1957 assert_eq!(
1958 editor.selections.ranges(cx),
1959 &[
1960 Point::new(2, 0)..Point::new(2, 0),
1961 Point::new(4, 0)..Point::new(4, 0),
1962 ],
1963 );
1964 });
1965}
1966
1967#[gpui::test]
1968async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1969 init_test(cx, |settings| {
1970 settings.defaults.tab_size = NonZeroU32::new(4)
1971 });
1972
1973 let language = Arc::new(
1974 Language::new(
1975 LanguageConfig::default(),
1976 Some(tree_sitter_rust::language()),
1977 )
1978 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1979 .unwrap(),
1980 );
1981
1982 let mut cx = EditorTestContext::new(cx).await;
1983 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1984 cx.set_state(indoc! {"
1985 const a: ˇA = (
1986 (ˇ
1987 «const_functionˇ»(ˇ),
1988 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1989 )ˇ
1990 ˇ);ˇ
1991 "});
1992
1993 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1994 cx.assert_editor_state(indoc! {"
1995 ˇ
1996 const a: A = (
1997 ˇ
1998 (
1999 ˇ
2000 ˇ
2001 const_function(),
2002 ˇ
2003 ˇ
2004 ˇ
2005 ˇ
2006 something_else,
2007 ˇ
2008 )
2009 ˇ
2010 ˇ
2011 );
2012 "});
2013}
2014
2015#[gpui::test]
2016async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2017 init_test(cx, |settings| {
2018 settings.defaults.tab_size = NonZeroU32::new(4)
2019 });
2020
2021 let language = Arc::new(
2022 Language::new(
2023 LanguageConfig::default(),
2024 Some(tree_sitter_rust::language()),
2025 )
2026 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2027 .unwrap(),
2028 );
2029
2030 let mut cx = EditorTestContext::new(cx).await;
2031 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2032 cx.set_state(indoc! {"
2033 const a: ˇA = (
2034 (ˇ
2035 «const_functionˇ»(ˇ),
2036 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2037 )ˇ
2038 ˇ);ˇ
2039 "});
2040
2041 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2042 cx.assert_editor_state(indoc! {"
2043 const a: A = (
2044 ˇ
2045 (
2046 ˇ
2047 const_function(),
2048 ˇ
2049 ˇ
2050 something_else,
2051 ˇ
2052 ˇ
2053 ˇ
2054 ˇ
2055 )
2056 ˇ
2057 );
2058 ˇ
2059 ˇ
2060 "});
2061}
2062
2063#[gpui::test]
2064async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2065 init_test(cx, |settings| {
2066 settings.defaults.tab_size = NonZeroU32::new(4)
2067 });
2068
2069 let language = Arc::new(Language::new(
2070 LanguageConfig {
2071 line_comments: vec!["//".into()],
2072 ..LanguageConfig::default()
2073 },
2074 None,
2075 ));
2076 {
2077 let mut cx = EditorTestContext::new(cx).await;
2078 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2079 cx.set_state(indoc! {"
2080 // Fooˇ
2081 "});
2082
2083 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2084 cx.assert_editor_state(indoc! {"
2085 // Foo
2086 //ˇ
2087 "});
2088 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2089 cx.set_state(indoc! {"
2090 ˇ// Foo
2091 "});
2092 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2093 cx.assert_editor_state(indoc! {"
2094
2095 ˇ// Foo
2096 "});
2097 }
2098 // Ensure that comment continuations can be disabled.
2099 update_test_language_settings(cx, |settings| {
2100 settings.defaults.extend_comment_on_newline = Some(false);
2101 });
2102 let mut cx = EditorTestContext::new(cx).await;
2103 cx.set_state(indoc! {"
2104 // Fooˇ
2105 "});
2106 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2107 cx.assert_editor_state(indoc! {"
2108 // Foo
2109 ˇ
2110 "});
2111}
2112
2113#[gpui::test]
2114fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2115 init_test(cx, |_| {});
2116
2117 let editor = cx.add_window(|cx| {
2118 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2119 let mut editor = build_editor(buffer.clone(), cx);
2120 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2121 editor
2122 });
2123
2124 _ = editor.update(cx, |editor, cx| {
2125 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2126 editor.buffer.update(cx, |buffer, cx| {
2127 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2128 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2129 });
2130 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2131
2132 editor.insert("Z", cx);
2133 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2134
2135 // The selections are moved after the inserted characters
2136 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2137 });
2138}
2139
2140#[gpui::test]
2141async fn test_tab(cx: &mut gpui::TestAppContext) {
2142 init_test(cx, |settings| {
2143 settings.defaults.tab_size = NonZeroU32::new(3)
2144 });
2145
2146 let mut cx = EditorTestContext::new(cx).await;
2147 cx.set_state(indoc! {"
2148 ˇabˇc
2149 ˇ🏀ˇ🏀ˇefg
2150 dˇ
2151 "});
2152 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2153 cx.assert_editor_state(indoc! {"
2154 ˇab ˇc
2155 ˇ🏀 ˇ🏀 ˇefg
2156 d ˇ
2157 "});
2158
2159 cx.set_state(indoc! {"
2160 a
2161 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2162 "});
2163 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2164 cx.assert_editor_state(indoc! {"
2165 a
2166 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2167 "});
2168}
2169
2170#[gpui::test]
2171async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2172 init_test(cx, |_| {});
2173
2174 let mut cx = EditorTestContext::new(cx).await;
2175 let language = Arc::new(
2176 Language::new(
2177 LanguageConfig::default(),
2178 Some(tree_sitter_rust::language()),
2179 )
2180 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2181 .unwrap(),
2182 );
2183 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2184
2185 // cursors that are already at the suggested indent level insert
2186 // a soft tab. cursors that are to the left of the suggested indent
2187 // auto-indent their line.
2188 cx.set_state(indoc! {"
2189 ˇ
2190 const a: B = (
2191 c(
2192 d(
2193 ˇ
2194 )
2195 ˇ
2196 ˇ )
2197 );
2198 "});
2199 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2200 cx.assert_editor_state(indoc! {"
2201 ˇ
2202 const a: B = (
2203 c(
2204 d(
2205 ˇ
2206 )
2207 ˇ
2208 ˇ)
2209 );
2210 "});
2211
2212 // handle auto-indent when there are multiple cursors on the same line
2213 cx.set_state(indoc! {"
2214 const a: B = (
2215 c(
2216 ˇ ˇ
2217 ˇ )
2218 );
2219 "});
2220 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2221 cx.assert_editor_state(indoc! {"
2222 const a: B = (
2223 c(
2224 ˇ
2225 ˇ)
2226 );
2227 "});
2228}
2229
2230#[gpui::test]
2231async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2232 init_test(cx, |settings| {
2233 settings.defaults.tab_size = NonZeroU32::new(4)
2234 });
2235
2236 let language = Arc::new(
2237 Language::new(
2238 LanguageConfig::default(),
2239 Some(tree_sitter_rust::language()),
2240 )
2241 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2242 .unwrap(),
2243 );
2244
2245 let mut cx = EditorTestContext::new(cx).await;
2246 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2247 cx.set_state(indoc! {"
2248 fn a() {
2249 if b {
2250 \t ˇc
2251 }
2252 }
2253 "});
2254
2255 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2256 cx.assert_editor_state(indoc! {"
2257 fn a() {
2258 if b {
2259 ˇc
2260 }
2261 }
2262 "});
2263}
2264
2265#[gpui::test]
2266async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2267 init_test(cx, |settings| {
2268 settings.defaults.tab_size = NonZeroU32::new(4);
2269 });
2270
2271 let mut cx = EditorTestContext::new(cx).await;
2272
2273 cx.set_state(indoc! {"
2274 «oneˇ» «twoˇ»
2275 three
2276 four
2277 "});
2278 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2279 cx.assert_editor_state(indoc! {"
2280 «oneˇ» «twoˇ»
2281 three
2282 four
2283 "});
2284
2285 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2286 cx.assert_editor_state(indoc! {"
2287 «oneˇ» «twoˇ»
2288 three
2289 four
2290 "});
2291
2292 // select across line ending
2293 cx.set_state(indoc! {"
2294 one two
2295 t«hree
2296 ˇ» four
2297 "});
2298 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2299 cx.assert_editor_state(indoc! {"
2300 one two
2301 t«hree
2302 ˇ» four
2303 "});
2304
2305 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2306 cx.assert_editor_state(indoc! {"
2307 one two
2308 t«hree
2309 ˇ» four
2310 "});
2311
2312 // Ensure that indenting/outdenting works when the cursor is at column 0.
2313 cx.set_state(indoc! {"
2314 one two
2315 ˇthree
2316 four
2317 "});
2318 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2319 cx.assert_editor_state(indoc! {"
2320 one two
2321 ˇthree
2322 four
2323 "});
2324
2325 cx.set_state(indoc! {"
2326 one two
2327 ˇ three
2328 four
2329 "});
2330 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2331 cx.assert_editor_state(indoc! {"
2332 one two
2333 ˇthree
2334 four
2335 "});
2336}
2337
2338#[gpui::test]
2339async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2340 init_test(cx, |settings| {
2341 settings.defaults.hard_tabs = Some(true);
2342 });
2343
2344 let mut cx = EditorTestContext::new(cx).await;
2345
2346 // select two ranges on one line
2347 cx.set_state(indoc! {"
2348 «oneˇ» «twoˇ»
2349 three
2350 four
2351 "});
2352 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2353 cx.assert_editor_state(indoc! {"
2354 \t«oneˇ» «twoˇ»
2355 three
2356 four
2357 "});
2358 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2359 cx.assert_editor_state(indoc! {"
2360 \t\t«oneˇ» «twoˇ»
2361 three
2362 four
2363 "});
2364 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2365 cx.assert_editor_state(indoc! {"
2366 \t«oneˇ» «twoˇ»
2367 three
2368 four
2369 "});
2370 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2371 cx.assert_editor_state(indoc! {"
2372 «oneˇ» «twoˇ»
2373 three
2374 four
2375 "});
2376
2377 // select across a line ending
2378 cx.set_state(indoc! {"
2379 one two
2380 t«hree
2381 ˇ»four
2382 "});
2383 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2384 cx.assert_editor_state(indoc! {"
2385 one two
2386 \tt«hree
2387 ˇ»four
2388 "});
2389 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2390 cx.assert_editor_state(indoc! {"
2391 one two
2392 \t\tt«hree
2393 ˇ»four
2394 "});
2395 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2396 cx.assert_editor_state(indoc! {"
2397 one two
2398 \tt«hree
2399 ˇ»four
2400 "});
2401 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2402 cx.assert_editor_state(indoc! {"
2403 one two
2404 t«hree
2405 ˇ»four
2406 "});
2407
2408 // Ensure that indenting/outdenting works when the cursor is at column 0.
2409 cx.set_state(indoc! {"
2410 one two
2411 ˇthree
2412 four
2413 "});
2414 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2415 cx.assert_editor_state(indoc! {"
2416 one two
2417 ˇthree
2418 four
2419 "});
2420 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2421 cx.assert_editor_state(indoc! {"
2422 one two
2423 \tˇthree
2424 four
2425 "});
2426 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2427 cx.assert_editor_state(indoc! {"
2428 one two
2429 ˇthree
2430 four
2431 "});
2432}
2433
2434#[gpui::test]
2435fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2436 init_test(cx, |settings| {
2437 settings.languages.extend([
2438 (
2439 "TOML".into(),
2440 LanguageSettingsContent {
2441 tab_size: NonZeroU32::new(2),
2442 ..Default::default()
2443 },
2444 ),
2445 (
2446 "Rust".into(),
2447 LanguageSettingsContent {
2448 tab_size: NonZeroU32::new(4),
2449 ..Default::default()
2450 },
2451 ),
2452 ]);
2453 });
2454
2455 let toml_language = Arc::new(Language::new(
2456 LanguageConfig {
2457 name: "TOML".into(),
2458 ..Default::default()
2459 },
2460 None,
2461 ));
2462 let rust_language = Arc::new(Language::new(
2463 LanguageConfig {
2464 name: "Rust".into(),
2465 ..Default::default()
2466 },
2467 None,
2468 ));
2469
2470 let toml_buffer =
2471 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2472 let rust_buffer = cx.new_model(|cx| {
2473 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2474 });
2475 let multibuffer = cx.new_model(|cx| {
2476 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2477 multibuffer.push_excerpts(
2478 toml_buffer.clone(),
2479 [ExcerptRange {
2480 context: Point::new(0, 0)..Point::new(2, 0),
2481 primary: None,
2482 }],
2483 cx,
2484 );
2485 multibuffer.push_excerpts(
2486 rust_buffer.clone(),
2487 [ExcerptRange {
2488 context: Point::new(0, 0)..Point::new(1, 0),
2489 primary: None,
2490 }],
2491 cx,
2492 );
2493 multibuffer
2494 });
2495
2496 cx.add_window(|cx| {
2497 let mut editor = build_editor(multibuffer, cx);
2498
2499 assert_eq!(
2500 editor.text(cx),
2501 indoc! {"
2502 a = 1
2503 b = 2
2504
2505 const c: usize = 3;
2506 "}
2507 );
2508
2509 select_ranges(
2510 &mut editor,
2511 indoc! {"
2512 «aˇ» = 1
2513 b = 2
2514
2515 «const c:ˇ» usize = 3;
2516 "},
2517 cx,
2518 );
2519
2520 editor.tab(&Tab, cx);
2521 assert_text_with_selections(
2522 &mut editor,
2523 indoc! {"
2524 «aˇ» = 1
2525 b = 2
2526
2527 «const c:ˇ» usize = 3;
2528 "},
2529 cx,
2530 );
2531 editor.tab_prev(&TabPrev, cx);
2532 assert_text_with_selections(
2533 &mut editor,
2534 indoc! {"
2535 «aˇ» = 1
2536 b = 2
2537
2538 «const c:ˇ» usize = 3;
2539 "},
2540 cx,
2541 );
2542
2543 editor
2544 });
2545}
2546
2547#[gpui::test]
2548async fn test_backspace(cx: &mut gpui::TestAppContext) {
2549 init_test(cx, |_| {});
2550
2551 let mut cx = EditorTestContext::new(cx).await;
2552
2553 // Basic backspace
2554 cx.set_state(indoc! {"
2555 onˇe two three
2556 fou«rˇ» five six
2557 seven «ˇeight nine
2558 »ten
2559 "});
2560 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2561 cx.assert_editor_state(indoc! {"
2562 oˇe two three
2563 fouˇ five six
2564 seven ˇten
2565 "});
2566
2567 // Test backspace inside and around indents
2568 cx.set_state(indoc! {"
2569 zero
2570 ˇone
2571 ˇtwo
2572 ˇ ˇ ˇ three
2573 ˇ ˇ four
2574 "});
2575 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2576 cx.assert_editor_state(indoc! {"
2577 zero
2578 ˇone
2579 ˇtwo
2580 ˇ threeˇ four
2581 "});
2582
2583 // Test backspace with line_mode set to true
2584 cx.update_editor(|e, _| e.selections.line_mode = true);
2585 cx.set_state(indoc! {"
2586 The ˇquick ˇbrown
2587 fox jumps over
2588 the lazy dog
2589 ˇThe qu«ick bˇ»rown"});
2590 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2591 cx.assert_editor_state(indoc! {"
2592 ˇfox jumps over
2593 the lazy dogˇ"});
2594}
2595
2596#[gpui::test]
2597async fn test_delete(cx: &mut gpui::TestAppContext) {
2598 init_test(cx, |_| {});
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.set_state(indoc! {"
2602 onˇe two three
2603 fou«rˇ» five six
2604 seven «ˇeight nine
2605 »ten
2606 "});
2607 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2608 cx.assert_editor_state(indoc! {"
2609 onˇ two three
2610 fouˇ five six
2611 seven ˇten
2612 "});
2613
2614 // Test backspace with line_mode set to true
2615 cx.update_editor(|e, _| e.selections.line_mode = true);
2616 cx.set_state(indoc! {"
2617 The ˇquick ˇbrown
2618 fox «ˇjum»ps over
2619 the lazy dog
2620 ˇThe qu«ick bˇ»rown"});
2621 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2622 cx.assert_editor_state("ˇthe lazy dogˇ");
2623}
2624
2625#[gpui::test]
2626fn test_delete_line(cx: &mut TestAppContext) {
2627 init_test(cx, |_| {});
2628
2629 let view = cx.add_window(|cx| {
2630 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2631 build_editor(buffer, cx)
2632 });
2633 _ = view.update(cx, |view, cx| {
2634 view.change_selections(None, cx, |s| {
2635 s.select_display_ranges([
2636 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2637 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2638 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2639 ])
2640 });
2641 view.delete_line(&DeleteLine, cx);
2642 assert_eq!(view.display_text(cx), "ghi");
2643 assert_eq!(
2644 view.selections.display_ranges(cx),
2645 vec![
2646 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2647 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2648 ]
2649 );
2650 });
2651
2652 let view = cx.add_window(|cx| {
2653 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2654 build_editor(buffer, cx)
2655 });
2656 _ = view.update(cx, |view, cx| {
2657 view.change_selections(None, cx, |s| {
2658 s.select_display_ranges([
2659 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2660 ])
2661 });
2662 view.delete_line(&DeleteLine, cx);
2663 assert_eq!(view.display_text(cx), "ghi\n");
2664 assert_eq!(
2665 view.selections.display_ranges(cx),
2666 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2667 );
2668 });
2669}
2670
2671#[gpui::test]
2672fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2673 init_test(cx, |_| {});
2674
2675 cx.add_window(|cx| {
2676 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2677 let mut editor = build_editor(buffer.clone(), cx);
2678 let buffer = buffer.read(cx).as_singleton().unwrap();
2679
2680 assert_eq!(
2681 editor.selections.ranges::<Point>(cx),
2682 &[Point::new(0, 0)..Point::new(0, 0)]
2683 );
2684
2685 // When on single line, replace newline at end by space
2686 editor.join_lines(&JoinLines, cx);
2687 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2688 assert_eq!(
2689 editor.selections.ranges::<Point>(cx),
2690 &[Point::new(0, 3)..Point::new(0, 3)]
2691 );
2692
2693 // When multiple lines are selected, remove newlines that are spanned by the selection
2694 editor.change_selections(None, cx, |s| {
2695 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2696 });
2697 editor.join_lines(&JoinLines, cx);
2698 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2699 assert_eq!(
2700 editor.selections.ranges::<Point>(cx),
2701 &[Point::new(0, 11)..Point::new(0, 11)]
2702 );
2703
2704 // Undo should be transactional
2705 editor.undo(&Undo, cx);
2706 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2707 assert_eq!(
2708 editor.selections.ranges::<Point>(cx),
2709 &[Point::new(0, 5)..Point::new(2, 2)]
2710 );
2711
2712 // When joining an empty line don't insert a space
2713 editor.change_selections(None, cx, |s| {
2714 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2715 });
2716 editor.join_lines(&JoinLines, cx);
2717 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2718 assert_eq!(
2719 editor.selections.ranges::<Point>(cx),
2720 [Point::new(2, 3)..Point::new(2, 3)]
2721 );
2722
2723 // We can remove trailing newlines
2724 editor.join_lines(&JoinLines, cx);
2725 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2726 assert_eq!(
2727 editor.selections.ranges::<Point>(cx),
2728 [Point::new(2, 3)..Point::new(2, 3)]
2729 );
2730
2731 // We don't blow up on the last line
2732 editor.join_lines(&JoinLines, cx);
2733 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2734 assert_eq!(
2735 editor.selections.ranges::<Point>(cx),
2736 [Point::new(2, 3)..Point::new(2, 3)]
2737 );
2738
2739 // reset to test indentation
2740 editor.buffer.update(cx, |buffer, cx| {
2741 buffer.edit(
2742 [
2743 (Point::new(1, 0)..Point::new(1, 2), " "),
2744 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
2745 ],
2746 None,
2747 cx,
2748 )
2749 });
2750
2751 // We remove any leading spaces
2752 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
2753 editor.change_selections(None, cx, |s| {
2754 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
2755 });
2756 editor.join_lines(&JoinLines, cx);
2757 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
2758
2759 // We don't insert a space for a line containing only spaces
2760 editor.join_lines(&JoinLines, cx);
2761 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
2762
2763 // We ignore any leading tabs
2764 editor.join_lines(&JoinLines, cx);
2765 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
2766
2767 editor
2768 });
2769}
2770
2771#[gpui::test]
2772fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
2773 init_test(cx, |_| {});
2774
2775 cx.add_window(|cx| {
2776 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2777 let mut editor = build_editor(buffer.clone(), cx);
2778 let buffer = buffer.read(cx).as_singleton().unwrap();
2779
2780 editor.change_selections(None, cx, |s| {
2781 s.select_ranges([
2782 Point::new(0, 2)..Point::new(1, 1),
2783 Point::new(1, 2)..Point::new(1, 2),
2784 Point::new(3, 1)..Point::new(3, 2),
2785 ])
2786 });
2787
2788 editor.join_lines(&JoinLines, cx);
2789 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
2790
2791 assert_eq!(
2792 editor.selections.ranges::<Point>(cx),
2793 [
2794 Point::new(0, 7)..Point::new(0, 7),
2795 Point::new(1, 3)..Point::new(1, 3)
2796 ]
2797 );
2798 editor
2799 });
2800}
2801
2802#[gpui::test]
2803async fn test_join_lines_with_git_diff_base(
2804 executor: BackgroundExecutor,
2805 cx: &mut gpui::TestAppContext,
2806) {
2807 init_test(cx, |_| {});
2808
2809 let mut cx = EditorTestContext::new(cx).await;
2810
2811 let diff_base = r#"
2812 Line 0
2813 Line 1
2814 Line 2
2815 Line 3
2816 "#
2817 .unindent();
2818
2819 cx.set_state(
2820 &r#"
2821 ˇLine 0
2822 Line 1
2823 Line 2
2824 Line 3
2825 "#
2826 .unindent(),
2827 );
2828
2829 cx.set_diff_base(Some(&diff_base));
2830 executor.run_until_parked();
2831
2832 // Join lines
2833 cx.update_editor(|editor, cx| {
2834 editor.join_lines(&JoinLines, cx);
2835 });
2836 executor.run_until_parked();
2837
2838 cx.assert_editor_state(
2839 &r#"
2840 Line 0ˇ Line 1
2841 Line 2
2842 Line 3
2843 "#
2844 .unindent(),
2845 );
2846 // Join again
2847 cx.update_editor(|editor, cx| {
2848 editor.join_lines(&JoinLines, cx);
2849 });
2850 executor.run_until_parked();
2851
2852 cx.assert_editor_state(
2853 &r#"
2854 Line 0 Line 1ˇ Line 2
2855 Line 3
2856 "#
2857 .unindent(),
2858 );
2859}
2860
2861#[gpui::test]
2862async fn test_custom_newlines_cause_no_false_positive_diffs(
2863 executor: BackgroundExecutor,
2864 cx: &mut gpui::TestAppContext,
2865) {
2866 init_test(cx, |_| {});
2867 let mut cx = EditorTestContext::new(cx).await;
2868 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
2869 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
2870 executor.run_until_parked();
2871
2872 cx.update_editor(|editor, cx| {
2873 assert_eq!(
2874 editor
2875 .buffer()
2876 .read(cx)
2877 .snapshot(cx)
2878 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
2879 .collect::<Vec<_>>(),
2880 Vec::new(),
2881 "Should not have any diffs for files with custom newlines"
2882 );
2883 });
2884}
2885
2886#[gpui::test]
2887async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let mut cx = EditorTestContext::new(cx).await;
2891
2892 // Test sort_lines_case_insensitive()
2893 cx.set_state(indoc! {"
2894 «z
2895 y
2896 x
2897 Z
2898 Y
2899 Xˇ»
2900 "});
2901 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
2902 cx.assert_editor_state(indoc! {"
2903 «x
2904 X
2905 y
2906 Y
2907 z
2908 Zˇ»
2909 "});
2910
2911 // Test reverse_lines()
2912 cx.set_state(indoc! {"
2913 «5
2914 4
2915 3
2916 2
2917 1ˇ»
2918 "});
2919 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
2920 cx.assert_editor_state(indoc! {"
2921 «1
2922 2
2923 3
2924 4
2925 5ˇ»
2926 "});
2927
2928 // Skip testing shuffle_line()
2929
2930 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
2931 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
2932
2933 // Don't manipulate when cursor is on single line, but expand the selection
2934 cx.set_state(indoc! {"
2935 ddˇdd
2936 ccc
2937 bb
2938 a
2939 "});
2940 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2941 cx.assert_editor_state(indoc! {"
2942 «ddddˇ»
2943 ccc
2944 bb
2945 a
2946 "});
2947
2948 // Basic manipulate case
2949 // Start selection moves to column 0
2950 // End of selection shrinks to fit shorter line
2951 cx.set_state(indoc! {"
2952 dd«d
2953 ccc
2954 bb
2955 aaaaaˇ»
2956 "});
2957 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2958 cx.assert_editor_state(indoc! {"
2959 «aaaaa
2960 bb
2961 ccc
2962 dddˇ»
2963 "});
2964
2965 // Manipulate case with newlines
2966 cx.set_state(indoc! {"
2967 dd«d
2968 ccc
2969
2970 bb
2971 aaaaa
2972
2973 ˇ»
2974 "});
2975 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2976 cx.assert_editor_state(indoc! {"
2977 «
2978
2979 aaaaa
2980 bb
2981 ccc
2982 dddˇ»
2983
2984 "});
2985
2986 // Adding new line
2987 cx.set_state(indoc! {"
2988 aa«a
2989 bbˇ»b
2990 "});
2991 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
2992 cx.assert_editor_state(indoc! {"
2993 «aaa
2994 bbb
2995 added_lineˇ»
2996 "});
2997
2998 // Removing line
2999 cx.set_state(indoc! {"
3000 aa«a
3001 bbbˇ»
3002 "});
3003 cx.update_editor(|e, cx| {
3004 e.manipulate_lines(cx, |lines| {
3005 lines.pop();
3006 })
3007 });
3008 cx.assert_editor_state(indoc! {"
3009 «aaaˇ»
3010 "});
3011
3012 // Removing all lines
3013 cx.set_state(indoc! {"
3014 aa«a
3015 bbbˇ»
3016 "});
3017 cx.update_editor(|e, cx| {
3018 e.manipulate_lines(cx, |lines| {
3019 lines.drain(..);
3020 })
3021 });
3022 cx.assert_editor_state(indoc! {"
3023 ˇ
3024 "});
3025}
3026
3027#[gpui::test]
3028async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3029 init_test(cx, |_| {});
3030
3031 let mut cx = EditorTestContext::new(cx).await;
3032
3033 // Consider continuous selection as single selection
3034 cx.set_state(indoc! {"
3035 Aaa«aa
3036 cˇ»c«c
3037 bb
3038 aaaˇ»aa
3039 "});
3040 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3041 cx.assert_editor_state(indoc! {"
3042 «Aaaaa
3043 ccc
3044 bb
3045 aaaaaˇ»
3046 "});
3047
3048 cx.set_state(indoc! {"
3049 Aaa«aa
3050 cˇ»c«c
3051 bb
3052 aaaˇ»aa
3053 "});
3054 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3055 cx.assert_editor_state(indoc! {"
3056 «Aaaaa
3057 ccc
3058 bbˇ»
3059 "});
3060
3061 // Consider non continuous selection as distinct dedup operations
3062 cx.set_state(indoc! {"
3063 «aaaaa
3064 bb
3065 aaaaa
3066 aaaaaˇ»
3067
3068 aaa«aaˇ»
3069 "});
3070 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3071 cx.assert_editor_state(indoc! {"
3072 «aaaaa
3073 bbˇ»
3074
3075 «aaaaaˇ»
3076 "});
3077}
3078
3079#[gpui::test]
3080async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3081 init_test(cx, |_| {});
3082
3083 let mut cx = EditorTestContext::new(cx).await;
3084
3085 cx.set_state(indoc! {"
3086 «Aaa
3087 aAa
3088 Aaaˇ»
3089 "});
3090 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3091 cx.assert_editor_state(indoc! {"
3092 «Aaa
3093 aAaˇ»
3094 "});
3095
3096 cx.set_state(indoc! {"
3097 «Aaa
3098 aAa
3099 aaAˇ»
3100 "});
3101 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3102 cx.assert_editor_state(indoc! {"
3103 «Aaaˇ»
3104 "});
3105}
3106
3107#[gpui::test]
3108async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3109 init_test(cx, |_| {});
3110
3111 let mut cx = EditorTestContext::new(cx).await;
3112
3113 // Manipulate with multiple selections on a single line
3114 cx.set_state(indoc! {"
3115 dd«dd
3116 cˇ»c«c
3117 bb
3118 aaaˇ»aa
3119 "});
3120 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3121 cx.assert_editor_state(indoc! {"
3122 «aaaaa
3123 bb
3124 ccc
3125 ddddˇ»
3126 "});
3127
3128 // Manipulate with multiple disjoin selections
3129 cx.set_state(indoc! {"
3130 5«
3131 4
3132 3
3133 2
3134 1ˇ»
3135
3136 dd«dd
3137 ccc
3138 bb
3139 aaaˇ»aa
3140 "});
3141 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3142 cx.assert_editor_state(indoc! {"
3143 «1
3144 2
3145 3
3146 4
3147 5ˇ»
3148
3149 «aaaaa
3150 bb
3151 ccc
3152 ddddˇ»
3153 "});
3154
3155 // Adding lines on each selection
3156 cx.set_state(indoc! {"
3157 2«
3158 1ˇ»
3159
3160 bb«bb
3161 aaaˇ»aa
3162 "});
3163 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3164 cx.assert_editor_state(indoc! {"
3165 «2
3166 1
3167 added lineˇ»
3168
3169 «bbbb
3170 aaaaa
3171 added lineˇ»
3172 "});
3173
3174 // Removing lines on each selection
3175 cx.set_state(indoc! {"
3176 2«
3177 1ˇ»
3178
3179 bb«bb
3180 aaaˇ»aa
3181 "});
3182 cx.update_editor(|e, cx| {
3183 e.manipulate_lines(cx, |lines| {
3184 lines.pop();
3185 })
3186 });
3187 cx.assert_editor_state(indoc! {"
3188 «2ˇ»
3189
3190 «bbbbˇ»
3191 "});
3192}
3193
3194#[gpui::test]
3195async fn test_manipulate_text(cx: &mut TestAppContext) {
3196 init_test(cx, |_| {});
3197
3198 let mut cx = EditorTestContext::new(cx).await;
3199
3200 // Test convert_to_upper_case()
3201 cx.set_state(indoc! {"
3202 «hello worldˇ»
3203 "});
3204 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3205 cx.assert_editor_state(indoc! {"
3206 «HELLO WORLDˇ»
3207 "});
3208
3209 // Test convert_to_lower_case()
3210 cx.set_state(indoc! {"
3211 «HELLO WORLDˇ»
3212 "});
3213 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3214 cx.assert_editor_state(indoc! {"
3215 «hello worldˇ»
3216 "});
3217
3218 // Test multiple line, single selection case
3219 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3220 cx.set_state(indoc! {"
3221 «The quick brown
3222 fox jumps over
3223 the lazy dogˇ»
3224 "});
3225 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3226 cx.assert_editor_state(indoc! {"
3227 «The Quick Brown
3228 Fox Jumps Over
3229 The Lazy Dogˇ»
3230 "});
3231
3232 // Test multiple line, single selection case
3233 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3234 cx.set_state(indoc! {"
3235 «The quick brown
3236 fox jumps over
3237 the lazy dogˇ»
3238 "});
3239 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3240 cx.assert_editor_state(indoc! {"
3241 «TheQuickBrown
3242 FoxJumpsOver
3243 TheLazyDogˇ»
3244 "});
3245
3246 // From here on out, test more complex cases of manipulate_text()
3247
3248 // Test no selection case - should affect words cursors are in
3249 // Cursor at beginning, middle, and end of word
3250 cx.set_state(indoc! {"
3251 ˇhello big beauˇtiful worldˇ
3252 "});
3253 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3254 cx.assert_editor_state(indoc! {"
3255 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3256 "});
3257
3258 // Test multiple selections on a single line and across multiple lines
3259 cx.set_state(indoc! {"
3260 «Theˇ» quick «brown
3261 foxˇ» jumps «overˇ»
3262 the «lazyˇ» dog
3263 "});
3264 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3265 cx.assert_editor_state(indoc! {"
3266 «THEˇ» quick «BROWN
3267 FOXˇ» jumps «OVERˇ»
3268 the «LAZYˇ» dog
3269 "});
3270
3271 // Test case where text length grows
3272 cx.set_state(indoc! {"
3273 «tschüߡ»
3274 "});
3275 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3276 cx.assert_editor_state(indoc! {"
3277 «TSCHÜSSˇ»
3278 "});
3279
3280 // Test to make sure we don't crash when text shrinks
3281 cx.set_state(indoc! {"
3282 aaa_bbbˇ
3283 "});
3284 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3285 cx.assert_editor_state(indoc! {"
3286 «aaaBbbˇ»
3287 "});
3288
3289 // Test to make sure we all aware of the fact that each word can grow and shrink
3290 // Final selections should be aware of this fact
3291 cx.set_state(indoc! {"
3292 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3293 "});
3294 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3295 cx.assert_editor_state(indoc! {"
3296 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3297 "});
3298
3299 cx.set_state(indoc! {"
3300 «hElLo, WoRld!ˇ»
3301 "});
3302 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3303 cx.assert_editor_state(indoc! {"
3304 «HeLlO, wOrLD!ˇ»
3305 "});
3306}
3307
3308#[gpui::test]
3309fn test_duplicate_line(cx: &mut TestAppContext) {
3310 init_test(cx, |_| {});
3311
3312 let view = cx.add_window(|cx| {
3313 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3314 build_editor(buffer, cx)
3315 });
3316 _ = view.update(cx, |view, cx| {
3317 view.change_selections(None, cx, |s| {
3318 s.select_display_ranges([
3319 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3320 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3322 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3323 ])
3324 });
3325 view.duplicate_line_down(&DuplicateLineDown, cx);
3326 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3327 assert_eq!(
3328 view.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3331 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3332 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3333 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3334 ]
3335 );
3336 });
3337
3338 let view = cx.add_window(|cx| {
3339 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3340 build_editor(buffer, cx)
3341 });
3342 _ = view.update(cx, |view, cx| {
3343 view.change_selections(None, cx, |s| {
3344 s.select_display_ranges([
3345 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3346 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3347 ])
3348 });
3349 view.duplicate_line_down(&DuplicateLineDown, cx);
3350 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3351 assert_eq!(
3352 view.selections.display_ranges(cx),
3353 vec![
3354 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3355 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3356 ]
3357 );
3358 });
3359
3360 // With `move_upwards` the selections stay in place, except for
3361 // the lines inserted above them
3362 let view = cx.add_window(|cx| {
3363 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3364 build_editor(buffer, cx)
3365 });
3366 _ = view.update(cx, |view, cx| {
3367 view.change_selections(None, cx, |s| {
3368 s.select_display_ranges([
3369 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3370 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3372 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3373 ])
3374 });
3375 view.duplicate_line_up(&DuplicateLineUp, cx);
3376 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3377 assert_eq!(
3378 view.selections.display_ranges(cx),
3379 vec![
3380 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3381 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3382 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3383 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3384 ]
3385 );
3386 });
3387
3388 let view = cx.add_window(|cx| {
3389 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3390 build_editor(buffer, cx)
3391 });
3392 _ = view.update(cx, |view, cx| {
3393 view.change_selections(None, cx, |s| {
3394 s.select_display_ranges([
3395 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3396 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3397 ])
3398 });
3399 view.duplicate_line_up(&DuplicateLineUp, cx);
3400 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3401 assert_eq!(
3402 view.selections.display_ranges(cx),
3403 vec![
3404 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3405 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3406 ]
3407 );
3408 });
3409}
3410
3411#[gpui::test]
3412fn test_move_line_up_down(cx: &mut TestAppContext) {
3413 init_test(cx, |_| {});
3414
3415 let view = cx.add_window(|cx| {
3416 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3417 build_editor(buffer, cx)
3418 });
3419 _ = view.update(cx, |view, cx| {
3420 view.fold_ranges(
3421 vec![
3422 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3423 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3424 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3425 ],
3426 true,
3427 cx,
3428 );
3429 view.change_selections(None, cx, |s| {
3430 s.select_display_ranges([
3431 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3432 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3433 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3434 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3435 ])
3436 });
3437 assert_eq!(
3438 view.display_text(cx),
3439 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3440 );
3441
3442 view.move_line_up(&MoveLineUp, cx);
3443 assert_eq!(
3444 view.display_text(cx),
3445 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3446 );
3447 assert_eq!(
3448 view.selections.display_ranges(cx),
3449 vec![
3450 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3451 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3452 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3453 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3454 ]
3455 );
3456 });
3457
3458 _ = view.update(cx, |view, cx| {
3459 view.move_line_down(&MoveLineDown, cx);
3460 assert_eq!(
3461 view.display_text(cx),
3462 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3463 );
3464 assert_eq!(
3465 view.selections.display_ranges(cx),
3466 vec![
3467 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3468 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3469 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3470 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3471 ]
3472 );
3473 });
3474
3475 _ = view.update(cx, |view, cx| {
3476 view.move_line_down(&MoveLineDown, cx);
3477 assert_eq!(
3478 view.display_text(cx),
3479 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3480 );
3481 assert_eq!(
3482 view.selections.display_ranges(cx),
3483 vec![
3484 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3485 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3486 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3487 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3488 ]
3489 );
3490 });
3491
3492 _ = view.update(cx, |view, cx| {
3493 view.move_line_up(&MoveLineUp, cx);
3494 assert_eq!(
3495 view.display_text(cx),
3496 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3497 );
3498 assert_eq!(
3499 view.selections.display_ranges(cx),
3500 vec![
3501 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3502 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3503 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3504 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3505 ]
3506 );
3507 });
3508}
3509
3510#[gpui::test]
3511fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3512 init_test(cx, |_| {});
3513
3514 let editor = cx.add_window(|cx| {
3515 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3516 build_editor(buffer, cx)
3517 });
3518 _ = editor.update(cx, |editor, cx| {
3519 let snapshot = editor.buffer.read(cx).snapshot(cx);
3520 editor.insert_blocks(
3521 [BlockProperties {
3522 style: BlockStyle::Fixed,
3523 position: snapshot.anchor_after(Point::new(2, 0)),
3524 disposition: BlockDisposition::Below,
3525 height: 1,
3526 render: Box::new(|_| div().into_any()),
3527 }],
3528 Some(Autoscroll::fit()),
3529 cx,
3530 );
3531 editor.change_selections(None, cx, |s| {
3532 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3533 });
3534 editor.move_line_down(&MoveLineDown, cx);
3535 });
3536}
3537
3538#[gpui::test]
3539fn test_transpose(cx: &mut TestAppContext) {
3540 init_test(cx, |_| {});
3541
3542 _ = cx.add_window(|cx| {
3543 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3544 editor.set_style(EditorStyle::default(), cx);
3545 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3546 editor.transpose(&Default::default(), cx);
3547 assert_eq!(editor.text(cx), "bac");
3548 assert_eq!(editor.selections.ranges(cx), [2..2]);
3549
3550 editor.transpose(&Default::default(), cx);
3551 assert_eq!(editor.text(cx), "bca");
3552 assert_eq!(editor.selections.ranges(cx), [3..3]);
3553
3554 editor.transpose(&Default::default(), cx);
3555 assert_eq!(editor.text(cx), "bac");
3556 assert_eq!(editor.selections.ranges(cx), [3..3]);
3557
3558 editor
3559 });
3560
3561 _ = cx.add_window(|cx| {
3562 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3563 editor.set_style(EditorStyle::default(), cx);
3564 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3565 editor.transpose(&Default::default(), cx);
3566 assert_eq!(editor.text(cx), "acb\nde");
3567 assert_eq!(editor.selections.ranges(cx), [3..3]);
3568
3569 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3570 editor.transpose(&Default::default(), cx);
3571 assert_eq!(editor.text(cx), "acbd\ne");
3572 assert_eq!(editor.selections.ranges(cx), [5..5]);
3573
3574 editor.transpose(&Default::default(), cx);
3575 assert_eq!(editor.text(cx), "acbde\n");
3576 assert_eq!(editor.selections.ranges(cx), [6..6]);
3577
3578 editor.transpose(&Default::default(), cx);
3579 assert_eq!(editor.text(cx), "acbd\ne");
3580 assert_eq!(editor.selections.ranges(cx), [6..6]);
3581
3582 editor
3583 });
3584
3585 _ = cx.add_window(|cx| {
3586 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3587 editor.set_style(EditorStyle::default(), cx);
3588 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3589 editor.transpose(&Default::default(), cx);
3590 assert_eq!(editor.text(cx), "bacd\ne");
3591 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3592
3593 editor.transpose(&Default::default(), cx);
3594 assert_eq!(editor.text(cx), "bcade\n");
3595 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3596
3597 editor.transpose(&Default::default(), cx);
3598 assert_eq!(editor.text(cx), "bcda\ne");
3599 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3600
3601 editor.transpose(&Default::default(), cx);
3602 assert_eq!(editor.text(cx), "bcade\n");
3603 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3604
3605 editor.transpose(&Default::default(), cx);
3606 assert_eq!(editor.text(cx), "bcaed\n");
3607 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3608
3609 editor
3610 });
3611
3612 _ = cx.add_window(|cx| {
3613 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3614 editor.set_style(EditorStyle::default(), cx);
3615 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3616 editor.transpose(&Default::default(), cx);
3617 assert_eq!(editor.text(cx), "🏀🍐✋");
3618 assert_eq!(editor.selections.ranges(cx), [8..8]);
3619
3620 editor.transpose(&Default::default(), cx);
3621 assert_eq!(editor.text(cx), "🏀✋🍐");
3622 assert_eq!(editor.selections.ranges(cx), [11..11]);
3623
3624 editor.transpose(&Default::default(), cx);
3625 assert_eq!(editor.text(cx), "🏀🍐✋");
3626 assert_eq!(editor.selections.ranges(cx), [11..11]);
3627
3628 editor
3629 });
3630}
3631
3632#[gpui::test]
3633async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3634 init_test(cx, |_| {});
3635
3636 let mut cx = EditorTestContext::new(cx).await;
3637
3638 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3639 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3640 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3641
3642 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3643 cx.set_state("two ˇfour ˇsix ˇ");
3644 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3645 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3646
3647 // Paste again but with only two cursors. Since the number of cursors doesn't
3648 // match the number of slices in the clipboard, the entire clipboard text
3649 // is pasted at each cursor.
3650 cx.set_state("ˇtwo one✅ four three six five ˇ");
3651 cx.update_editor(|e, cx| {
3652 e.handle_input("( ", cx);
3653 e.paste(&Paste, cx);
3654 e.handle_input(") ", cx);
3655 });
3656 cx.assert_editor_state(
3657 &([
3658 "( one✅ ",
3659 "three ",
3660 "five ) ˇtwo one✅ four three six five ( one✅ ",
3661 "three ",
3662 "five ) ˇ",
3663 ]
3664 .join("\n")),
3665 );
3666
3667 // Cut with three selections, one of which is full-line.
3668 cx.set_state(indoc! {"
3669 1«2ˇ»3
3670 4ˇ567
3671 «8ˇ»9"});
3672 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3673 cx.assert_editor_state(indoc! {"
3674 1ˇ3
3675 ˇ9"});
3676
3677 // Paste with three selections, noticing how the copied selection that was full-line
3678 // gets inserted before the second cursor.
3679 cx.set_state(indoc! {"
3680 1ˇ3
3681 9ˇ
3682 «oˇ»ne"});
3683 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3684 cx.assert_editor_state(indoc! {"
3685 12ˇ3
3686 4567
3687 9ˇ
3688 8ˇne"});
3689
3690 // Copy with a single cursor only, which writes the whole line into the clipboard.
3691 cx.set_state(indoc! {"
3692 The quick brown
3693 fox juˇmps over
3694 the lazy dog"});
3695 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3696 assert_eq!(
3697 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3698 Some("fox jumps over\n".to_owned())
3699 );
3700
3701 // Paste with three selections, noticing how the copied full-line selection is inserted
3702 // before the empty selections but replaces the selection that is non-empty.
3703 cx.set_state(indoc! {"
3704 Tˇhe quick brown
3705 «foˇ»x jumps over
3706 tˇhe lazy dog"});
3707 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3708 cx.assert_editor_state(indoc! {"
3709 fox jumps over
3710 Tˇhe quick brown
3711 fox jumps over
3712 ˇx jumps over
3713 fox jumps over
3714 tˇhe lazy dog"});
3715}
3716
3717#[gpui::test]
3718async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3719 init_test(cx, |_| {});
3720
3721 let mut cx = EditorTestContext::new(cx).await;
3722 let language = Arc::new(Language::new(
3723 LanguageConfig::default(),
3724 Some(tree_sitter_rust::language()),
3725 ));
3726 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3727
3728 // Cut an indented block, without the leading whitespace.
3729 cx.set_state(indoc! {"
3730 const a: B = (
3731 c(),
3732 «d(
3733 e,
3734 f
3735 )ˇ»
3736 );
3737 "});
3738 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3739 cx.assert_editor_state(indoc! {"
3740 const a: B = (
3741 c(),
3742 ˇ
3743 );
3744 "});
3745
3746 // Paste it at the same position.
3747 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3748 cx.assert_editor_state(indoc! {"
3749 const a: B = (
3750 c(),
3751 d(
3752 e,
3753 f
3754 )ˇ
3755 );
3756 "});
3757
3758 // Paste it at a line with a lower indent level.
3759 cx.set_state(indoc! {"
3760 ˇ
3761 const a: B = (
3762 c(),
3763 );
3764 "});
3765 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3766 cx.assert_editor_state(indoc! {"
3767 d(
3768 e,
3769 f
3770 )ˇ
3771 const a: B = (
3772 c(),
3773 );
3774 "});
3775
3776 // Cut an indented block, with the leading whitespace.
3777 cx.set_state(indoc! {"
3778 const a: B = (
3779 c(),
3780 « d(
3781 e,
3782 f
3783 )
3784 ˇ»);
3785 "});
3786 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3787 cx.assert_editor_state(indoc! {"
3788 const a: B = (
3789 c(),
3790 ˇ);
3791 "});
3792
3793 // Paste it at the same position.
3794 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3795 cx.assert_editor_state(indoc! {"
3796 const a: B = (
3797 c(),
3798 d(
3799 e,
3800 f
3801 )
3802 ˇ);
3803 "});
3804
3805 // Paste it at a line with a higher indent level.
3806 cx.set_state(indoc! {"
3807 const a: B = (
3808 c(),
3809 d(
3810 e,
3811 fˇ
3812 )
3813 );
3814 "});
3815 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3816 cx.assert_editor_state(indoc! {"
3817 const a: B = (
3818 c(),
3819 d(
3820 e,
3821 f d(
3822 e,
3823 f
3824 )
3825 ˇ
3826 )
3827 );
3828 "});
3829}
3830
3831#[gpui::test]
3832fn test_select_all(cx: &mut TestAppContext) {
3833 init_test(cx, |_| {});
3834
3835 let view = cx.add_window(|cx| {
3836 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3837 build_editor(buffer, cx)
3838 });
3839 _ = view.update(cx, |view, cx| {
3840 view.select_all(&SelectAll, cx);
3841 assert_eq!(
3842 view.selections.display_ranges(cx),
3843 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
3844 );
3845 });
3846}
3847
3848#[gpui::test]
3849fn test_select_line(cx: &mut TestAppContext) {
3850 init_test(cx, |_| {});
3851
3852 let view = cx.add_window(|cx| {
3853 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3854 build_editor(buffer, cx)
3855 });
3856 _ = view.update(cx, |view, cx| {
3857 view.change_selections(None, cx, |s| {
3858 s.select_display_ranges([
3859 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3860 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3861 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3862 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
3863 ])
3864 });
3865 view.select_line(&SelectLine, cx);
3866 assert_eq!(
3867 view.selections.display_ranges(cx),
3868 vec![
3869 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
3871 ]
3872 );
3873 });
3874
3875 _ = view.update(cx, |view, cx| {
3876 view.select_line(&SelectLine, cx);
3877 assert_eq!(
3878 view.selections.display_ranges(cx),
3879 vec![
3880 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
3881 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
3882 ]
3883 );
3884 });
3885
3886 _ = view.update(cx, |view, cx| {
3887 view.select_line(&SelectLine, cx);
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
3891 );
3892 });
3893}
3894
3895#[gpui::test]
3896fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3897 init_test(cx, |_| {});
3898
3899 let view = cx.add_window(|cx| {
3900 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3901 build_editor(buffer, cx)
3902 });
3903 _ = view.update(cx, |view, cx| {
3904 view.fold_ranges(
3905 vec![
3906 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3907 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3908 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3909 ],
3910 true,
3911 cx,
3912 );
3913 view.change_selections(None, cx, |s| {
3914 s.select_display_ranges([
3915 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3916 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3917 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3918 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
3919 ])
3920 });
3921 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3922 });
3923
3924 _ = view.update(cx, |view, cx| {
3925 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3926 assert_eq!(
3927 view.display_text(cx),
3928 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3929 );
3930 assert_eq!(
3931 view.selections.display_ranges(cx),
3932 [
3933 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3934 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3935 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3936 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
3937 ]
3938 );
3939 });
3940
3941 _ = view.update(cx, |view, cx| {
3942 view.change_selections(None, cx, |s| {
3943 s.select_display_ranges([
3944 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
3945 ])
3946 });
3947 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3948 assert_eq!(
3949 view.display_text(cx),
3950 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3951 );
3952 assert_eq!(
3953 view.selections.display_ranges(cx),
3954 [
3955 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
3956 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
3957 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
3958 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
3959 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
3960 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
3961 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
3962 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
3963 ]
3964 );
3965 });
3966}
3967
3968#[gpui::test]
3969async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3970 init_test(cx, |_| {});
3971
3972 let mut cx = EditorTestContext::new(cx).await;
3973
3974 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3975 cx.set_state(indoc!(
3976 r#"abc
3977 defˇghi
3978
3979 jk
3980 nlmo
3981 "#
3982 ));
3983
3984 cx.update_editor(|editor, cx| {
3985 editor.add_selection_above(&Default::default(), cx);
3986 });
3987
3988 cx.assert_editor_state(indoc!(
3989 r#"abcˇ
3990 defˇghi
3991
3992 jk
3993 nlmo
3994 "#
3995 ));
3996
3997 cx.update_editor(|editor, cx| {
3998 editor.add_selection_above(&Default::default(), cx);
3999 });
4000
4001 cx.assert_editor_state(indoc!(
4002 r#"abcˇ
4003 defˇghi
4004
4005 jk
4006 nlmo
4007 "#
4008 ));
4009
4010 cx.update_editor(|view, cx| {
4011 view.add_selection_below(&Default::default(), cx);
4012 });
4013
4014 cx.assert_editor_state(indoc!(
4015 r#"abc
4016 defˇghi
4017
4018 jk
4019 nlmo
4020 "#
4021 ));
4022
4023 cx.update_editor(|view, cx| {
4024 view.undo_selection(&Default::default(), cx);
4025 });
4026
4027 cx.assert_editor_state(indoc!(
4028 r#"abcˇ
4029 defˇghi
4030
4031 jk
4032 nlmo
4033 "#
4034 ));
4035
4036 cx.update_editor(|view, cx| {
4037 view.redo_selection(&Default::default(), cx);
4038 });
4039
4040 cx.assert_editor_state(indoc!(
4041 r#"abc
4042 defˇghi
4043
4044 jk
4045 nlmo
4046 "#
4047 ));
4048
4049 cx.update_editor(|view, cx| {
4050 view.add_selection_below(&Default::default(), cx);
4051 });
4052
4053 cx.assert_editor_state(indoc!(
4054 r#"abc
4055 defˇghi
4056
4057 jk
4058 nlmˇo
4059 "#
4060 ));
4061
4062 cx.update_editor(|view, cx| {
4063 view.add_selection_below(&Default::default(), cx);
4064 });
4065
4066 cx.assert_editor_state(indoc!(
4067 r#"abc
4068 defˇghi
4069
4070 jk
4071 nlmˇo
4072 "#
4073 ));
4074
4075 // change selections
4076 cx.set_state(indoc!(
4077 r#"abc
4078 def«ˇg»hi
4079
4080 jk
4081 nlmo
4082 "#
4083 ));
4084
4085 cx.update_editor(|view, cx| {
4086 view.add_selection_below(&Default::default(), cx);
4087 });
4088
4089 cx.assert_editor_state(indoc!(
4090 r#"abc
4091 def«ˇg»hi
4092
4093 jk
4094 nlm«ˇo»
4095 "#
4096 ));
4097
4098 cx.update_editor(|view, cx| {
4099 view.add_selection_below(&Default::default(), cx);
4100 });
4101
4102 cx.assert_editor_state(indoc!(
4103 r#"abc
4104 def«ˇg»hi
4105
4106 jk
4107 nlm«ˇo»
4108 "#
4109 ));
4110
4111 cx.update_editor(|view, cx| {
4112 view.add_selection_above(&Default::default(), cx);
4113 });
4114
4115 cx.assert_editor_state(indoc!(
4116 r#"abc
4117 def«ˇg»hi
4118
4119 jk
4120 nlmo
4121 "#
4122 ));
4123
4124 cx.update_editor(|view, cx| {
4125 view.add_selection_above(&Default::default(), cx);
4126 });
4127
4128 cx.assert_editor_state(indoc!(
4129 r#"abc
4130 def«ˇg»hi
4131
4132 jk
4133 nlmo
4134 "#
4135 ));
4136
4137 // Change selections again
4138 cx.set_state(indoc!(
4139 r#"a«bc
4140 defgˇ»hi
4141
4142 jk
4143 nlmo
4144 "#
4145 ));
4146
4147 cx.update_editor(|view, cx| {
4148 view.add_selection_below(&Default::default(), cx);
4149 });
4150
4151 cx.assert_editor_state(indoc!(
4152 r#"a«bcˇ»
4153 d«efgˇ»hi
4154
4155 j«kˇ»
4156 nlmo
4157 "#
4158 ));
4159
4160 cx.update_editor(|view, cx| {
4161 view.add_selection_below(&Default::default(), cx);
4162 });
4163 cx.assert_editor_state(indoc!(
4164 r#"a«bcˇ»
4165 d«efgˇ»hi
4166
4167 j«kˇ»
4168 n«lmoˇ»
4169 "#
4170 ));
4171 cx.update_editor(|view, cx| {
4172 view.add_selection_above(&Default::default(), cx);
4173 });
4174
4175 cx.assert_editor_state(indoc!(
4176 r#"a«bcˇ»
4177 d«efgˇ»hi
4178
4179 j«kˇ»
4180 nlmo
4181 "#
4182 ));
4183
4184 // Change selections again
4185 cx.set_state(indoc!(
4186 r#"abc
4187 d«ˇefghi
4188
4189 jk
4190 nlm»o
4191 "#
4192 ));
4193
4194 cx.update_editor(|view, cx| {
4195 view.add_selection_above(&Default::default(), cx);
4196 });
4197
4198 cx.assert_editor_state(indoc!(
4199 r#"a«ˇbc»
4200 d«ˇef»ghi
4201
4202 j«ˇk»
4203 n«ˇlm»o
4204 "#
4205 ));
4206
4207 cx.update_editor(|view, cx| {
4208 view.add_selection_below(&Default::default(), cx);
4209 });
4210
4211 cx.assert_editor_state(indoc!(
4212 r#"abc
4213 d«ˇef»ghi
4214
4215 j«ˇk»
4216 n«ˇlm»o
4217 "#
4218 ));
4219}
4220
4221#[gpui::test]
4222async fn test_select_next(cx: &mut gpui::TestAppContext) {
4223 init_test(cx, |_| {});
4224
4225 let mut cx = EditorTestContext::new(cx).await;
4226 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4227
4228 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4229 .unwrap();
4230 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4231
4232 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4233 .unwrap();
4234 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4235
4236 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4237 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4238
4239 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4240 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4241
4242 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4243 .unwrap();
4244 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4245
4246 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4247 .unwrap();
4248 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4249}
4250
4251#[gpui::test]
4252async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4253 init_test(cx, |_| {});
4254
4255 let mut cx = EditorTestContext::new(cx).await;
4256 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4257
4258 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4259 .unwrap();
4260 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4261}
4262
4263#[gpui::test]
4264async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4265 init_test(cx, |_| {});
4266
4267 let mut cx = EditorTestContext::new(cx).await;
4268 cx.set_state(
4269 r#"let foo = 2;
4270lˇet foo = 2;
4271let fooˇ = 2;
4272let foo = 2;
4273let foo = ˇ2;"#,
4274 );
4275
4276 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4277 .unwrap();
4278 cx.assert_editor_state(
4279 r#"let foo = 2;
4280«letˇ» foo = 2;
4281let «fooˇ» = 2;
4282let foo = 2;
4283let foo = «2ˇ»;"#,
4284 );
4285
4286 // noop for multiple selections with different contents
4287 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4288 .unwrap();
4289 cx.assert_editor_state(
4290 r#"let foo = 2;
4291«letˇ» foo = 2;
4292let «fooˇ» = 2;
4293let foo = 2;
4294let foo = «2ˇ»;"#,
4295 );
4296}
4297
4298#[gpui::test]
4299async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4300 init_test(cx, |_| {});
4301
4302 let mut cx = EditorTestContext::new_multibuffer(
4303 cx,
4304 [
4305 &indoc! {
4306 "aaa\n«bbb\nccc\n»ddd"
4307 },
4308 &indoc! {
4309 "aaa\n«bbb\nccc\n»ddd"
4310 },
4311 ],
4312 );
4313
4314 cx.assert_editor_state(indoc! {"
4315 ˇbbb
4316 ccc
4317
4318 bbb
4319 ccc
4320 "});
4321 cx.dispatch_action(SelectPrevious::default());
4322 cx.assert_editor_state(indoc! {"
4323 «bbbˇ»
4324 ccc
4325
4326 bbb
4327 ccc
4328 "});
4329 cx.dispatch_action(SelectPrevious::default());
4330 cx.assert_editor_state(indoc! {"
4331 «bbbˇ»
4332 ccc
4333
4334 «bbbˇ»
4335 ccc
4336 "});
4337}
4338
4339#[gpui::test]
4340async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4341 init_test(cx, |_| {});
4342
4343 let mut cx = EditorTestContext::new(cx).await;
4344 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4345
4346 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4347 .unwrap();
4348 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4349
4350 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4351 .unwrap();
4352 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4353
4354 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4355 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4356
4357 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4358 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4359
4360 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4361 .unwrap();
4362 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4363
4364 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4365 .unwrap();
4366 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4367
4368 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4369 .unwrap();
4370 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4371}
4372
4373#[gpui::test]
4374async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4375 init_test(cx, |_| {});
4376
4377 let mut cx = EditorTestContext::new(cx).await;
4378 cx.set_state(
4379 r#"let foo = 2;
4380lˇet foo = 2;
4381let fooˇ = 2;
4382let foo = 2;
4383let foo = ˇ2;"#,
4384 );
4385
4386 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4387 .unwrap();
4388 cx.assert_editor_state(
4389 r#"let foo = 2;
4390«letˇ» foo = 2;
4391let «fooˇ» = 2;
4392let foo = 2;
4393let foo = «2ˇ»;"#,
4394 );
4395
4396 // noop for multiple selections with different contents
4397 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4398 .unwrap();
4399 cx.assert_editor_state(
4400 r#"let foo = 2;
4401«letˇ» foo = 2;
4402let «fooˇ» = 2;
4403let foo = 2;
4404let foo = «2ˇ»;"#,
4405 );
4406}
4407
4408#[gpui::test]
4409async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4410 init_test(cx, |_| {});
4411
4412 let mut cx = EditorTestContext::new(cx).await;
4413 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4414
4415 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4416 .unwrap();
4417 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4418
4419 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4420 .unwrap();
4421 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4422
4423 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4424 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4425
4426 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4427 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4428
4429 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4430 .unwrap();
4431 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4432
4433 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4434 .unwrap();
4435 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4436}
4437
4438#[gpui::test]
4439async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4440 init_test(cx, |_| {});
4441
4442 let language = Arc::new(Language::new(
4443 LanguageConfig::default(),
4444 Some(tree_sitter_rust::language()),
4445 ));
4446
4447 let text = r#"
4448 use mod1::mod2::{mod3, mod4};
4449
4450 fn fn_1(param1: bool, param2: &str) {
4451 let var1 = "text";
4452 }
4453 "#
4454 .unindent();
4455
4456 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4457 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4458 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4459
4460 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4461 .await;
4462
4463 _ = view.update(cx, |view, cx| {
4464 view.change_selections(None, cx, |s| {
4465 s.select_display_ranges([
4466 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4467 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4468 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4469 ]);
4470 });
4471 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4472 });
4473 assert_eq!(
4474 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4475 &[
4476 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4477 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4478 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4479 ]
4480 );
4481
4482 _ = view.update(cx, |view, cx| {
4483 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4484 });
4485 assert_eq!(
4486 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4487 &[
4488 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4489 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4490 ]
4491 );
4492
4493 _ = view.update(cx, |view, cx| {
4494 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4495 });
4496 assert_eq!(
4497 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4498 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4499 );
4500
4501 // Trying to expand the selected syntax node one more time has no effect.
4502 _ = view.update(cx, |view, cx| {
4503 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4504 });
4505 assert_eq!(
4506 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4507 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4508 );
4509
4510 _ = view.update(cx, |view, cx| {
4511 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4512 });
4513 assert_eq!(
4514 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4515 &[
4516 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4517 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4518 ]
4519 );
4520
4521 _ = view.update(cx, |view, cx| {
4522 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4523 });
4524 assert_eq!(
4525 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4526 &[
4527 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4528 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4529 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4530 ]
4531 );
4532
4533 _ = view.update(cx, |view, cx| {
4534 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4535 });
4536 assert_eq!(
4537 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4538 &[
4539 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4540 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4541 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4542 ]
4543 );
4544
4545 // Trying to shrink the selected syntax node one more time has no effect.
4546 _ = view.update(cx, |view, cx| {
4547 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4548 });
4549 assert_eq!(
4550 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4551 &[
4552 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4553 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4554 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4555 ]
4556 );
4557
4558 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4559 // a fold.
4560 _ = view.update(cx, |view, cx| {
4561 view.fold_ranges(
4562 vec![
4563 (
4564 Point::new(0, 21)..Point::new(0, 24),
4565 FoldPlaceholder::test(),
4566 ),
4567 (
4568 Point::new(3, 20)..Point::new(3, 22),
4569 FoldPlaceholder::test(),
4570 ),
4571 ],
4572 true,
4573 cx,
4574 );
4575 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4576 });
4577 assert_eq!(
4578 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4579 &[
4580 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4581 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4582 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(3), 23),
4583 ]
4584 );
4585}
4586
4587#[gpui::test]
4588async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4589 init_test(cx, |_| {});
4590
4591 let language = Arc::new(
4592 Language::new(
4593 LanguageConfig {
4594 brackets: BracketPairConfig {
4595 pairs: vec![
4596 BracketPair {
4597 start: "{".to_string(),
4598 end: "}".to_string(),
4599 close: false,
4600 newline: true,
4601 },
4602 BracketPair {
4603 start: "(".to_string(),
4604 end: ")".to_string(),
4605 close: false,
4606 newline: true,
4607 },
4608 ],
4609 ..Default::default()
4610 },
4611 ..Default::default()
4612 },
4613 Some(tree_sitter_rust::language()),
4614 )
4615 .with_indents_query(
4616 r#"
4617 (_ "(" ")" @end) @indent
4618 (_ "{" "}" @end) @indent
4619 "#,
4620 )
4621 .unwrap(),
4622 );
4623
4624 let text = "fn a() {}";
4625
4626 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4627 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4628 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4629 editor
4630 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4631 .await;
4632
4633 _ = editor.update(cx, |editor, cx| {
4634 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4635 editor.newline(&Newline, cx);
4636 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4637 assert_eq!(
4638 editor.selections.ranges(cx),
4639 &[
4640 Point::new(1, 4)..Point::new(1, 4),
4641 Point::new(3, 4)..Point::new(3, 4),
4642 Point::new(5, 0)..Point::new(5, 0)
4643 ]
4644 );
4645 });
4646}
4647
4648#[gpui::test]
4649async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4650 init_test(cx, |_| {});
4651
4652 let mut cx = EditorTestContext::new(cx).await;
4653
4654 let language = Arc::new(Language::new(
4655 LanguageConfig {
4656 brackets: BracketPairConfig {
4657 pairs: vec![
4658 BracketPair {
4659 start: "{".to_string(),
4660 end: "}".to_string(),
4661 close: true,
4662 newline: true,
4663 },
4664 BracketPair {
4665 start: "(".to_string(),
4666 end: ")".to_string(),
4667 close: true,
4668 newline: true,
4669 },
4670 BracketPair {
4671 start: "/*".to_string(),
4672 end: " */".to_string(),
4673 close: true,
4674 newline: true,
4675 },
4676 BracketPair {
4677 start: "[".to_string(),
4678 end: "]".to_string(),
4679 close: false,
4680 newline: true,
4681 },
4682 BracketPair {
4683 start: "\"".to_string(),
4684 end: "\"".to_string(),
4685 close: true,
4686 newline: false,
4687 },
4688 ],
4689 ..Default::default()
4690 },
4691 autoclose_before: "})]".to_string(),
4692 ..Default::default()
4693 },
4694 Some(tree_sitter_rust::language()),
4695 ));
4696
4697 cx.language_registry().add(language.clone());
4698 cx.update_buffer(|buffer, cx| {
4699 buffer.set_language(Some(language), cx);
4700 });
4701
4702 cx.set_state(
4703 &r#"
4704 🏀ˇ
4705 εˇ
4706 ❤️ˇ
4707 "#
4708 .unindent(),
4709 );
4710
4711 // autoclose multiple nested brackets at multiple cursors
4712 cx.update_editor(|view, cx| {
4713 view.handle_input("{", cx);
4714 view.handle_input("{", cx);
4715 view.handle_input("{", cx);
4716 });
4717 cx.assert_editor_state(
4718 &"
4719 🏀{{{ˇ}}}
4720 ε{{{ˇ}}}
4721 ❤️{{{ˇ}}}
4722 "
4723 .unindent(),
4724 );
4725
4726 // insert a different closing bracket
4727 cx.update_editor(|view, cx| {
4728 view.handle_input(")", cx);
4729 });
4730 cx.assert_editor_state(
4731 &"
4732 🏀{{{)ˇ}}}
4733 ε{{{)ˇ}}}
4734 ❤️{{{)ˇ}}}
4735 "
4736 .unindent(),
4737 );
4738
4739 // skip over the auto-closed brackets when typing a closing bracket
4740 cx.update_editor(|view, cx| {
4741 view.move_right(&MoveRight, cx);
4742 view.handle_input("}", cx);
4743 view.handle_input("}", cx);
4744 view.handle_input("}", cx);
4745 });
4746 cx.assert_editor_state(
4747 &"
4748 🏀{{{)}}}}ˇ
4749 ε{{{)}}}}ˇ
4750 ❤️{{{)}}}}ˇ
4751 "
4752 .unindent(),
4753 );
4754
4755 // autoclose multi-character pairs
4756 cx.set_state(
4757 &"
4758 ˇ
4759 ˇ
4760 "
4761 .unindent(),
4762 );
4763 cx.update_editor(|view, cx| {
4764 view.handle_input("/", cx);
4765 view.handle_input("*", cx);
4766 });
4767 cx.assert_editor_state(
4768 &"
4769 /*ˇ */
4770 /*ˇ */
4771 "
4772 .unindent(),
4773 );
4774
4775 // one cursor autocloses a multi-character pair, one cursor
4776 // does not autoclose.
4777 cx.set_state(
4778 &"
4779 /ˇ
4780 ˇ
4781 "
4782 .unindent(),
4783 );
4784 cx.update_editor(|view, cx| view.handle_input("*", cx));
4785 cx.assert_editor_state(
4786 &"
4787 /*ˇ */
4788 *ˇ
4789 "
4790 .unindent(),
4791 );
4792
4793 // Don't autoclose if the next character isn't whitespace and isn't
4794 // listed in the language's "autoclose_before" section.
4795 cx.set_state("ˇa b");
4796 cx.update_editor(|view, cx| view.handle_input("{", cx));
4797 cx.assert_editor_state("{ˇa b");
4798
4799 // Don't autoclose if `close` is false for the bracket pair
4800 cx.set_state("ˇ");
4801 cx.update_editor(|view, cx| view.handle_input("[", cx));
4802 cx.assert_editor_state("[ˇ");
4803
4804 // Surround with brackets if text is selected
4805 cx.set_state("«aˇ» b");
4806 cx.update_editor(|view, cx| view.handle_input("{", cx));
4807 cx.assert_editor_state("{«aˇ»} b");
4808
4809 // Autclose pair where the start and end characters are the same
4810 cx.set_state("aˇ");
4811 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4812 cx.assert_editor_state("a\"ˇ\"");
4813 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4814 cx.assert_editor_state("a\"\"ˇ");
4815}
4816
4817#[gpui::test]
4818async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
4819 init_test(cx, |settings| {
4820 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
4821 });
4822
4823 let mut cx = EditorTestContext::new(cx).await;
4824
4825 let language = Arc::new(Language::new(
4826 LanguageConfig {
4827 brackets: BracketPairConfig {
4828 pairs: vec![
4829 BracketPair {
4830 start: "{".to_string(),
4831 end: "}".to_string(),
4832 close: true,
4833 newline: true,
4834 },
4835 BracketPair {
4836 start: "(".to_string(),
4837 end: ")".to_string(),
4838 close: true,
4839 newline: true,
4840 },
4841 BracketPair {
4842 start: "[".to_string(),
4843 end: "]".to_string(),
4844 close: false,
4845 newline: true,
4846 },
4847 ],
4848 ..Default::default()
4849 },
4850 autoclose_before: "})]".to_string(),
4851 ..Default::default()
4852 },
4853 Some(tree_sitter_rust::language()),
4854 ));
4855
4856 cx.language_registry().add(language.clone());
4857 cx.update_buffer(|buffer, cx| {
4858 buffer.set_language(Some(language), cx);
4859 });
4860
4861 cx.set_state(
4862 &"
4863 ˇ
4864 ˇ
4865 ˇ
4866 "
4867 .unindent(),
4868 );
4869
4870 // ensure only matching closing brackets are skipped over
4871 cx.update_editor(|view, cx| {
4872 view.handle_input("}", cx);
4873 view.move_left(&MoveLeft, cx);
4874 view.handle_input(")", cx);
4875 view.move_left(&MoveLeft, cx);
4876 });
4877 cx.assert_editor_state(
4878 &"
4879 ˇ)}
4880 ˇ)}
4881 ˇ)}
4882 "
4883 .unindent(),
4884 );
4885
4886 // skip-over closing brackets at multiple cursors
4887 cx.update_editor(|view, cx| {
4888 view.handle_input(")", cx);
4889 view.handle_input("}", cx);
4890 });
4891 cx.assert_editor_state(
4892 &"
4893 )}ˇ
4894 )}ˇ
4895 )}ˇ
4896 "
4897 .unindent(),
4898 );
4899
4900 // ignore non-close brackets
4901 cx.update_editor(|view, cx| {
4902 view.handle_input("]", cx);
4903 view.move_left(&MoveLeft, cx);
4904 view.handle_input("]", cx);
4905 });
4906 cx.assert_editor_state(
4907 &"
4908 )}]ˇ]
4909 )}]ˇ]
4910 )}]ˇ]
4911 "
4912 .unindent(),
4913 );
4914}
4915
4916#[gpui::test]
4917async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4918 init_test(cx, |_| {});
4919
4920 let mut cx = EditorTestContext::new(cx).await;
4921
4922 let html_language = Arc::new(
4923 Language::new(
4924 LanguageConfig {
4925 name: "HTML".into(),
4926 brackets: BracketPairConfig {
4927 pairs: vec![
4928 BracketPair {
4929 start: "<".into(),
4930 end: ">".into(),
4931 close: true,
4932 ..Default::default()
4933 },
4934 BracketPair {
4935 start: "{".into(),
4936 end: "}".into(),
4937 close: true,
4938 ..Default::default()
4939 },
4940 BracketPair {
4941 start: "(".into(),
4942 end: ")".into(),
4943 close: true,
4944 ..Default::default()
4945 },
4946 ],
4947 ..Default::default()
4948 },
4949 autoclose_before: "})]>".into(),
4950 ..Default::default()
4951 },
4952 Some(tree_sitter_html::language()),
4953 )
4954 .with_injection_query(
4955 r#"
4956 (script_element
4957 (raw_text) @content
4958 (#set! "language" "javascript"))
4959 "#,
4960 )
4961 .unwrap(),
4962 );
4963
4964 let javascript_language = Arc::new(Language::new(
4965 LanguageConfig {
4966 name: "JavaScript".into(),
4967 brackets: BracketPairConfig {
4968 pairs: vec![
4969 BracketPair {
4970 start: "/*".into(),
4971 end: " */".into(),
4972 close: true,
4973 ..Default::default()
4974 },
4975 BracketPair {
4976 start: "{".into(),
4977 end: "}".into(),
4978 close: true,
4979 ..Default::default()
4980 },
4981 BracketPair {
4982 start: "(".into(),
4983 end: ")".into(),
4984 close: true,
4985 ..Default::default()
4986 },
4987 ],
4988 ..Default::default()
4989 },
4990 autoclose_before: "})]>".into(),
4991 ..Default::default()
4992 },
4993 Some(tree_sitter_typescript::language_tsx()),
4994 ));
4995
4996 cx.language_registry().add(html_language.clone());
4997 cx.language_registry().add(javascript_language.clone());
4998
4999 cx.update_buffer(|buffer, cx| {
5000 buffer.set_language(Some(html_language), cx);
5001 });
5002
5003 cx.set_state(
5004 &r#"
5005 <body>ˇ
5006 <script>
5007 var x = 1;ˇ
5008 </script>
5009 </body>ˇ
5010 "#
5011 .unindent(),
5012 );
5013
5014 // Precondition: different languages are active at different locations.
5015 cx.update_editor(|editor, cx| {
5016 let snapshot = editor.snapshot(cx);
5017 let cursors = editor.selections.ranges::<usize>(cx);
5018 let languages = cursors
5019 .iter()
5020 .map(|c| snapshot.language_at(c.start).unwrap().name())
5021 .collect::<Vec<_>>();
5022 assert_eq!(
5023 languages,
5024 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5025 );
5026 });
5027
5028 // Angle brackets autoclose in HTML, but not JavaScript.
5029 cx.update_editor(|editor, cx| {
5030 editor.handle_input("<", cx);
5031 editor.handle_input("a", cx);
5032 });
5033 cx.assert_editor_state(
5034 &r#"
5035 <body><aˇ>
5036 <script>
5037 var x = 1;<aˇ
5038 </script>
5039 </body><aˇ>
5040 "#
5041 .unindent(),
5042 );
5043
5044 // Curly braces and parens autoclose in both HTML and JavaScript.
5045 cx.update_editor(|editor, cx| {
5046 editor.handle_input(" b=", cx);
5047 editor.handle_input("{", cx);
5048 editor.handle_input("c", cx);
5049 editor.handle_input("(", cx);
5050 });
5051 cx.assert_editor_state(
5052 &r#"
5053 <body><a b={c(ˇ)}>
5054 <script>
5055 var x = 1;<a b={c(ˇ)}
5056 </script>
5057 </body><a b={c(ˇ)}>
5058 "#
5059 .unindent(),
5060 );
5061
5062 // Brackets that were already autoclosed are skipped.
5063 cx.update_editor(|editor, cx| {
5064 editor.handle_input(")", cx);
5065 editor.handle_input("d", cx);
5066 editor.handle_input("}", cx);
5067 });
5068 cx.assert_editor_state(
5069 &r#"
5070 <body><a b={c()d}ˇ>
5071 <script>
5072 var x = 1;<a b={c()d}ˇ
5073 </script>
5074 </body><a b={c()d}ˇ>
5075 "#
5076 .unindent(),
5077 );
5078 cx.update_editor(|editor, cx| {
5079 editor.handle_input(">", cx);
5080 });
5081 cx.assert_editor_state(
5082 &r#"
5083 <body><a b={c()d}>ˇ
5084 <script>
5085 var x = 1;<a b={c()d}>ˇ
5086 </script>
5087 </body><a b={c()d}>ˇ
5088 "#
5089 .unindent(),
5090 );
5091
5092 // Reset
5093 cx.set_state(
5094 &r#"
5095 <body>ˇ
5096 <script>
5097 var x = 1;ˇ
5098 </script>
5099 </body>ˇ
5100 "#
5101 .unindent(),
5102 );
5103
5104 cx.update_editor(|editor, cx| {
5105 editor.handle_input("<", cx);
5106 });
5107 cx.assert_editor_state(
5108 &r#"
5109 <body><ˇ>
5110 <script>
5111 var x = 1;<ˇ
5112 </script>
5113 </body><ˇ>
5114 "#
5115 .unindent(),
5116 );
5117
5118 // When backspacing, the closing angle brackets are removed.
5119 cx.update_editor(|editor, cx| {
5120 editor.backspace(&Backspace, cx);
5121 });
5122 cx.assert_editor_state(
5123 &r#"
5124 <body>ˇ
5125 <script>
5126 var x = 1;ˇ
5127 </script>
5128 </body>ˇ
5129 "#
5130 .unindent(),
5131 );
5132
5133 // Block comments autoclose in JavaScript, but not HTML.
5134 cx.update_editor(|editor, cx| {
5135 editor.handle_input("/", cx);
5136 editor.handle_input("*", cx);
5137 });
5138 cx.assert_editor_state(
5139 &r#"
5140 <body>/*ˇ
5141 <script>
5142 var x = 1;/*ˇ */
5143 </script>
5144 </body>/*ˇ
5145 "#
5146 .unindent(),
5147 );
5148}
5149
5150#[gpui::test]
5151async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5152 init_test(cx, |_| {});
5153
5154 let mut cx = EditorTestContext::new(cx).await;
5155
5156 let rust_language = Arc::new(
5157 Language::new(
5158 LanguageConfig {
5159 name: "Rust".into(),
5160 brackets: serde_json::from_value(json!([
5161 { "start": "{", "end": "}", "close": true, "newline": true },
5162 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5163 ]))
5164 .unwrap(),
5165 autoclose_before: "})]>".into(),
5166 ..Default::default()
5167 },
5168 Some(tree_sitter_rust::language()),
5169 )
5170 .with_override_query("(string_literal) @string")
5171 .unwrap(),
5172 );
5173
5174 cx.language_registry().add(rust_language.clone());
5175 cx.update_buffer(|buffer, cx| {
5176 buffer.set_language(Some(rust_language), cx);
5177 });
5178
5179 cx.set_state(
5180 &r#"
5181 let x = ˇ
5182 "#
5183 .unindent(),
5184 );
5185
5186 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5187 cx.update_editor(|editor, cx| {
5188 editor.handle_input("\"", cx);
5189 });
5190 cx.assert_editor_state(
5191 &r#"
5192 let x = "ˇ"
5193 "#
5194 .unindent(),
5195 );
5196
5197 // Inserting another quotation mark. The cursor moves across the existing
5198 // automatically-inserted quotation mark.
5199 cx.update_editor(|editor, cx| {
5200 editor.handle_input("\"", cx);
5201 });
5202 cx.assert_editor_state(
5203 &r#"
5204 let x = ""ˇ
5205 "#
5206 .unindent(),
5207 );
5208
5209 // Reset
5210 cx.set_state(
5211 &r#"
5212 let x = ˇ
5213 "#
5214 .unindent(),
5215 );
5216
5217 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5218 cx.update_editor(|editor, cx| {
5219 editor.handle_input("\"", cx);
5220 editor.handle_input(" ", cx);
5221 editor.move_left(&Default::default(), cx);
5222 editor.handle_input("\\", cx);
5223 editor.handle_input("\"", cx);
5224 });
5225 cx.assert_editor_state(
5226 &r#"
5227 let x = "\"ˇ "
5228 "#
5229 .unindent(),
5230 );
5231
5232 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5233 // mark. Nothing is inserted.
5234 cx.update_editor(|editor, cx| {
5235 editor.move_right(&Default::default(), cx);
5236 editor.handle_input("\"", cx);
5237 });
5238 cx.assert_editor_state(
5239 &r#"
5240 let x = "\" "ˇ
5241 "#
5242 .unindent(),
5243 );
5244}
5245
5246#[gpui::test]
5247async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5248 init_test(cx, |_| {});
5249
5250 let language = Arc::new(Language::new(
5251 LanguageConfig {
5252 brackets: BracketPairConfig {
5253 pairs: vec![
5254 BracketPair {
5255 start: "{".to_string(),
5256 end: "}".to_string(),
5257 close: true,
5258 newline: true,
5259 },
5260 BracketPair {
5261 start: "/* ".to_string(),
5262 end: "*/".to_string(),
5263 close: true,
5264 ..Default::default()
5265 },
5266 ],
5267 ..Default::default()
5268 },
5269 ..Default::default()
5270 },
5271 Some(tree_sitter_rust::language()),
5272 ));
5273
5274 let text = r#"
5275 a
5276 b
5277 c
5278 "#
5279 .unindent();
5280
5281 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5282 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5283 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5284 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5285 .await;
5286
5287 _ = view.update(cx, |view, cx| {
5288 view.change_selections(None, cx, |s| {
5289 s.select_display_ranges([
5290 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5291 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5292 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5293 ])
5294 });
5295
5296 view.handle_input("{", cx);
5297 view.handle_input("{", cx);
5298 view.handle_input("{", cx);
5299 assert_eq!(
5300 view.text(cx),
5301 "
5302 {{{a}}}
5303 {{{b}}}
5304 {{{c}}}
5305 "
5306 .unindent()
5307 );
5308 assert_eq!(
5309 view.selections.display_ranges(cx),
5310 [
5311 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5312 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5313 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5314 ]
5315 );
5316
5317 view.undo(&Undo, cx);
5318 view.undo(&Undo, cx);
5319 view.undo(&Undo, cx);
5320 assert_eq!(
5321 view.text(cx),
5322 "
5323 a
5324 b
5325 c
5326 "
5327 .unindent()
5328 );
5329 assert_eq!(
5330 view.selections.display_ranges(cx),
5331 [
5332 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5333 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5334 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5335 ]
5336 );
5337
5338 // Ensure inserting the first character of a multi-byte bracket pair
5339 // doesn't surround the selections with the bracket.
5340 view.handle_input("/", cx);
5341 assert_eq!(
5342 view.text(cx),
5343 "
5344 /
5345 /
5346 /
5347 "
5348 .unindent()
5349 );
5350 assert_eq!(
5351 view.selections.display_ranges(cx),
5352 [
5353 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5354 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5355 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5356 ]
5357 );
5358
5359 view.undo(&Undo, cx);
5360 assert_eq!(
5361 view.text(cx),
5362 "
5363 a
5364 b
5365 c
5366 "
5367 .unindent()
5368 );
5369 assert_eq!(
5370 view.selections.display_ranges(cx),
5371 [
5372 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5373 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5374 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5375 ]
5376 );
5377
5378 // Ensure inserting the last character of a multi-byte bracket pair
5379 // doesn't surround the selections with the bracket.
5380 view.handle_input("*", cx);
5381 assert_eq!(
5382 view.text(cx),
5383 "
5384 *
5385 *
5386 *
5387 "
5388 .unindent()
5389 );
5390 assert_eq!(
5391 view.selections.display_ranges(cx),
5392 [
5393 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5394 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5395 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5396 ]
5397 );
5398 });
5399}
5400
5401#[gpui::test]
5402async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5403 init_test(cx, |_| {});
5404
5405 let language = Arc::new(Language::new(
5406 LanguageConfig {
5407 brackets: BracketPairConfig {
5408 pairs: vec![BracketPair {
5409 start: "{".to_string(),
5410 end: "}".to_string(),
5411 close: true,
5412 newline: true,
5413 }],
5414 ..Default::default()
5415 },
5416 autoclose_before: "}".to_string(),
5417 ..Default::default()
5418 },
5419 Some(tree_sitter_rust::language()),
5420 ));
5421
5422 let text = r#"
5423 a
5424 b
5425 c
5426 "#
5427 .unindent();
5428
5429 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5430 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5431 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5432 editor
5433 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5434 .await;
5435
5436 _ = editor.update(cx, |editor, cx| {
5437 editor.change_selections(None, cx, |s| {
5438 s.select_ranges([
5439 Point::new(0, 1)..Point::new(0, 1),
5440 Point::new(1, 1)..Point::new(1, 1),
5441 Point::new(2, 1)..Point::new(2, 1),
5442 ])
5443 });
5444
5445 editor.handle_input("{", cx);
5446 editor.handle_input("{", cx);
5447 editor.handle_input("_", cx);
5448 assert_eq!(
5449 editor.text(cx),
5450 "
5451 a{{_}}
5452 b{{_}}
5453 c{{_}}
5454 "
5455 .unindent()
5456 );
5457 assert_eq!(
5458 editor.selections.ranges::<Point>(cx),
5459 [
5460 Point::new(0, 4)..Point::new(0, 4),
5461 Point::new(1, 4)..Point::new(1, 4),
5462 Point::new(2, 4)..Point::new(2, 4)
5463 ]
5464 );
5465
5466 editor.backspace(&Default::default(), cx);
5467 editor.backspace(&Default::default(), cx);
5468 assert_eq!(
5469 editor.text(cx),
5470 "
5471 a{}
5472 b{}
5473 c{}
5474 "
5475 .unindent()
5476 );
5477 assert_eq!(
5478 editor.selections.ranges::<Point>(cx),
5479 [
5480 Point::new(0, 2)..Point::new(0, 2),
5481 Point::new(1, 2)..Point::new(1, 2),
5482 Point::new(2, 2)..Point::new(2, 2)
5483 ]
5484 );
5485
5486 editor.delete_to_previous_word_start(&Default::default(), cx);
5487 assert_eq!(
5488 editor.text(cx),
5489 "
5490 a
5491 b
5492 c
5493 "
5494 .unindent()
5495 );
5496 assert_eq!(
5497 editor.selections.ranges::<Point>(cx),
5498 [
5499 Point::new(0, 1)..Point::new(0, 1),
5500 Point::new(1, 1)..Point::new(1, 1),
5501 Point::new(2, 1)..Point::new(2, 1)
5502 ]
5503 );
5504 });
5505}
5506
5507#[gpui::test]
5508async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5509 init_test(cx, |settings| {
5510 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5511 });
5512
5513 let mut cx = EditorTestContext::new(cx).await;
5514
5515 let language = Arc::new(Language::new(
5516 LanguageConfig {
5517 brackets: BracketPairConfig {
5518 pairs: vec![
5519 BracketPair {
5520 start: "{".to_string(),
5521 end: "}".to_string(),
5522 close: true,
5523 newline: true,
5524 },
5525 BracketPair {
5526 start: "(".to_string(),
5527 end: ")".to_string(),
5528 close: true,
5529 newline: true,
5530 },
5531 BracketPair {
5532 start: "[".to_string(),
5533 end: "]".to_string(),
5534 close: false,
5535 newline: true,
5536 },
5537 ],
5538 ..Default::default()
5539 },
5540 autoclose_before: "})]".to_string(),
5541 ..Default::default()
5542 },
5543 Some(tree_sitter_rust::language()),
5544 ));
5545
5546 cx.language_registry().add(language.clone());
5547 cx.update_buffer(|buffer, cx| {
5548 buffer.set_language(Some(language), cx);
5549 });
5550
5551 cx.set_state(
5552 &"
5553 {(ˇ)}
5554 [[ˇ]]
5555 {(ˇ)}
5556 "
5557 .unindent(),
5558 );
5559
5560 cx.update_editor(|view, cx| {
5561 view.backspace(&Default::default(), cx);
5562 view.backspace(&Default::default(), cx);
5563 });
5564
5565 cx.assert_editor_state(
5566 &"
5567 ˇ
5568 ˇ]]
5569 ˇ
5570 "
5571 .unindent(),
5572 );
5573
5574 cx.update_editor(|view, cx| {
5575 view.handle_input("{", cx);
5576 view.handle_input("{", cx);
5577 view.move_right(&MoveRight, cx);
5578 view.move_right(&MoveRight, cx);
5579 view.move_left(&MoveLeft, cx);
5580 view.move_left(&MoveLeft, cx);
5581 view.backspace(&Default::default(), cx);
5582 });
5583
5584 cx.assert_editor_state(
5585 &"
5586 {ˇ}
5587 {ˇ}]]
5588 {ˇ}
5589 "
5590 .unindent(),
5591 );
5592
5593 cx.update_editor(|view, cx| {
5594 view.backspace(&Default::default(), cx);
5595 });
5596
5597 cx.assert_editor_state(
5598 &"
5599 ˇ
5600 ˇ]]
5601 ˇ
5602 "
5603 .unindent(),
5604 );
5605}
5606
5607#[gpui::test]
5608async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5609 init_test(cx, |_| {});
5610
5611 let language = Arc::new(Language::new(
5612 LanguageConfig::default(),
5613 Some(tree_sitter_rust::language()),
5614 ));
5615
5616 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5617 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5618 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5619 editor
5620 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5621 .await;
5622
5623 _ = editor.update(cx, |editor, cx| {
5624 editor.set_auto_replace_emoji_shortcode(true);
5625
5626 editor.handle_input("Hello ", cx);
5627 editor.handle_input(":wave", cx);
5628 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5629
5630 editor.handle_input(":", cx);
5631 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5632
5633 editor.handle_input(" :smile", cx);
5634 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5635
5636 editor.handle_input(":", cx);
5637 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5638
5639 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5640 editor.handle_input(":wave", cx);
5641 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5642
5643 editor.handle_input(":", cx);
5644 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5645
5646 editor.handle_input(":1", cx);
5647 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5648
5649 editor.handle_input(":", cx);
5650 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5651
5652 // Ensure shortcode does not get replaced when it is part of a word
5653 editor.handle_input(" Test:wave", cx);
5654 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5655
5656 editor.handle_input(":", cx);
5657 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5658
5659 editor.set_auto_replace_emoji_shortcode(false);
5660
5661 // Ensure shortcode does not get replaced when auto replace is off
5662 editor.handle_input(" :wave", cx);
5663 assert_eq!(
5664 editor.text(cx),
5665 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5666 );
5667
5668 editor.handle_input(":", cx);
5669 assert_eq!(
5670 editor.text(cx),
5671 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5672 );
5673 });
5674}
5675
5676#[gpui::test]
5677async fn test_snippets(cx: &mut gpui::TestAppContext) {
5678 init_test(cx, |_| {});
5679
5680 let (text, insertion_ranges) = marked_text_ranges(
5681 indoc! {"
5682 a.ˇ b
5683 a.ˇ b
5684 a.ˇ b
5685 "},
5686 false,
5687 );
5688
5689 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5690 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5691
5692 _ = editor.update(cx, |editor, cx| {
5693 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5694
5695 editor
5696 .insert_snippet(&insertion_ranges, snippet, cx)
5697 .unwrap();
5698
5699 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5700 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5701 assert_eq!(editor.text(cx), expected_text);
5702 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5703 }
5704
5705 assert(
5706 editor,
5707 cx,
5708 indoc! {"
5709 a.f(«one», two, «three») b
5710 a.f(«one», two, «three») b
5711 a.f(«one», two, «three») b
5712 "},
5713 );
5714
5715 // Can't move earlier than the first tab stop
5716 assert!(!editor.move_to_prev_snippet_tabstop(cx));
5717 assert(
5718 editor,
5719 cx,
5720 indoc! {"
5721 a.f(«one», two, «three») b
5722 a.f(«one», two, «three») b
5723 a.f(«one», two, «three») b
5724 "},
5725 );
5726
5727 assert!(editor.move_to_next_snippet_tabstop(cx));
5728 assert(
5729 editor,
5730 cx,
5731 indoc! {"
5732 a.f(one, «two», three) b
5733 a.f(one, «two», three) b
5734 a.f(one, «two», three) b
5735 "},
5736 );
5737
5738 editor.move_to_prev_snippet_tabstop(cx);
5739 assert(
5740 editor,
5741 cx,
5742 indoc! {"
5743 a.f(«one», two, «three») b
5744 a.f(«one», two, «three») b
5745 a.f(«one», two, «three») b
5746 "},
5747 );
5748
5749 assert!(editor.move_to_next_snippet_tabstop(cx));
5750 assert(
5751 editor,
5752 cx,
5753 indoc! {"
5754 a.f(one, «two», three) b
5755 a.f(one, «two», three) b
5756 a.f(one, «two», three) b
5757 "},
5758 );
5759 assert!(editor.move_to_next_snippet_tabstop(cx));
5760 assert(
5761 editor,
5762 cx,
5763 indoc! {"
5764 a.f(one, two, three)ˇ b
5765 a.f(one, two, three)ˇ b
5766 a.f(one, two, three)ˇ b
5767 "},
5768 );
5769
5770 // As soon as the last tab stop is reached, snippet state is gone
5771 editor.move_to_prev_snippet_tabstop(cx);
5772 assert(
5773 editor,
5774 cx,
5775 indoc! {"
5776 a.f(one, two, three)ˇ b
5777 a.f(one, two, three)ˇ b
5778 a.f(one, two, three)ˇ b
5779 "},
5780 );
5781 });
5782}
5783
5784#[gpui::test]
5785async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
5786 init_test(cx, |_| {});
5787
5788 let fs = FakeFs::new(cx.executor());
5789 fs.insert_file("/file.rs", Default::default()).await;
5790
5791 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5792
5793 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5794 language_registry.add(rust_lang());
5795 let mut fake_servers = language_registry.register_fake_lsp_adapter(
5796 "Rust",
5797 FakeLspAdapter {
5798 capabilities: lsp::ServerCapabilities {
5799 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5800 ..Default::default()
5801 },
5802 ..Default::default()
5803 },
5804 );
5805
5806 let buffer = project
5807 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5808 .await
5809 .unwrap();
5810
5811 cx.executor().start_waiting();
5812 let fake_server = fake_servers.next().await.unwrap();
5813
5814 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5815 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5816 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5817 assert!(cx.read(|cx| editor.is_dirty(cx)));
5818
5819 let save = editor
5820 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5821 .unwrap();
5822 fake_server
5823 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5824 assert_eq!(
5825 params.text_document.uri,
5826 lsp::Url::from_file_path("/file.rs").unwrap()
5827 );
5828 assert_eq!(params.options.tab_size, 4);
5829 Ok(Some(vec![lsp::TextEdit::new(
5830 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5831 ", ".to_string(),
5832 )]))
5833 })
5834 .next()
5835 .await;
5836 cx.executor().start_waiting();
5837 save.await;
5838
5839 assert_eq!(
5840 editor.update(cx, |editor, cx| editor.text(cx)),
5841 "one, two\nthree\n"
5842 );
5843 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5844
5845 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5846 assert!(cx.read(|cx| editor.is_dirty(cx)));
5847
5848 // Ensure we can still save even if formatting hangs.
5849 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5850 assert_eq!(
5851 params.text_document.uri,
5852 lsp::Url::from_file_path("/file.rs").unwrap()
5853 );
5854 futures::future::pending::<()>().await;
5855 unreachable!()
5856 });
5857 let save = editor
5858 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5859 .unwrap();
5860 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5861 cx.executor().start_waiting();
5862 save.await;
5863 assert_eq!(
5864 editor.update(cx, |editor, cx| editor.text(cx)),
5865 "one\ntwo\nthree\n"
5866 );
5867 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5868
5869 // For non-dirty buffer, no formatting request should be sent
5870 let save = editor
5871 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5872 .unwrap();
5873 let _pending_format_request = fake_server
5874 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
5875 panic!("Should not be invoked on non-dirty buffer");
5876 })
5877 .next();
5878 cx.executor().start_waiting();
5879 save.await;
5880
5881 // Set rust language override and assert overridden tabsize is sent to language server
5882 update_test_language_settings(cx, |settings| {
5883 settings.languages.insert(
5884 "Rust".into(),
5885 LanguageSettingsContent {
5886 tab_size: NonZeroU32::new(8),
5887 ..Default::default()
5888 },
5889 );
5890 });
5891
5892 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
5893 assert!(cx.read(|cx| editor.is_dirty(cx)));
5894 let save = editor
5895 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5896 .unwrap();
5897 fake_server
5898 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5899 assert_eq!(
5900 params.text_document.uri,
5901 lsp::Url::from_file_path("/file.rs").unwrap()
5902 );
5903 assert_eq!(params.options.tab_size, 8);
5904 Ok(Some(vec![]))
5905 })
5906 .next()
5907 .await;
5908 cx.executor().start_waiting();
5909 save.await;
5910}
5911
5912#[gpui::test]
5913async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
5914 init_test(cx, |_| {});
5915
5916 let cols = 4;
5917 let rows = 10;
5918 let sample_text_1 = sample_text(rows, cols, 'a');
5919 assert_eq!(
5920 sample_text_1,
5921 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
5922 );
5923 let sample_text_2 = sample_text(rows, cols, 'l');
5924 assert_eq!(
5925 sample_text_2,
5926 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
5927 );
5928 let sample_text_3 = sample_text(rows, cols, 'v');
5929 assert_eq!(
5930 sample_text_3,
5931 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
5932 );
5933
5934 let fs = FakeFs::new(cx.executor());
5935 fs.insert_tree(
5936 "/a",
5937 json!({
5938 "main.rs": sample_text_1,
5939 "other.rs": sample_text_2,
5940 "lib.rs": sample_text_3,
5941 }),
5942 )
5943 .await;
5944
5945 let project = Project::test(fs, ["/a".as_ref()], cx).await;
5946 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5947 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
5948
5949 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5950 language_registry.add(rust_lang());
5951 let mut fake_servers = language_registry.register_fake_lsp_adapter(
5952 "Rust",
5953 FakeLspAdapter {
5954 capabilities: lsp::ServerCapabilities {
5955 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5956 ..Default::default()
5957 },
5958 ..Default::default()
5959 },
5960 );
5961
5962 let worktree = project.update(cx, |project, _| {
5963 let mut worktrees = project.worktrees().collect::<Vec<_>>();
5964 assert_eq!(worktrees.len(), 1);
5965 worktrees.pop().unwrap()
5966 });
5967 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
5968
5969 let buffer_1 = project
5970 .update(cx, |project, cx| {
5971 project.open_buffer((worktree_id, "main.rs"), cx)
5972 })
5973 .await
5974 .unwrap();
5975 let buffer_2 = project
5976 .update(cx, |project, cx| {
5977 project.open_buffer((worktree_id, "other.rs"), cx)
5978 })
5979 .await
5980 .unwrap();
5981 let buffer_3 = project
5982 .update(cx, |project, cx| {
5983 project.open_buffer((worktree_id, "lib.rs"), cx)
5984 })
5985 .await
5986 .unwrap();
5987
5988 let multi_buffer = cx.new_model(|cx| {
5989 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
5990 multi_buffer.push_excerpts(
5991 buffer_1.clone(),
5992 [
5993 ExcerptRange {
5994 context: Point::new(0, 0)..Point::new(3, 0),
5995 primary: None,
5996 },
5997 ExcerptRange {
5998 context: Point::new(5, 0)..Point::new(7, 0),
5999 primary: None,
6000 },
6001 ExcerptRange {
6002 context: Point::new(9, 0)..Point::new(10, 4),
6003 primary: None,
6004 },
6005 ],
6006 cx,
6007 );
6008 multi_buffer.push_excerpts(
6009 buffer_2.clone(),
6010 [
6011 ExcerptRange {
6012 context: Point::new(0, 0)..Point::new(3, 0),
6013 primary: None,
6014 },
6015 ExcerptRange {
6016 context: Point::new(5, 0)..Point::new(7, 0),
6017 primary: None,
6018 },
6019 ExcerptRange {
6020 context: Point::new(9, 0)..Point::new(10, 4),
6021 primary: None,
6022 },
6023 ],
6024 cx,
6025 );
6026 multi_buffer.push_excerpts(
6027 buffer_3.clone(),
6028 [
6029 ExcerptRange {
6030 context: Point::new(0, 0)..Point::new(3, 0),
6031 primary: None,
6032 },
6033 ExcerptRange {
6034 context: Point::new(5, 0)..Point::new(7, 0),
6035 primary: None,
6036 },
6037 ExcerptRange {
6038 context: Point::new(9, 0)..Point::new(10, 4),
6039 primary: None,
6040 },
6041 ],
6042 cx,
6043 );
6044 multi_buffer
6045 });
6046 let multi_buffer_editor = cx.new_view(|cx| {
6047 Editor::new(
6048 EditorMode::Full,
6049 multi_buffer,
6050 Some(project.clone()),
6051 true,
6052 cx,
6053 )
6054 });
6055
6056 multi_buffer_editor.update(cx, |editor, cx| {
6057 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6058 editor.insert("|one|two|three|", cx);
6059 });
6060 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6061 multi_buffer_editor.update(cx, |editor, cx| {
6062 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6063 s.select_ranges(Some(60..70))
6064 });
6065 editor.insert("|four|five|six|", cx);
6066 });
6067 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6068
6069 // First two buffers should be edited, but not the third one.
6070 assert_eq!(
6071 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6072 "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}",
6073 );
6074 buffer_1.update(cx, |buffer, _| {
6075 assert!(buffer.is_dirty());
6076 assert_eq!(
6077 buffer.text(),
6078 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6079 )
6080 });
6081 buffer_2.update(cx, |buffer, _| {
6082 assert!(buffer.is_dirty());
6083 assert_eq!(
6084 buffer.text(),
6085 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6086 )
6087 });
6088 buffer_3.update(cx, |buffer, _| {
6089 assert!(!buffer.is_dirty());
6090 assert_eq!(buffer.text(), sample_text_3,)
6091 });
6092
6093 cx.executor().start_waiting();
6094 let save = multi_buffer_editor
6095 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6096 .unwrap();
6097
6098 let fake_server = fake_servers.next().await.unwrap();
6099 fake_server
6100 .server
6101 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6102 Ok(Some(vec![lsp::TextEdit::new(
6103 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6104 format!("[{} formatted]", params.text_document.uri),
6105 )]))
6106 })
6107 .detach();
6108 save.await;
6109
6110 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6111 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6112 assert_eq!(
6113 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6114 "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}",
6115 );
6116 buffer_1.update(cx, |buffer, _| {
6117 assert!(!buffer.is_dirty());
6118 assert_eq!(
6119 buffer.text(),
6120 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6121 )
6122 });
6123 buffer_2.update(cx, |buffer, _| {
6124 assert!(!buffer.is_dirty());
6125 assert_eq!(
6126 buffer.text(),
6127 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6128 )
6129 });
6130 buffer_3.update(cx, |buffer, _| {
6131 assert!(!buffer.is_dirty());
6132 assert_eq!(buffer.text(), sample_text_3,)
6133 });
6134}
6135
6136#[gpui::test]
6137async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6138 init_test(cx, |_| {});
6139
6140 let fs = FakeFs::new(cx.executor());
6141 fs.insert_file("/file.rs", Default::default()).await;
6142
6143 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6144
6145 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6146 language_registry.add(rust_lang());
6147 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6148 "Rust",
6149 FakeLspAdapter {
6150 capabilities: lsp::ServerCapabilities {
6151 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6152 ..Default::default()
6153 },
6154 ..Default::default()
6155 },
6156 );
6157
6158 let buffer = project
6159 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6160 .await
6161 .unwrap();
6162
6163 cx.executor().start_waiting();
6164 let fake_server = fake_servers.next().await.unwrap();
6165
6166 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6167 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6168 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6169 assert!(cx.read(|cx| editor.is_dirty(cx)));
6170
6171 let save = editor
6172 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6173 .unwrap();
6174 fake_server
6175 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6176 assert_eq!(
6177 params.text_document.uri,
6178 lsp::Url::from_file_path("/file.rs").unwrap()
6179 );
6180 assert_eq!(params.options.tab_size, 4);
6181 Ok(Some(vec![lsp::TextEdit::new(
6182 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6183 ", ".to_string(),
6184 )]))
6185 })
6186 .next()
6187 .await;
6188 cx.executor().start_waiting();
6189 save.await;
6190 assert_eq!(
6191 editor.update(cx, |editor, cx| editor.text(cx)),
6192 "one, two\nthree\n"
6193 );
6194 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6195
6196 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6197 assert!(cx.read(|cx| editor.is_dirty(cx)));
6198
6199 // Ensure we can still save even if formatting hangs.
6200 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6201 move |params, _| async move {
6202 assert_eq!(
6203 params.text_document.uri,
6204 lsp::Url::from_file_path("/file.rs").unwrap()
6205 );
6206 futures::future::pending::<()>().await;
6207 unreachable!()
6208 },
6209 );
6210 let save = editor
6211 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6212 .unwrap();
6213 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6214 cx.executor().start_waiting();
6215 save.await;
6216 assert_eq!(
6217 editor.update(cx, |editor, cx| editor.text(cx)),
6218 "one\ntwo\nthree\n"
6219 );
6220 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6221
6222 // For non-dirty buffer, no formatting request should be sent
6223 let save = editor
6224 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6225 .unwrap();
6226 let _pending_format_request = fake_server
6227 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6228 panic!("Should not be invoked on non-dirty buffer");
6229 })
6230 .next();
6231 cx.executor().start_waiting();
6232 save.await;
6233
6234 // Set Rust language override and assert overridden tabsize is sent to language server
6235 update_test_language_settings(cx, |settings| {
6236 settings.languages.insert(
6237 "Rust".into(),
6238 LanguageSettingsContent {
6239 tab_size: NonZeroU32::new(8),
6240 ..Default::default()
6241 },
6242 );
6243 });
6244
6245 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6246 assert!(cx.read(|cx| editor.is_dirty(cx)));
6247 let save = editor
6248 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6249 .unwrap();
6250 fake_server
6251 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6252 assert_eq!(
6253 params.text_document.uri,
6254 lsp::Url::from_file_path("/file.rs").unwrap()
6255 );
6256 assert_eq!(params.options.tab_size, 8);
6257 Ok(Some(vec![]))
6258 })
6259 .next()
6260 .await;
6261 cx.executor().start_waiting();
6262 save.await;
6263}
6264
6265#[gpui::test]
6266async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6267 init_test(cx, |settings| {
6268 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
6269 });
6270
6271 let fs = FakeFs::new(cx.executor());
6272 fs.insert_file("/file.rs", Default::default()).await;
6273
6274 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6275
6276 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6277 language_registry.add(Arc::new(Language::new(
6278 LanguageConfig {
6279 name: "Rust".into(),
6280 matcher: LanguageMatcher {
6281 path_suffixes: vec!["rs".to_string()],
6282 ..Default::default()
6283 },
6284 ..LanguageConfig::default()
6285 },
6286 Some(tree_sitter_rust::language()),
6287 )));
6288 update_test_language_settings(cx, |settings| {
6289 // Enable Prettier formatting for the same buffer, and ensure
6290 // LSP is called instead of Prettier.
6291 settings.defaults.prettier = Some(PrettierSettings {
6292 allowed: true,
6293 ..PrettierSettings::default()
6294 });
6295 });
6296 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6297 "Rust",
6298 FakeLspAdapter {
6299 capabilities: lsp::ServerCapabilities {
6300 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6301 ..Default::default()
6302 },
6303 ..Default::default()
6304 },
6305 );
6306
6307 let buffer = project
6308 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6309 .await
6310 .unwrap();
6311
6312 cx.executor().start_waiting();
6313 let fake_server = fake_servers.next().await.unwrap();
6314
6315 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6316 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6317 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6318
6319 let format = editor
6320 .update(cx, |editor, cx| {
6321 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6322 })
6323 .unwrap();
6324 fake_server
6325 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6326 assert_eq!(
6327 params.text_document.uri,
6328 lsp::Url::from_file_path("/file.rs").unwrap()
6329 );
6330 assert_eq!(params.options.tab_size, 4);
6331 Ok(Some(vec![lsp::TextEdit::new(
6332 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6333 ", ".to_string(),
6334 )]))
6335 })
6336 .next()
6337 .await;
6338 cx.executor().start_waiting();
6339 format.await;
6340 assert_eq!(
6341 editor.update(cx, |editor, cx| editor.text(cx)),
6342 "one, two\nthree\n"
6343 );
6344
6345 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6346 // Ensure we don't lock if formatting hangs.
6347 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6348 assert_eq!(
6349 params.text_document.uri,
6350 lsp::Url::from_file_path("/file.rs").unwrap()
6351 );
6352 futures::future::pending::<()>().await;
6353 unreachable!()
6354 });
6355 let format = editor
6356 .update(cx, |editor, cx| {
6357 editor.perform_format(project, FormatTrigger::Manual, cx)
6358 })
6359 .unwrap();
6360 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6361 cx.executor().start_waiting();
6362 format.await;
6363 assert_eq!(
6364 editor.update(cx, |editor, cx| editor.text(cx)),
6365 "one\ntwo\nthree\n"
6366 );
6367}
6368
6369#[gpui::test]
6370async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6371 init_test(cx, |_| {});
6372
6373 let mut cx = EditorLspTestContext::new_rust(
6374 lsp::ServerCapabilities {
6375 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6376 ..Default::default()
6377 },
6378 cx,
6379 )
6380 .await;
6381
6382 cx.set_state(indoc! {"
6383 one.twoˇ
6384 "});
6385
6386 // The format request takes a long time. When it completes, it inserts
6387 // a newline and an indent before the `.`
6388 cx.lsp
6389 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6390 let executor = cx.background_executor().clone();
6391 async move {
6392 executor.timer(Duration::from_millis(100)).await;
6393 Ok(Some(vec![lsp::TextEdit {
6394 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6395 new_text: "\n ".into(),
6396 }]))
6397 }
6398 });
6399
6400 // Submit a format request.
6401 let format_1 = cx
6402 .update_editor(|editor, cx| editor.format(&Format, cx))
6403 .unwrap();
6404 cx.executor().run_until_parked();
6405
6406 // Submit a second format request.
6407 let format_2 = cx
6408 .update_editor(|editor, cx| editor.format(&Format, cx))
6409 .unwrap();
6410 cx.executor().run_until_parked();
6411
6412 // Wait for both format requests to complete
6413 cx.executor().advance_clock(Duration::from_millis(200));
6414 cx.executor().start_waiting();
6415 format_1.await.unwrap();
6416 cx.executor().start_waiting();
6417 format_2.await.unwrap();
6418
6419 // The formatting edits only happens once.
6420 cx.assert_editor_state(indoc! {"
6421 one
6422 .twoˇ
6423 "});
6424}
6425
6426#[gpui::test]
6427async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6428 init_test(cx, |settings| {
6429 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
6430 });
6431
6432 let mut cx = EditorLspTestContext::new_rust(
6433 lsp::ServerCapabilities {
6434 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6435 ..Default::default()
6436 },
6437 cx,
6438 )
6439 .await;
6440
6441 // Set up a buffer white some trailing whitespace and no trailing newline.
6442 cx.set_state(
6443 &[
6444 "one ", //
6445 "twoˇ", //
6446 "three ", //
6447 "four", //
6448 ]
6449 .join("\n"),
6450 );
6451
6452 // Submit a format request.
6453 let format = cx
6454 .update_editor(|editor, cx| editor.format(&Format, cx))
6455 .unwrap();
6456
6457 // Record which buffer changes have been sent to the language server
6458 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6459 cx.lsp
6460 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6461 let buffer_changes = buffer_changes.clone();
6462 move |params, _| {
6463 buffer_changes.lock().extend(
6464 params
6465 .content_changes
6466 .into_iter()
6467 .map(|e| (e.range.unwrap(), e.text)),
6468 );
6469 }
6470 });
6471
6472 // Handle formatting requests to the language server.
6473 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6474 let buffer_changes = buffer_changes.clone();
6475 move |_, _| {
6476 // When formatting is requested, trailing whitespace has already been stripped,
6477 // and the trailing newline has already been added.
6478 assert_eq!(
6479 &buffer_changes.lock()[1..],
6480 &[
6481 (
6482 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6483 "".into()
6484 ),
6485 (
6486 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6487 "".into()
6488 ),
6489 (
6490 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6491 "\n".into()
6492 ),
6493 ]
6494 );
6495
6496 // Insert blank lines between each line of the buffer.
6497 async move {
6498 Ok(Some(vec![
6499 lsp::TextEdit {
6500 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6501 new_text: "\n".into(),
6502 },
6503 lsp::TextEdit {
6504 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6505 new_text: "\n".into(),
6506 },
6507 ]))
6508 }
6509 }
6510 });
6511
6512 // After formatting the buffer, the trailing whitespace is stripped,
6513 // a newline is appended, and the edits provided by the language server
6514 // have been applied.
6515 format.await.unwrap();
6516 cx.assert_editor_state(
6517 &[
6518 "one", //
6519 "", //
6520 "twoˇ", //
6521 "", //
6522 "three", //
6523 "four", //
6524 "", //
6525 ]
6526 .join("\n"),
6527 );
6528
6529 // Undoing the formatting undoes the trailing whitespace removal, the
6530 // trailing newline, and the LSP edits.
6531 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6532 cx.assert_editor_state(
6533 &[
6534 "one ", //
6535 "twoˇ", //
6536 "three ", //
6537 "four", //
6538 ]
6539 .join("\n"),
6540 );
6541}
6542
6543#[gpui::test]
6544async fn test_completion(cx: &mut gpui::TestAppContext) {
6545 init_test(cx, |_| {});
6546
6547 let mut cx = EditorLspTestContext::new_rust(
6548 lsp::ServerCapabilities {
6549 completion_provider: Some(lsp::CompletionOptions {
6550 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6551 resolve_provider: Some(true),
6552 ..Default::default()
6553 }),
6554 ..Default::default()
6555 },
6556 cx,
6557 )
6558 .await;
6559 let counter = Arc::new(AtomicUsize::new(0));
6560
6561 cx.set_state(indoc! {"
6562 oneˇ
6563 two
6564 three
6565 "});
6566 cx.simulate_keystroke(".");
6567 handle_completion_request(
6568 &mut cx,
6569 indoc! {"
6570 one.|<>
6571 two
6572 three
6573 "},
6574 vec!["first_completion", "second_completion"],
6575 counter.clone(),
6576 )
6577 .await;
6578 cx.condition(|editor, _| editor.context_menu_visible())
6579 .await;
6580 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
6581
6582 let apply_additional_edits = cx.update_editor(|editor, cx| {
6583 editor.context_menu_next(&Default::default(), cx);
6584 editor
6585 .confirm_completion(&ConfirmCompletion::default(), cx)
6586 .unwrap()
6587 });
6588 cx.assert_editor_state(indoc! {"
6589 one.second_completionˇ
6590 two
6591 three
6592 "});
6593
6594 handle_resolve_completion_request(
6595 &mut cx,
6596 Some(vec![
6597 (
6598 //This overlaps with the primary completion edit which is
6599 //misbehavior from the LSP spec, test that we filter it out
6600 indoc! {"
6601 one.second_ˇcompletion
6602 two
6603 threeˇ
6604 "},
6605 "overlapping additional edit",
6606 ),
6607 (
6608 indoc! {"
6609 one.second_completion
6610 two
6611 threeˇ
6612 "},
6613 "\nadditional edit",
6614 ),
6615 ]),
6616 )
6617 .await;
6618 apply_additional_edits.await.unwrap();
6619 cx.assert_editor_state(indoc! {"
6620 one.second_completionˇ
6621 two
6622 three
6623 additional edit
6624 "});
6625
6626 cx.set_state(indoc! {"
6627 one.second_completion
6628 twoˇ
6629 threeˇ
6630 additional edit
6631 "});
6632 cx.simulate_keystroke(" ");
6633 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6634 cx.simulate_keystroke("s");
6635 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6636
6637 cx.assert_editor_state(indoc! {"
6638 one.second_completion
6639 two sˇ
6640 three sˇ
6641 additional edit
6642 "});
6643 handle_completion_request(
6644 &mut cx,
6645 indoc! {"
6646 one.second_completion
6647 two s
6648 three <s|>
6649 additional edit
6650 "},
6651 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6652 counter.clone(),
6653 )
6654 .await;
6655 cx.condition(|editor, _| editor.context_menu_visible())
6656 .await;
6657 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
6658
6659 cx.simulate_keystroke("i");
6660
6661 handle_completion_request(
6662 &mut cx,
6663 indoc! {"
6664 one.second_completion
6665 two si
6666 three <si|>
6667 additional edit
6668 "},
6669 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6670 counter.clone(),
6671 )
6672 .await;
6673 cx.condition(|editor, _| editor.context_menu_visible())
6674 .await;
6675 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
6676
6677 let apply_additional_edits = cx.update_editor(|editor, cx| {
6678 editor
6679 .confirm_completion(&ConfirmCompletion::default(), cx)
6680 .unwrap()
6681 });
6682 cx.assert_editor_state(indoc! {"
6683 one.second_completion
6684 two sixth_completionˇ
6685 three sixth_completionˇ
6686 additional edit
6687 "});
6688
6689 handle_resolve_completion_request(&mut cx, None).await;
6690 apply_additional_edits.await.unwrap();
6691
6692 _ = cx.update(|cx| {
6693 cx.update_global::<SettingsStore, _>(|settings, cx| {
6694 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6695 settings.show_completions_on_input = Some(false);
6696 });
6697 })
6698 });
6699 cx.set_state("editorˇ");
6700 cx.simulate_keystroke(".");
6701 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6702 cx.simulate_keystroke("c");
6703 cx.simulate_keystroke("l");
6704 cx.simulate_keystroke("o");
6705 cx.assert_editor_state("editor.cloˇ");
6706 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6707 cx.update_editor(|editor, cx| {
6708 editor.show_completions(&ShowCompletions, cx);
6709 });
6710 handle_completion_request(
6711 &mut cx,
6712 "editor.<clo|>",
6713 vec!["close", "clobber"],
6714 counter.clone(),
6715 )
6716 .await;
6717 cx.condition(|editor, _| editor.context_menu_visible())
6718 .await;
6719 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
6720
6721 let apply_additional_edits = cx.update_editor(|editor, cx| {
6722 editor
6723 .confirm_completion(&ConfirmCompletion::default(), cx)
6724 .unwrap()
6725 });
6726 cx.assert_editor_state("editor.closeˇ");
6727 handle_resolve_completion_request(&mut cx, None).await;
6728 apply_additional_edits.await.unwrap();
6729}
6730
6731#[gpui::test]
6732async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
6733 init_test(cx, |_| {});
6734
6735 let mut cx = EditorLspTestContext::new_rust(
6736 lsp::ServerCapabilities {
6737 completion_provider: Some(lsp::CompletionOptions {
6738 trigger_characters: Some(vec![".".to_string()]),
6739 resolve_provider: Some(true),
6740 ..Default::default()
6741 }),
6742 ..Default::default()
6743 },
6744 cx,
6745 )
6746 .await;
6747
6748 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
6749 cx.simulate_keystroke(".");
6750 let completion_item = lsp::CompletionItem {
6751 label: "Some".into(),
6752 kind: Some(lsp::CompletionItemKind::SNIPPET),
6753 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
6754 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
6755 kind: lsp::MarkupKind::Markdown,
6756 value: "```rust\nSome(2)\n```".to_string(),
6757 })),
6758 deprecated: Some(false),
6759 sort_text: Some("Some".to_string()),
6760 filter_text: Some("Some".to_string()),
6761 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
6762 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6763 range: lsp::Range {
6764 start: lsp::Position {
6765 line: 0,
6766 character: 22,
6767 },
6768 end: lsp::Position {
6769 line: 0,
6770 character: 22,
6771 },
6772 },
6773 new_text: "Some(2)".to_string(),
6774 })),
6775 additional_text_edits: Some(vec![lsp::TextEdit {
6776 range: lsp::Range {
6777 start: lsp::Position {
6778 line: 0,
6779 character: 20,
6780 },
6781 end: lsp::Position {
6782 line: 0,
6783 character: 22,
6784 },
6785 },
6786 new_text: "".to_string(),
6787 }]),
6788 ..Default::default()
6789 };
6790
6791 let closure_completion_item = completion_item.clone();
6792 let counter = Arc::new(AtomicUsize::new(0));
6793 let counter_clone = counter.clone();
6794 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
6795 let task_completion_item = closure_completion_item.clone();
6796 counter_clone.fetch_add(1, atomic::Ordering::Release);
6797 async move {
6798 Ok(Some(lsp::CompletionResponse::Array(vec![
6799 task_completion_item,
6800 ])))
6801 }
6802 });
6803
6804 cx.condition(|editor, _| editor.context_menu_visible())
6805 .await;
6806 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
6807 assert!(request.next().await.is_some());
6808 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
6809
6810 cx.simulate_keystroke("S");
6811 cx.simulate_keystroke("o");
6812 cx.simulate_keystroke("m");
6813 cx.condition(|editor, _| editor.context_menu_visible())
6814 .await;
6815 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
6816 assert!(request.next().await.is_some());
6817 assert!(request.next().await.is_some());
6818 assert!(request.next().await.is_some());
6819 request.close();
6820 assert!(request.next().await.is_none());
6821 assert_eq!(
6822 counter.load(atomic::Ordering::Acquire),
6823 4,
6824 "With the completions menu open, only one LSP request should happen per input"
6825 );
6826}
6827
6828#[gpui::test]
6829async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
6830 init_test(cx, |_| {});
6831 let mut cx = EditorTestContext::new(cx).await;
6832 let language = Arc::new(Language::new(
6833 LanguageConfig {
6834 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
6835 ..Default::default()
6836 },
6837 Some(tree_sitter_rust::language()),
6838 ));
6839 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
6840
6841 // If multiple selections intersect a line, the line is only toggled once.
6842 cx.set_state(indoc! {"
6843 fn a() {
6844 «//b();
6845 ˇ»// «c();
6846 //ˇ» d();
6847 }
6848 "});
6849
6850 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6851
6852 cx.assert_editor_state(indoc! {"
6853 fn a() {
6854 «b();
6855 c();
6856 ˇ» d();
6857 }
6858 "});
6859
6860 // The comment prefix is inserted at the same column for every line in a
6861 // selection.
6862 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6863
6864 cx.assert_editor_state(indoc! {"
6865 fn a() {
6866 // «b();
6867 // c();
6868 ˇ»// d();
6869 }
6870 "});
6871
6872 // If a selection ends at the beginning of a line, that line is not toggled.
6873 cx.set_selections_state(indoc! {"
6874 fn a() {
6875 // b();
6876 «// c();
6877 ˇ» // d();
6878 }
6879 "});
6880
6881 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6882
6883 cx.assert_editor_state(indoc! {"
6884 fn a() {
6885 // b();
6886 «c();
6887 ˇ» // d();
6888 }
6889 "});
6890
6891 // If a selection span a single line and is empty, the line is toggled.
6892 cx.set_state(indoc! {"
6893 fn a() {
6894 a();
6895 b();
6896 ˇ
6897 }
6898 "});
6899
6900 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6901
6902 cx.assert_editor_state(indoc! {"
6903 fn a() {
6904 a();
6905 b();
6906 //•ˇ
6907 }
6908 "});
6909
6910 // If a selection span multiple lines, empty lines are not toggled.
6911 cx.set_state(indoc! {"
6912 fn a() {
6913 «a();
6914
6915 c();ˇ»
6916 }
6917 "});
6918
6919 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6920
6921 cx.assert_editor_state(indoc! {"
6922 fn a() {
6923 // «a();
6924
6925 // c();ˇ»
6926 }
6927 "});
6928
6929 // If a selection includes multiple comment prefixes, all lines are uncommented.
6930 cx.set_state(indoc! {"
6931 fn a() {
6932 «// a();
6933 /// b();
6934 //! c();ˇ»
6935 }
6936 "});
6937
6938 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6939
6940 cx.assert_editor_state(indoc! {"
6941 fn a() {
6942 «a();
6943 b();
6944 c();ˇ»
6945 }
6946 "});
6947}
6948
6949#[gpui::test]
6950async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
6951 init_test(cx, |_| {});
6952
6953 let language = Arc::new(Language::new(
6954 LanguageConfig {
6955 line_comments: vec!["// ".into()],
6956 ..Default::default()
6957 },
6958 Some(tree_sitter_rust::language()),
6959 ));
6960
6961 let mut cx = EditorTestContext::new(cx).await;
6962
6963 cx.language_registry().add(language.clone());
6964 cx.update_buffer(|buffer, cx| {
6965 buffer.set_language(Some(language), cx);
6966 });
6967
6968 let toggle_comments = &ToggleComments {
6969 advance_downwards: true,
6970 };
6971
6972 // Single cursor on one line -> advance
6973 // Cursor moves horizontally 3 characters as well on non-blank line
6974 cx.set_state(indoc!(
6975 "fn a() {
6976 ˇdog();
6977 cat();
6978 }"
6979 ));
6980 cx.update_editor(|editor, cx| {
6981 editor.toggle_comments(toggle_comments, cx);
6982 });
6983 cx.assert_editor_state(indoc!(
6984 "fn a() {
6985 // dog();
6986 catˇ();
6987 }"
6988 ));
6989
6990 // Single selection on one line -> don't advance
6991 cx.set_state(indoc!(
6992 "fn a() {
6993 «dog()ˇ»;
6994 cat();
6995 }"
6996 ));
6997 cx.update_editor(|editor, cx| {
6998 editor.toggle_comments(toggle_comments, cx);
6999 });
7000 cx.assert_editor_state(indoc!(
7001 "fn a() {
7002 // «dog()ˇ»;
7003 cat();
7004 }"
7005 ));
7006
7007 // Multiple cursors on one line -> advance
7008 cx.set_state(indoc!(
7009 "fn a() {
7010 ˇdˇog();
7011 cat();
7012 }"
7013 ));
7014 cx.update_editor(|editor, cx| {
7015 editor.toggle_comments(toggle_comments, cx);
7016 });
7017 cx.assert_editor_state(indoc!(
7018 "fn a() {
7019 // dog();
7020 catˇ(ˇ);
7021 }"
7022 ));
7023
7024 // Multiple cursors on one line, with selection -> don't advance
7025 cx.set_state(indoc!(
7026 "fn a() {
7027 ˇdˇog«()ˇ»;
7028 cat();
7029 }"
7030 ));
7031 cx.update_editor(|editor, cx| {
7032 editor.toggle_comments(toggle_comments, cx);
7033 });
7034 cx.assert_editor_state(indoc!(
7035 "fn a() {
7036 // ˇdˇog«()ˇ»;
7037 cat();
7038 }"
7039 ));
7040
7041 // Single cursor on one line -> advance
7042 // Cursor moves to column 0 on blank line
7043 cx.set_state(indoc!(
7044 "fn a() {
7045 ˇdog();
7046
7047 cat();
7048 }"
7049 ));
7050 cx.update_editor(|editor, cx| {
7051 editor.toggle_comments(toggle_comments, cx);
7052 });
7053 cx.assert_editor_state(indoc!(
7054 "fn a() {
7055 // dog();
7056 ˇ
7057 cat();
7058 }"
7059 ));
7060
7061 // Single cursor on one line -> advance
7062 // Cursor starts and ends at column 0
7063 cx.set_state(indoc!(
7064 "fn a() {
7065 ˇ dog();
7066 cat();
7067 }"
7068 ));
7069 cx.update_editor(|editor, cx| {
7070 editor.toggle_comments(toggle_comments, cx);
7071 });
7072 cx.assert_editor_state(indoc!(
7073 "fn a() {
7074 // dog();
7075 ˇ cat();
7076 }"
7077 ));
7078}
7079
7080#[gpui::test]
7081async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
7082 init_test(cx, |_| {});
7083
7084 let mut cx = EditorTestContext::new(cx).await;
7085
7086 let html_language = Arc::new(
7087 Language::new(
7088 LanguageConfig {
7089 name: "HTML".into(),
7090 block_comment: Some(("<!-- ".into(), " -->".into())),
7091 ..Default::default()
7092 },
7093 Some(tree_sitter_html::language()),
7094 )
7095 .with_injection_query(
7096 r#"
7097 (script_element
7098 (raw_text) @content
7099 (#set! "language" "javascript"))
7100 "#,
7101 )
7102 .unwrap(),
7103 );
7104
7105 let javascript_language = Arc::new(Language::new(
7106 LanguageConfig {
7107 name: "JavaScript".into(),
7108 line_comments: vec!["// ".into()],
7109 ..Default::default()
7110 },
7111 Some(tree_sitter_typescript::language_tsx()),
7112 ));
7113
7114 cx.language_registry().add(html_language.clone());
7115 cx.language_registry().add(javascript_language.clone());
7116 cx.update_buffer(|buffer, cx| {
7117 buffer.set_language(Some(html_language), cx);
7118 });
7119
7120 // Toggle comments for empty selections
7121 cx.set_state(
7122 &r#"
7123 <p>A</p>ˇ
7124 <p>B</p>ˇ
7125 <p>C</p>ˇ
7126 "#
7127 .unindent(),
7128 );
7129 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7130 cx.assert_editor_state(
7131 &r#"
7132 <!-- <p>A</p>ˇ -->
7133 <!-- <p>B</p>ˇ -->
7134 <!-- <p>C</p>ˇ -->
7135 "#
7136 .unindent(),
7137 );
7138 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7139 cx.assert_editor_state(
7140 &r#"
7141 <p>A</p>ˇ
7142 <p>B</p>ˇ
7143 <p>C</p>ˇ
7144 "#
7145 .unindent(),
7146 );
7147
7148 // Toggle comments for mixture of empty and non-empty selections, where
7149 // multiple selections occupy a given line.
7150 cx.set_state(
7151 &r#"
7152 <p>A«</p>
7153 <p>ˇ»B</p>ˇ
7154 <p>C«</p>
7155 <p>ˇ»D</p>ˇ
7156 "#
7157 .unindent(),
7158 );
7159
7160 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7161 cx.assert_editor_state(
7162 &r#"
7163 <!-- <p>A«</p>
7164 <p>ˇ»B</p>ˇ -->
7165 <!-- <p>C«</p>
7166 <p>ˇ»D</p>ˇ -->
7167 "#
7168 .unindent(),
7169 );
7170 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7171 cx.assert_editor_state(
7172 &r#"
7173 <p>A«</p>
7174 <p>ˇ»B</p>ˇ
7175 <p>C«</p>
7176 <p>ˇ»D</p>ˇ
7177 "#
7178 .unindent(),
7179 );
7180
7181 // Toggle comments when different languages are active for different
7182 // selections.
7183 cx.set_state(
7184 &r#"
7185 ˇ<script>
7186 ˇvar x = new Y();
7187 ˇ</script>
7188 "#
7189 .unindent(),
7190 );
7191 cx.executor().run_until_parked();
7192 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7193 cx.assert_editor_state(
7194 &r#"
7195 <!-- ˇ<script> -->
7196 // ˇvar x = new Y();
7197 <!-- ˇ</script> -->
7198 "#
7199 .unindent(),
7200 );
7201}
7202
7203#[gpui::test]
7204fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
7205 init_test(cx, |_| {});
7206
7207 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7208 let multibuffer = cx.new_model(|cx| {
7209 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7210 multibuffer.push_excerpts(
7211 buffer.clone(),
7212 [
7213 ExcerptRange {
7214 context: Point::new(0, 0)..Point::new(0, 4),
7215 primary: None,
7216 },
7217 ExcerptRange {
7218 context: Point::new(1, 0)..Point::new(1, 4),
7219 primary: None,
7220 },
7221 ],
7222 cx,
7223 );
7224 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
7225 multibuffer
7226 });
7227
7228 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7229 _ = view.update(cx, |view, cx| {
7230 assert_eq!(view.text(cx), "aaaa\nbbbb");
7231 view.change_selections(None, cx, |s| {
7232 s.select_ranges([
7233 Point::new(0, 0)..Point::new(0, 0),
7234 Point::new(1, 0)..Point::new(1, 0),
7235 ])
7236 });
7237
7238 view.handle_input("X", cx);
7239 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
7240 assert_eq!(
7241 view.selections.ranges(cx),
7242 [
7243 Point::new(0, 1)..Point::new(0, 1),
7244 Point::new(1, 1)..Point::new(1, 1),
7245 ]
7246 );
7247
7248 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
7249 view.change_selections(None, cx, |s| {
7250 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
7251 });
7252 view.backspace(&Default::default(), cx);
7253 assert_eq!(view.text(cx), "Xa\nbbb");
7254 assert_eq!(
7255 view.selections.ranges(cx),
7256 [Point::new(1, 0)..Point::new(1, 0)]
7257 );
7258
7259 view.change_selections(None, cx, |s| {
7260 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
7261 });
7262 view.backspace(&Default::default(), cx);
7263 assert_eq!(view.text(cx), "X\nbb");
7264 assert_eq!(
7265 view.selections.ranges(cx),
7266 [Point::new(0, 1)..Point::new(0, 1)]
7267 );
7268 });
7269}
7270
7271#[gpui::test]
7272fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
7273 init_test(cx, |_| {});
7274
7275 let markers = vec![('[', ']').into(), ('(', ')').into()];
7276 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
7277 indoc! {"
7278 [aaaa
7279 (bbbb]
7280 cccc)",
7281 },
7282 markers.clone(),
7283 );
7284 let excerpt_ranges = markers.into_iter().map(|marker| {
7285 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
7286 ExcerptRange {
7287 context,
7288 primary: None,
7289 }
7290 });
7291 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
7292 let multibuffer = cx.new_model(|cx| {
7293 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7294 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
7295 multibuffer
7296 });
7297
7298 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7299 _ = view.update(cx, |view, cx| {
7300 let (expected_text, selection_ranges) = marked_text_ranges(
7301 indoc! {"
7302 aaaa
7303 bˇbbb
7304 bˇbbˇb
7305 cccc"
7306 },
7307 true,
7308 );
7309 assert_eq!(view.text(cx), expected_text);
7310 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
7311
7312 view.handle_input("X", cx);
7313
7314 let (expected_text, expected_selections) = marked_text_ranges(
7315 indoc! {"
7316 aaaa
7317 bXˇbbXb
7318 bXˇbbXˇb
7319 cccc"
7320 },
7321 false,
7322 );
7323 assert_eq!(view.text(cx), expected_text);
7324 assert_eq!(view.selections.ranges(cx), expected_selections);
7325
7326 view.newline(&Newline, cx);
7327 let (expected_text, expected_selections) = marked_text_ranges(
7328 indoc! {"
7329 aaaa
7330 bX
7331 ˇbbX
7332 b
7333 bX
7334 ˇbbX
7335 ˇb
7336 cccc"
7337 },
7338 false,
7339 );
7340 assert_eq!(view.text(cx), expected_text);
7341 assert_eq!(view.selections.ranges(cx), expected_selections);
7342 });
7343}
7344
7345#[gpui::test]
7346fn test_refresh_selections(cx: &mut TestAppContext) {
7347 init_test(cx, |_| {});
7348
7349 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7350 let mut excerpt1_id = None;
7351 let multibuffer = cx.new_model(|cx| {
7352 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7353 excerpt1_id = multibuffer
7354 .push_excerpts(
7355 buffer.clone(),
7356 [
7357 ExcerptRange {
7358 context: Point::new(0, 0)..Point::new(1, 4),
7359 primary: None,
7360 },
7361 ExcerptRange {
7362 context: Point::new(1, 0)..Point::new(2, 4),
7363 primary: None,
7364 },
7365 ],
7366 cx,
7367 )
7368 .into_iter()
7369 .next();
7370 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7371 multibuffer
7372 });
7373
7374 let editor = cx.add_window(|cx| {
7375 let mut editor = build_editor(multibuffer.clone(), cx);
7376 let snapshot = editor.snapshot(cx);
7377 editor.change_selections(None, cx, |s| {
7378 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
7379 });
7380 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
7381 assert_eq!(
7382 editor.selections.ranges(cx),
7383 [
7384 Point::new(1, 3)..Point::new(1, 3),
7385 Point::new(2, 1)..Point::new(2, 1),
7386 ]
7387 );
7388 editor
7389 });
7390
7391 // Refreshing selections is a no-op when excerpts haven't changed.
7392 _ = editor.update(cx, |editor, cx| {
7393 editor.change_selections(None, cx, |s| s.refresh());
7394 assert_eq!(
7395 editor.selections.ranges(cx),
7396 [
7397 Point::new(1, 3)..Point::new(1, 3),
7398 Point::new(2, 1)..Point::new(2, 1),
7399 ]
7400 );
7401 });
7402
7403 _ = multibuffer.update(cx, |multibuffer, cx| {
7404 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7405 });
7406 _ = editor.update(cx, |editor, cx| {
7407 // Removing an excerpt causes the first selection to become degenerate.
7408 assert_eq!(
7409 editor.selections.ranges(cx),
7410 [
7411 Point::new(0, 0)..Point::new(0, 0),
7412 Point::new(0, 1)..Point::new(0, 1)
7413 ]
7414 );
7415
7416 // Refreshing selections will relocate the first selection to the original buffer
7417 // location.
7418 editor.change_selections(None, cx, |s| s.refresh());
7419 assert_eq!(
7420 editor.selections.ranges(cx),
7421 [
7422 Point::new(0, 1)..Point::new(0, 1),
7423 Point::new(0, 3)..Point::new(0, 3)
7424 ]
7425 );
7426 assert!(editor.selections.pending_anchor().is_some());
7427 });
7428}
7429
7430#[gpui::test]
7431fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
7432 init_test(cx, |_| {});
7433
7434 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7435 let mut excerpt1_id = None;
7436 let multibuffer = cx.new_model(|cx| {
7437 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7438 excerpt1_id = multibuffer
7439 .push_excerpts(
7440 buffer.clone(),
7441 [
7442 ExcerptRange {
7443 context: Point::new(0, 0)..Point::new(1, 4),
7444 primary: None,
7445 },
7446 ExcerptRange {
7447 context: Point::new(1, 0)..Point::new(2, 4),
7448 primary: None,
7449 },
7450 ],
7451 cx,
7452 )
7453 .into_iter()
7454 .next();
7455 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7456 multibuffer
7457 });
7458
7459 let editor = cx.add_window(|cx| {
7460 let mut editor = build_editor(multibuffer.clone(), cx);
7461 let snapshot = editor.snapshot(cx);
7462 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
7463 assert_eq!(
7464 editor.selections.ranges(cx),
7465 [Point::new(1, 3)..Point::new(1, 3)]
7466 );
7467 editor
7468 });
7469
7470 _ = multibuffer.update(cx, |multibuffer, cx| {
7471 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7472 });
7473 _ = editor.update(cx, |editor, cx| {
7474 assert_eq!(
7475 editor.selections.ranges(cx),
7476 [Point::new(0, 0)..Point::new(0, 0)]
7477 );
7478
7479 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
7480 editor.change_selections(None, cx, |s| s.refresh());
7481 assert_eq!(
7482 editor.selections.ranges(cx),
7483 [Point::new(0, 3)..Point::new(0, 3)]
7484 );
7485 assert!(editor.selections.pending_anchor().is_some());
7486 });
7487}
7488
7489#[gpui::test]
7490async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
7491 init_test(cx, |_| {});
7492
7493 let language = Arc::new(
7494 Language::new(
7495 LanguageConfig {
7496 brackets: BracketPairConfig {
7497 pairs: vec![
7498 BracketPair {
7499 start: "{".to_string(),
7500 end: "}".to_string(),
7501 close: true,
7502 newline: true,
7503 },
7504 BracketPair {
7505 start: "/* ".to_string(),
7506 end: " */".to_string(),
7507 close: true,
7508 newline: true,
7509 },
7510 ],
7511 ..Default::default()
7512 },
7513 ..Default::default()
7514 },
7515 Some(tree_sitter_rust::language()),
7516 )
7517 .with_indents_query("")
7518 .unwrap(),
7519 );
7520
7521 let text = concat!(
7522 "{ }\n", //
7523 " x\n", //
7524 " /* */\n", //
7525 "x\n", //
7526 "{{} }\n", //
7527 );
7528
7529 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
7530 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7531 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7532 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
7533 .await;
7534
7535 _ = view.update(cx, |view, cx| {
7536 view.change_selections(None, cx, |s| {
7537 s.select_display_ranges([
7538 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
7539 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
7540 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
7541 ])
7542 });
7543 view.newline(&Newline, cx);
7544
7545 assert_eq!(
7546 view.buffer().read(cx).read(cx).text(),
7547 concat!(
7548 "{ \n", // Suppress rustfmt
7549 "\n", //
7550 "}\n", //
7551 " x\n", //
7552 " /* \n", //
7553 " \n", //
7554 " */\n", //
7555 "x\n", //
7556 "{{} \n", //
7557 "}\n", //
7558 )
7559 );
7560 });
7561}
7562
7563#[gpui::test]
7564fn test_highlighted_ranges(cx: &mut TestAppContext) {
7565 init_test(cx, |_| {});
7566
7567 let editor = cx.add_window(|cx| {
7568 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
7569 build_editor(buffer.clone(), cx)
7570 });
7571
7572 _ = editor.update(cx, |editor, cx| {
7573 struct Type1;
7574 struct Type2;
7575
7576 let buffer = editor.buffer.read(cx).snapshot(cx);
7577
7578 let anchor_range =
7579 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
7580
7581 editor.highlight_background::<Type1>(
7582 &[
7583 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
7584 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
7585 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
7586 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
7587 ],
7588 |_| Hsla::red(),
7589 cx,
7590 );
7591 editor.highlight_background::<Type2>(
7592 &[
7593 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
7594 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
7595 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
7596 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
7597 ],
7598 |_| Hsla::green(),
7599 cx,
7600 );
7601
7602 let snapshot = editor.snapshot(cx);
7603 let mut highlighted_ranges = editor.background_highlights_in_range(
7604 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7605 &snapshot,
7606 cx.theme().colors(),
7607 );
7608 // Enforce a consistent ordering based on color without relying on the ordering of the
7609 // highlight's `TypeId` which is non-executor.
7610 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7611 assert_eq!(
7612 highlighted_ranges,
7613 &[
7614 (
7615 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
7616 Hsla::red(),
7617 ),
7618 (
7619 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7620 Hsla::red(),
7621 ),
7622 (
7623 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
7624 Hsla::green(),
7625 ),
7626 (
7627 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
7628 Hsla::green(),
7629 ),
7630 ]
7631 );
7632 assert_eq!(
7633 editor.background_highlights_in_range(
7634 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7635 &snapshot,
7636 cx.theme().colors(),
7637 ),
7638 &[(
7639 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7640 Hsla::red(),
7641 )]
7642 );
7643 });
7644}
7645
7646#[gpui::test]
7647async fn test_following(cx: &mut gpui::TestAppContext) {
7648 init_test(cx, |_| {});
7649
7650 let fs = FakeFs::new(cx.executor());
7651 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7652
7653 let buffer = project.update(cx, |project, cx| {
7654 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
7655 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7656 });
7657 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7658 let follower = cx.update(|cx| {
7659 cx.open_window(
7660 WindowOptions {
7661 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
7662 gpui::Point::new(0.into(), 0.into()),
7663 gpui::Point::new(10.into(), 80.into()),
7664 ))),
7665 ..Default::default()
7666 },
7667 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7668 )
7669 .unwrap()
7670 });
7671
7672 let is_still_following = Rc::new(RefCell::new(true));
7673 let follower_edit_event_count = Rc::new(RefCell::new(0));
7674 let pending_update = Rc::new(RefCell::new(None));
7675 _ = follower.update(cx, {
7676 let update = pending_update.clone();
7677 let is_still_following = is_still_following.clone();
7678 let follower_edit_event_count = follower_edit_event_count.clone();
7679 |_, cx| {
7680 cx.subscribe(
7681 &leader.root_view(cx).unwrap(),
7682 move |_, leader, event, cx| {
7683 leader
7684 .read(cx)
7685 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7686 },
7687 )
7688 .detach();
7689
7690 cx.subscribe(
7691 &follower.root_view(cx).unwrap(),
7692 move |_, _, event: &EditorEvent, _cx| {
7693 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7694 *is_still_following.borrow_mut() = false;
7695 }
7696
7697 if let EditorEvent::BufferEdited = event {
7698 *follower_edit_event_count.borrow_mut() += 1;
7699 }
7700 },
7701 )
7702 .detach();
7703 }
7704 });
7705
7706 // Update the selections only
7707 _ = leader.update(cx, |leader, cx| {
7708 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7709 });
7710 follower
7711 .update(cx, |follower, cx| {
7712 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7713 })
7714 .unwrap()
7715 .await
7716 .unwrap();
7717 _ = follower.update(cx, |follower, cx| {
7718 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
7719 });
7720 assert_eq!(*is_still_following.borrow(), true);
7721 assert_eq!(*follower_edit_event_count.borrow(), 0);
7722
7723 // Update the scroll position only
7724 _ = leader.update(cx, |leader, cx| {
7725 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7726 });
7727 follower
7728 .update(cx, |follower, cx| {
7729 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7730 })
7731 .unwrap()
7732 .await
7733 .unwrap();
7734 assert_eq!(
7735 follower
7736 .update(cx, |follower, cx| follower.scroll_position(cx))
7737 .unwrap(),
7738 gpui::Point::new(1.5, 3.5)
7739 );
7740 assert_eq!(*is_still_following.borrow(), true);
7741 assert_eq!(*follower_edit_event_count.borrow(), 0);
7742
7743 // Update the selections and scroll position. The follower's scroll position is updated
7744 // via autoscroll, not via the leader's exact scroll position.
7745 _ = leader.update(cx, |leader, cx| {
7746 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
7747 leader.request_autoscroll(Autoscroll::newest(), cx);
7748 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7749 });
7750 follower
7751 .update(cx, |follower, cx| {
7752 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7753 })
7754 .unwrap()
7755 .await
7756 .unwrap();
7757 _ = follower.update(cx, |follower, cx| {
7758 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
7759 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
7760 });
7761 assert_eq!(*is_still_following.borrow(), true);
7762
7763 // Creating a pending selection that precedes another selection
7764 _ = leader.update(cx, |leader, cx| {
7765 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7766 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
7767 });
7768 follower
7769 .update(cx, |follower, cx| {
7770 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7771 })
7772 .unwrap()
7773 .await
7774 .unwrap();
7775 _ = follower.update(cx, |follower, cx| {
7776 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
7777 });
7778 assert_eq!(*is_still_following.borrow(), true);
7779
7780 // Extend the pending selection so that it surrounds another selection
7781 _ = leader.update(cx, |leader, cx| {
7782 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
7783 });
7784 follower
7785 .update(cx, |follower, cx| {
7786 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7787 })
7788 .unwrap()
7789 .await
7790 .unwrap();
7791 _ = follower.update(cx, |follower, cx| {
7792 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7793 });
7794
7795 // Scrolling locally breaks the follow
7796 _ = follower.update(cx, |follower, cx| {
7797 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7798 follower.set_scroll_anchor(
7799 ScrollAnchor {
7800 anchor: top_anchor,
7801 offset: gpui::Point::new(0.0, 0.5),
7802 },
7803 cx,
7804 );
7805 });
7806 assert_eq!(*is_still_following.borrow(), false);
7807}
7808
7809#[gpui::test]
7810async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7811 init_test(cx, |_| {});
7812
7813 let fs = FakeFs::new(cx.executor());
7814 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7815 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7816 let pane = workspace
7817 .update(cx, |workspace, _| workspace.active_pane().clone())
7818 .unwrap();
7819
7820 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7821
7822 let leader = pane.update(cx, |_, cx| {
7823 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7824 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7825 });
7826
7827 // Start following the editor when it has no excerpts.
7828 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7829 let follower_1 = cx
7830 .update_window(*workspace.deref(), |_, cx| {
7831 Editor::from_state_proto(
7832 pane.clone(),
7833 workspace.root_view(cx).unwrap(),
7834 ViewId {
7835 creator: Default::default(),
7836 id: 0,
7837 },
7838 &mut state_message,
7839 cx,
7840 )
7841 })
7842 .unwrap()
7843 .unwrap()
7844 .await
7845 .unwrap();
7846
7847 let update_message = Rc::new(RefCell::new(None));
7848 follower_1.update(cx, {
7849 let update = update_message.clone();
7850 |_, cx| {
7851 cx.subscribe(&leader, move |_, leader, event, cx| {
7852 leader
7853 .read(cx)
7854 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7855 })
7856 .detach();
7857 }
7858 });
7859
7860 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7861 (
7862 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
7863 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
7864 )
7865 });
7866
7867 // Insert some excerpts.
7868 _ = leader.update(cx, |leader, cx| {
7869 leader.buffer.update(cx, |multibuffer, cx| {
7870 let excerpt_ids = multibuffer.push_excerpts(
7871 buffer_1.clone(),
7872 [
7873 ExcerptRange {
7874 context: 1..6,
7875 primary: None,
7876 },
7877 ExcerptRange {
7878 context: 12..15,
7879 primary: None,
7880 },
7881 ExcerptRange {
7882 context: 0..3,
7883 primary: None,
7884 },
7885 ],
7886 cx,
7887 );
7888 multibuffer.insert_excerpts_after(
7889 excerpt_ids[0],
7890 buffer_2.clone(),
7891 [
7892 ExcerptRange {
7893 context: 8..12,
7894 primary: None,
7895 },
7896 ExcerptRange {
7897 context: 0..6,
7898 primary: None,
7899 },
7900 ],
7901 cx,
7902 );
7903 });
7904 });
7905
7906 // Apply the update of adding the excerpts.
7907 follower_1
7908 .update(cx, |follower, cx| {
7909 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7910 })
7911 .await
7912 .unwrap();
7913 assert_eq!(
7914 follower_1.update(cx, |editor, cx| editor.text(cx)),
7915 leader.update(cx, |editor, cx| editor.text(cx))
7916 );
7917 update_message.borrow_mut().take();
7918
7919 // Start following separately after it already has excerpts.
7920 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7921 let follower_2 = cx
7922 .update_window(*workspace.deref(), |_, cx| {
7923 Editor::from_state_proto(
7924 pane.clone(),
7925 workspace.root_view(cx).unwrap().clone(),
7926 ViewId {
7927 creator: Default::default(),
7928 id: 0,
7929 },
7930 &mut state_message,
7931 cx,
7932 )
7933 })
7934 .unwrap()
7935 .unwrap()
7936 .await
7937 .unwrap();
7938 assert_eq!(
7939 follower_2.update(cx, |editor, cx| editor.text(cx)),
7940 leader.update(cx, |editor, cx| editor.text(cx))
7941 );
7942
7943 // Remove some excerpts.
7944 _ = leader.update(cx, |leader, cx| {
7945 leader.buffer.update(cx, |multibuffer, cx| {
7946 let excerpt_ids = multibuffer.excerpt_ids();
7947 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7948 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7949 });
7950 });
7951
7952 // Apply the update of removing the excerpts.
7953 follower_1
7954 .update(cx, |follower, cx| {
7955 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7956 })
7957 .await
7958 .unwrap();
7959 follower_2
7960 .update(cx, |follower, cx| {
7961 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7962 })
7963 .await
7964 .unwrap();
7965 update_message.borrow_mut().take();
7966 assert_eq!(
7967 follower_1.update(cx, |editor, cx| editor.text(cx)),
7968 leader.update(cx, |editor, cx| editor.text(cx))
7969 );
7970}
7971
7972#[gpui::test]
7973async fn go_to_prev_overlapping_diagnostic(
7974 executor: BackgroundExecutor,
7975 cx: &mut gpui::TestAppContext,
7976) {
7977 init_test(cx, |_| {});
7978
7979 let mut cx = EditorTestContext::new(cx).await;
7980 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7981
7982 cx.set_state(indoc! {"
7983 ˇfn func(abc def: i32) -> u32 {
7984 }
7985 "});
7986
7987 _ = cx.update(|cx| {
7988 _ = project.update(cx, |project, cx| {
7989 project
7990 .update_diagnostics(
7991 LanguageServerId(0),
7992 lsp::PublishDiagnosticsParams {
7993 uri: lsp::Url::from_file_path("/root/file").unwrap(),
7994 version: None,
7995 diagnostics: vec![
7996 lsp::Diagnostic {
7997 range: lsp::Range::new(
7998 lsp::Position::new(0, 11),
7999 lsp::Position::new(0, 12),
8000 ),
8001 severity: Some(lsp::DiagnosticSeverity::ERROR),
8002 ..Default::default()
8003 },
8004 lsp::Diagnostic {
8005 range: lsp::Range::new(
8006 lsp::Position::new(0, 12),
8007 lsp::Position::new(0, 15),
8008 ),
8009 severity: Some(lsp::DiagnosticSeverity::ERROR),
8010 ..Default::default()
8011 },
8012 lsp::Diagnostic {
8013 range: lsp::Range::new(
8014 lsp::Position::new(0, 25),
8015 lsp::Position::new(0, 28),
8016 ),
8017 severity: Some(lsp::DiagnosticSeverity::ERROR),
8018 ..Default::default()
8019 },
8020 ],
8021 },
8022 &[],
8023 cx,
8024 )
8025 .unwrap()
8026 });
8027 });
8028
8029 executor.run_until_parked();
8030
8031 cx.update_editor(|editor, cx| {
8032 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8033 });
8034
8035 cx.assert_editor_state(indoc! {"
8036 fn func(abc def: i32) -> ˇu32 {
8037 }
8038 "});
8039
8040 cx.update_editor(|editor, cx| {
8041 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8042 });
8043
8044 cx.assert_editor_state(indoc! {"
8045 fn func(abc ˇdef: i32) -> u32 {
8046 }
8047 "});
8048
8049 cx.update_editor(|editor, cx| {
8050 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8051 });
8052
8053 cx.assert_editor_state(indoc! {"
8054 fn func(abcˇ def: i32) -> u32 {
8055 }
8056 "});
8057
8058 cx.update_editor(|editor, cx| {
8059 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8060 });
8061
8062 cx.assert_editor_state(indoc! {"
8063 fn func(abc def: i32) -> ˇu32 {
8064 }
8065 "});
8066}
8067
8068#[gpui::test]
8069async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
8070 init_test(cx, |_| {});
8071
8072 let mut cx = EditorTestContext::new(cx).await;
8073
8074 let diff_base = r#"
8075 use some::mod;
8076
8077 const A: u32 = 42;
8078
8079 fn main() {
8080 println!("hello");
8081
8082 println!("world");
8083 }
8084 "#
8085 .unindent();
8086
8087 // Edits are modified, removed, modified, added
8088 cx.set_state(
8089 &r#"
8090 use some::modified;
8091
8092 ˇ
8093 fn main() {
8094 println!("hello there");
8095
8096 println!("around the");
8097 println!("world");
8098 }
8099 "#
8100 .unindent(),
8101 );
8102
8103 cx.set_diff_base(Some(&diff_base));
8104 executor.run_until_parked();
8105
8106 cx.update_editor(|editor, cx| {
8107 //Wrap around the bottom of the buffer
8108 for _ in 0..3 {
8109 editor.go_to_hunk(&GoToHunk, cx);
8110 }
8111 });
8112
8113 cx.assert_editor_state(
8114 &r#"
8115 ˇuse some::modified;
8116
8117
8118 fn main() {
8119 println!("hello there");
8120
8121 println!("around the");
8122 println!("world");
8123 }
8124 "#
8125 .unindent(),
8126 );
8127
8128 cx.update_editor(|editor, cx| {
8129 //Wrap around the top of the buffer
8130 for _ in 0..2 {
8131 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8132 }
8133 });
8134
8135 cx.assert_editor_state(
8136 &r#"
8137 use some::modified;
8138
8139
8140 fn main() {
8141 ˇ println!("hello there");
8142
8143 println!("around the");
8144 println!("world");
8145 }
8146 "#
8147 .unindent(),
8148 );
8149
8150 cx.update_editor(|editor, cx| {
8151 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8152 });
8153
8154 cx.assert_editor_state(
8155 &r#"
8156 use some::modified;
8157
8158 ˇ
8159 fn main() {
8160 println!("hello there");
8161
8162 println!("around the");
8163 println!("world");
8164 }
8165 "#
8166 .unindent(),
8167 );
8168
8169 cx.update_editor(|editor, cx| {
8170 for _ in 0..3 {
8171 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8172 }
8173 });
8174
8175 cx.assert_editor_state(
8176 &r#"
8177 use some::modified;
8178
8179
8180 fn main() {
8181 ˇ println!("hello there");
8182
8183 println!("around the");
8184 println!("world");
8185 }
8186 "#
8187 .unindent(),
8188 );
8189
8190 cx.update_editor(|editor, cx| {
8191 editor.fold(&Fold, cx);
8192
8193 //Make sure that the fold only gets one hunk
8194 for _ in 0..4 {
8195 editor.go_to_hunk(&GoToHunk, cx);
8196 }
8197 });
8198
8199 cx.assert_editor_state(
8200 &r#"
8201 ˇuse some::modified;
8202
8203
8204 fn main() {
8205 println!("hello there");
8206
8207 println!("around the");
8208 println!("world");
8209 }
8210 "#
8211 .unindent(),
8212 );
8213}
8214
8215#[test]
8216fn test_split_words() {
8217 fn split(text: &str) -> Vec<&str> {
8218 split_words(text).collect()
8219 }
8220
8221 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
8222 assert_eq!(split("hello_world"), &["hello_", "world"]);
8223 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
8224 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
8225 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
8226 assert_eq!(split("helloworld"), &["helloworld"]);
8227
8228 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
8229}
8230
8231#[gpui::test]
8232async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
8233 init_test(cx, |_| {});
8234
8235 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
8236 let mut assert = |before, after| {
8237 let _state_context = cx.set_state(before);
8238 cx.update_editor(|editor, cx| {
8239 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
8240 });
8241 cx.assert_editor_state(after);
8242 };
8243
8244 // Outside bracket jumps to outside of matching bracket
8245 assert("console.logˇ(var);", "console.log(var)ˇ;");
8246 assert("console.log(var)ˇ;", "console.logˇ(var);");
8247
8248 // Inside bracket jumps to inside of matching bracket
8249 assert("console.log(ˇvar);", "console.log(varˇ);");
8250 assert("console.log(varˇ);", "console.log(ˇvar);");
8251
8252 // When outside a bracket and inside, favor jumping to the inside bracket
8253 assert(
8254 "console.log('foo', [1, 2, 3]ˇ);",
8255 "console.log(ˇ'foo', [1, 2, 3]);",
8256 );
8257 assert(
8258 "console.log(ˇ'foo', [1, 2, 3]);",
8259 "console.log('foo', [1, 2, 3]ˇ);",
8260 );
8261
8262 // Bias forward if two options are equally likely
8263 assert(
8264 "let result = curried_fun()ˇ();",
8265 "let result = curried_fun()()ˇ;",
8266 );
8267
8268 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
8269 assert(
8270 indoc! {"
8271 function test() {
8272 console.log('test')ˇ
8273 }"},
8274 indoc! {"
8275 function test() {
8276 console.logˇ('test')
8277 }"},
8278 );
8279}
8280
8281#[gpui::test]
8282async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
8283 init_test(cx, |_| {});
8284
8285 let fs = FakeFs::new(cx.executor());
8286 fs.insert_tree(
8287 "/a",
8288 json!({
8289 "main.rs": "fn main() { let a = 5; }",
8290 "other.rs": "// Test file",
8291 }),
8292 )
8293 .await;
8294 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8295
8296 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8297 language_registry.add(Arc::new(Language::new(
8298 LanguageConfig {
8299 name: "Rust".into(),
8300 matcher: LanguageMatcher {
8301 path_suffixes: vec!["rs".to_string()],
8302 ..Default::default()
8303 },
8304 brackets: BracketPairConfig {
8305 pairs: vec![BracketPair {
8306 start: "{".to_string(),
8307 end: "}".to_string(),
8308 close: true,
8309 newline: true,
8310 }],
8311 disabled_scopes_by_bracket_ix: Vec::new(),
8312 },
8313 ..Default::default()
8314 },
8315 Some(tree_sitter_rust::language()),
8316 )));
8317 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8318 "Rust",
8319 FakeLspAdapter {
8320 capabilities: lsp::ServerCapabilities {
8321 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8322 first_trigger_character: "{".to_string(),
8323 more_trigger_character: None,
8324 }),
8325 ..Default::default()
8326 },
8327 ..Default::default()
8328 },
8329 );
8330
8331 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8332
8333 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8334
8335 let worktree_id = workspace
8336 .update(cx, |workspace, cx| {
8337 workspace.project().update(cx, |project, cx| {
8338 project.worktrees().next().unwrap().read(cx).id()
8339 })
8340 })
8341 .unwrap();
8342
8343 let buffer = project
8344 .update(cx, |project, cx| {
8345 project.open_local_buffer("/a/main.rs", cx)
8346 })
8347 .await
8348 .unwrap();
8349 cx.executor().run_until_parked();
8350 cx.executor().start_waiting();
8351 let fake_server = fake_servers.next().await.unwrap();
8352 let editor_handle = workspace
8353 .update(cx, |workspace, cx| {
8354 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8355 })
8356 .unwrap()
8357 .await
8358 .unwrap()
8359 .downcast::<Editor>()
8360 .unwrap();
8361
8362 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8363 assert_eq!(
8364 params.text_document_position.text_document.uri,
8365 lsp::Url::from_file_path("/a/main.rs").unwrap(),
8366 );
8367 assert_eq!(
8368 params.text_document_position.position,
8369 lsp::Position::new(0, 21),
8370 );
8371
8372 Ok(Some(vec![lsp::TextEdit {
8373 new_text: "]".to_string(),
8374 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8375 }]))
8376 });
8377
8378 editor_handle.update(cx, |editor, cx| {
8379 editor.focus(cx);
8380 editor.change_selections(None, cx, |s| {
8381 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8382 });
8383 editor.handle_input("{", cx);
8384 });
8385
8386 cx.executor().run_until_parked();
8387
8388 _ = buffer.update(cx, |buffer, _| {
8389 assert_eq!(
8390 buffer.text(),
8391 "fn main() { let a = {5}; }",
8392 "No extra braces from on type formatting should appear in the buffer"
8393 )
8394 });
8395}
8396
8397#[gpui::test]
8398async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8399 init_test(cx, |_| {});
8400
8401 let fs = FakeFs::new(cx.executor());
8402 fs.insert_tree(
8403 "/a",
8404 json!({
8405 "main.rs": "fn main() { let a = 5; }",
8406 "other.rs": "// Test file",
8407 }),
8408 )
8409 .await;
8410
8411 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8412
8413 let server_restarts = Arc::new(AtomicUsize::new(0));
8414 let closure_restarts = Arc::clone(&server_restarts);
8415 let language_server_name = "test language server";
8416 let language_name: Arc<str> = "Rust".into();
8417
8418 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8419 language_registry.add(Arc::new(Language::new(
8420 LanguageConfig {
8421 name: Arc::clone(&language_name),
8422 matcher: LanguageMatcher {
8423 path_suffixes: vec!["rs".to_string()],
8424 ..Default::default()
8425 },
8426 ..Default::default()
8427 },
8428 Some(tree_sitter_rust::language()),
8429 )));
8430 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8431 "Rust",
8432 FakeLspAdapter {
8433 name: language_server_name,
8434 initialization_options: Some(json!({
8435 "testOptionValue": true
8436 })),
8437 initializer: Some(Box::new(move |fake_server| {
8438 let task_restarts = Arc::clone(&closure_restarts);
8439 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8440 task_restarts.fetch_add(1, atomic::Ordering::Release);
8441 futures::future::ready(Ok(()))
8442 });
8443 })),
8444 ..Default::default()
8445 },
8446 );
8447
8448 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8449 let _buffer = project
8450 .update(cx, |project, cx| {
8451 project.open_local_buffer("/a/main.rs", cx)
8452 })
8453 .await
8454 .unwrap();
8455 let _fake_server = fake_servers.next().await.unwrap();
8456 update_test_language_settings(cx, |language_settings| {
8457 language_settings.languages.insert(
8458 Arc::clone(&language_name),
8459 LanguageSettingsContent {
8460 tab_size: NonZeroU32::new(8),
8461 ..Default::default()
8462 },
8463 );
8464 });
8465 cx.executor().run_until_parked();
8466 assert_eq!(
8467 server_restarts.load(atomic::Ordering::Acquire),
8468 0,
8469 "Should not restart LSP server on an unrelated change"
8470 );
8471
8472 update_test_project_settings(cx, |project_settings| {
8473 project_settings.lsp.insert(
8474 "Some other server name".into(),
8475 LspSettings {
8476 binary: None,
8477 settings: None,
8478 initialization_options: Some(json!({
8479 "some other init value": false
8480 })),
8481 },
8482 );
8483 });
8484 cx.executor().run_until_parked();
8485 assert_eq!(
8486 server_restarts.load(atomic::Ordering::Acquire),
8487 0,
8488 "Should not restart LSP server on an unrelated LSP settings change"
8489 );
8490
8491 update_test_project_settings(cx, |project_settings| {
8492 project_settings.lsp.insert(
8493 language_server_name.into(),
8494 LspSettings {
8495 binary: None,
8496 settings: None,
8497 initialization_options: Some(json!({
8498 "anotherInitValue": false
8499 })),
8500 },
8501 );
8502 });
8503 cx.executor().run_until_parked();
8504 assert_eq!(
8505 server_restarts.load(atomic::Ordering::Acquire),
8506 1,
8507 "Should restart LSP server on a related LSP settings change"
8508 );
8509
8510 update_test_project_settings(cx, |project_settings| {
8511 project_settings.lsp.insert(
8512 language_server_name.into(),
8513 LspSettings {
8514 binary: None,
8515 settings: None,
8516 initialization_options: Some(json!({
8517 "anotherInitValue": false
8518 })),
8519 },
8520 );
8521 });
8522 cx.executor().run_until_parked();
8523 assert_eq!(
8524 server_restarts.load(atomic::Ordering::Acquire),
8525 1,
8526 "Should not restart LSP server on a related LSP settings change that is the same"
8527 );
8528
8529 update_test_project_settings(cx, |project_settings| {
8530 project_settings.lsp.insert(
8531 language_server_name.into(),
8532 LspSettings {
8533 binary: None,
8534 settings: None,
8535 initialization_options: None,
8536 },
8537 );
8538 });
8539 cx.executor().run_until_parked();
8540 assert_eq!(
8541 server_restarts.load(atomic::Ordering::Acquire),
8542 2,
8543 "Should restart LSP server on another related LSP settings change"
8544 );
8545}
8546
8547#[gpui::test]
8548async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8549 init_test(cx, |_| {});
8550
8551 let mut cx = EditorLspTestContext::new_rust(
8552 lsp::ServerCapabilities {
8553 completion_provider: Some(lsp::CompletionOptions {
8554 trigger_characters: Some(vec![".".to_string()]),
8555 resolve_provider: Some(true),
8556 ..Default::default()
8557 }),
8558 ..Default::default()
8559 },
8560 cx,
8561 )
8562 .await;
8563
8564 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8565 cx.simulate_keystroke(".");
8566 let completion_item = lsp::CompletionItem {
8567 label: "some".into(),
8568 kind: Some(lsp::CompletionItemKind::SNIPPET),
8569 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8570 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8571 kind: lsp::MarkupKind::Markdown,
8572 value: "```rust\nSome(2)\n```".to_string(),
8573 })),
8574 deprecated: Some(false),
8575 sort_text: Some("fffffff2".to_string()),
8576 filter_text: Some("some".to_string()),
8577 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8578 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8579 range: lsp::Range {
8580 start: lsp::Position {
8581 line: 0,
8582 character: 22,
8583 },
8584 end: lsp::Position {
8585 line: 0,
8586 character: 22,
8587 },
8588 },
8589 new_text: "Some(2)".to_string(),
8590 })),
8591 additional_text_edits: Some(vec![lsp::TextEdit {
8592 range: lsp::Range {
8593 start: lsp::Position {
8594 line: 0,
8595 character: 20,
8596 },
8597 end: lsp::Position {
8598 line: 0,
8599 character: 22,
8600 },
8601 },
8602 new_text: "".to_string(),
8603 }]),
8604 ..Default::default()
8605 };
8606
8607 let closure_completion_item = completion_item.clone();
8608 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8609 let task_completion_item = closure_completion_item.clone();
8610 async move {
8611 Ok(Some(lsp::CompletionResponse::Array(vec![
8612 task_completion_item,
8613 ])))
8614 }
8615 });
8616
8617 request.next().await;
8618
8619 cx.condition(|editor, _| editor.context_menu_visible())
8620 .await;
8621 let apply_additional_edits = cx.update_editor(|editor, cx| {
8622 editor
8623 .confirm_completion(&ConfirmCompletion::default(), cx)
8624 .unwrap()
8625 });
8626 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8627
8628 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8629 let task_completion_item = completion_item.clone();
8630 async move { Ok(task_completion_item) }
8631 })
8632 .next()
8633 .await
8634 .unwrap();
8635 apply_additional_edits.await.unwrap();
8636 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8637}
8638
8639#[gpui::test]
8640async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8641 init_test(cx, |_| {});
8642
8643 let mut cx = EditorLspTestContext::new(
8644 Language::new(
8645 LanguageConfig {
8646 matcher: LanguageMatcher {
8647 path_suffixes: vec!["jsx".into()],
8648 ..Default::default()
8649 },
8650 overrides: [(
8651 "element".into(),
8652 LanguageConfigOverride {
8653 word_characters: Override::Set(['-'].into_iter().collect()),
8654 ..Default::default()
8655 },
8656 )]
8657 .into_iter()
8658 .collect(),
8659 ..Default::default()
8660 },
8661 Some(tree_sitter_typescript::language_tsx()),
8662 )
8663 .with_override_query("(jsx_self_closing_element) @element")
8664 .unwrap(),
8665 lsp::ServerCapabilities {
8666 completion_provider: Some(lsp::CompletionOptions {
8667 trigger_characters: Some(vec![":".to_string()]),
8668 ..Default::default()
8669 }),
8670 ..Default::default()
8671 },
8672 cx,
8673 )
8674 .await;
8675
8676 cx.lsp
8677 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8678 Ok(Some(lsp::CompletionResponse::Array(vec![
8679 lsp::CompletionItem {
8680 label: "bg-blue".into(),
8681 ..Default::default()
8682 },
8683 lsp::CompletionItem {
8684 label: "bg-red".into(),
8685 ..Default::default()
8686 },
8687 lsp::CompletionItem {
8688 label: "bg-yellow".into(),
8689 ..Default::default()
8690 },
8691 ])))
8692 });
8693
8694 cx.set_state(r#"<p class="bgˇ" />"#);
8695
8696 // Trigger completion when typing a dash, because the dash is an extra
8697 // word character in the 'element' scope, which contains the cursor.
8698 cx.simulate_keystroke("-");
8699 cx.executor().run_until_parked();
8700 cx.update_editor(|editor, _| {
8701 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8702 assert_eq!(
8703 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8704 &["bg-red", "bg-blue", "bg-yellow"]
8705 );
8706 } else {
8707 panic!("expected completion menu to be open");
8708 }
8709 });
8710
8711 cx.simulate_keystroke("l");
8712 cx.executor().run_until_parked();
8713 cx.update_editor(|editor, _| {
8714 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8715 assert_eq!(
8716 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8717 &["bg-blue", "bg-yellow"]
8718 );
8719 } else {
8720 panic!("expected completion menu to be open");
8721 }
8722 });
8723
8724 // When filtering completions, consider the character after the '-' to
8725 // be the start of a subword.
8726 cx.set_state(r#"<p class="yelˇ" />"#);
8727 cx.simulate_keystroke("l");
8728 cx.executor().run_until_parked();
8729 cx.update_editor(|editor, _| {
8730 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8731 assert_eq!(
8732 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8733 &["bg-yellow"]
8734 );
8735 } else {
8736 panic!("expected completion menu to be open");
8737 }
8738 });
8739}
8740
8741#[gpui::test]
8742async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8743 init_test(cx, |settings| {
8744 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8745 });
8746
8747 let fs = FakeFs::new(cx.executor());
8748 fs.insert_file("/file.ts", Default::default()).await;
8749
8750 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
8751 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8752
8753 language_registry.add(Arc::new(Language::new(
8754 LanguageConfig {
8755 name: "TypeScript".into(),
8756 matcher: LanguageMatcher {
8757 path_suffixes: vec!["ts".to_string()],
8758 ..Default::default()
8759 },
8760 ..Default::default()
8761 },
8762 Some(tree_sitter_rust::language()),
8763 )));
8764 update_test_language_settings(cx, |settings| {
8765 settings.defaults.prettier = Some(PrettierSettings {
8766 allowed: true,
8767 ..PrettierSettings::default()
8768 });
8769 });
8770
8771 let test_plugin = "test_plugin";
8772 let _ = language_registry.register_fake_lsp_adapter(
8773 "TypeScript",
8774 FakeLspAdapter {
8775 prettier_plugins: vec![test_plugin],
8776 ..Default::default()
8777 },
8778 );
8779
8780 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8781 let buffer = project
8782 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
8783 .await
8784 .unwrap();
8785
8786 let buffer_text = "one\ntwo\nthree\n";
8787 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8788 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8789 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8790
8791 editor
8792 .update(cx, |editor, cx| {
8793 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8794 })
8795 .unwrap()
8796 .await;
8797 assert_eq!(
8798 editor.update(cx, |editor, cx| editor.text(cx)),
8799 buffer_text.to_string() + prettier_format_suffix,
8800 "Test prettier formatting was not applied to the original buffer text",
8801 );
8802
8803 update_test_language_settings(cx, |settings| {
8804 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8805 });
8806 let format = editor.update(cx, |editor, cx| {
8807 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8808 });
8809 format.await.unwrap();
8810 assert_eq!(
8811 editor.update(cx, |editor, cx| editor.text(cx)),
8812 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8813 "Autoformatting (via test prettier) was not applied to the original buffer text",
8814 );
8815}
8816
8817#[gpui::test]
8818async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8819 init_test(cx, |_| {});
8820 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8821 let base_text = indoc! {r#"struct Row;
8822struct Row1;
8823struct Row2;
8824
8825struct Row4;
8826struct Row5;
8827struct Row6;
8828
8829struct Row8;
8830struct Row9;
8831struct Row10;"#};
8832
8833 // When addition hunks are not adjacent to carets, no hunk revert is performed
8834 assert_hunk_revert(
8835 indoc! {r#"struct Row;
8836 struct Row1;
8837 struct Row1.1;
8838 struct Row1.2;
8839 struct Row2;ˇ
8840
8841 struct Row4;
8842 struct Row5;
8843 struct Row6;
8844
8845 struct Row8;
8846 ˇstruct Row9;
8847 struct Row9.1;
8848 struct Row9.2;
8849 struct Row9.3;
8850 struct Row10;"#},
8851 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8852 indoc! {r#"struct Row;
8853 struct Row1;
8854 struct Row1.1;
8855 struct Row1.2;
8856 struct Row2;ˇ
8857
8858 struct Row4;
8859 struct Row5;
8860 struct Row6;
8861
8862 struct Row8;
8863 ˇstruct Row9;
8864 struct Row9.1;
8865 struct Row9.2;
8866 struct Row9.3;
8867 struct Row10;"#},
8868 base_text,
8869 &mut cx,
8870 );
8871 // Same for selections
8872 assert_hunk_revert(
8873 indoc! {r#"struct Row;
8874 struct Row1;
8875 struct Row2;
8876 struct Row2.1;
8877 struct Row2.2;
8878 «ˇ
8879 struct Row4;
8880 struct» Row5;
8881 «struct Row6;
8882 ˇ»
8883 struct Row9.1;
8884 struct Row9.2;
8885 struct Row9.3;
8886 struct Row8;
8887 struct Row9;
8888 struct Row10;"#},
8889 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8890 indoc! {r#"struct Row;
8891 struct Row1;
8892 struct Row2;
8893 struct Row2.1;
8894 struct Row2.2;
8895 «ˇ
8896 struct Row4;
8897 struct» Row5;
8898 «struct Row6;
8899 ˇ»
8900 struct Row9.1;
8901 struct Row9.2;
8902 struct Row9.3;
8903 struct Row8;
8904 struct Row9;
8905 struct Row10;"#},
8906 base_text,
8907 &mut cx,
8908 );
8909
8910 // When carets and selections intersect the addition hunks, those are reverted.
8911 // Adjacent carets got merged.
8912 assert_hunk_revert(
8913 indoc! {r#"struct Row;
8914 ˇ// something on the top
8915 struct Row1;
8916 struct Row2;
8917 struct Roˇw3.1;
8918 struct Row2.2;
8919 struct Row2.3;ˇ
8920
8921 struct Row4;
8922 struct ˇRow5.1;
8923 struct Row5.2;
8924 struct «Rowˇ»5.3;
8925 struct Row5;
8926 struct Row6;
8927 ˇ
8928 struct Row9.1;
8929 struct «Rowˇ»9.2;
8930 struct «ˇRow»9.3;
8931 struct Row8;
8932 struct Row9;
8933 «ˇ// something on bottom»
8934 struct Row10;"#},
8935 vec![
8936 DiffHunkStatus::Added,
8937 DiffHunkStatus::Added,
8938 DiffHunkStatus::Added,
8939 DiffHunkStatus::Added,
8940 DiffHunkStatus::Added,
8941 ],
8942 indoc! {r#"struct Row;
8943 ˇstruct Row1;
8944 struct Row2;
8945 ˇ
8946 struct Row4;
8947 ˇstruct Row5;
8948 struct Row6;
8949 ˇ
8950 ˇstruct Row8;
8951 struct Row9;
8952 ˇstruct Row10;"#},
8953 base_text,
8954 &mut cx,
8955 );
8956}
8957
8958#[gpui::test]
8959async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8960 init_test(cx, |_| {});
8961 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8962 let base_text = indoc! {r#"struct Row;
8963struct Row1;
8964struct Row2;
8965
8966struct Row4;
8967struct Row5;
8968struct Row6;
8969
8970struct Row8;
8971struct Row9;
8972struct Row10;"#};
8973
8974 // Modification hunks behave the same as the addition ones.
8975 assert_hunk_revert(
8976 indoc! {r#"struct Row;
8977 struct Row1;
8978 struct Row33;
8979 ˇ
8980 struct Row4;
8981 struct Row5;
8982 struct Row6;
8983 ˇ
8984 struct Row99;
8985 struct Row9;
8986 struct Row10;"#},
8987 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8988 indoc! {r#"struct Row;
8989 struct Row1;
8990 struct Row33;
8991 ˇ
8992 struct Row4;
8993 struct Row5;
8994 struct Row6;
8995 ˇ
8996 struct Row99;
8997 struct Row9;
8998 struct Row10;"#},
8999 base_text,
9000 &mut cx,
9001 );
9002 assert_hunk_revert(
9003 indoc! {r#"struct Row;
9004 struct Row1;
9005 struct Row33;
9006 «ˇ
9007 struct Row4;
9008 struct» Row5;
9009 «struct Row6;
9010 ˇ»
9011 struct Row99;
9012 struct Row9;
9013 struct Row10;"#},
9014 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9015 indoc! {r#"struct Row;
9016 struct Row1;
9017 struct Row33;
9018 «ˇ
9019 struct Row4;
9020 struct» Row5;
9021 «struct Row6;
9022 ˇ»
9023 struct Row99;
9024 struct Row9;
9025 struct Row10;"#},
9026 base_text,
9027 &mut cx,
9028 );
9029
9030 assert_hunk_revert(
9031 indoc! {r#"ˇstruct Row1.1;
9032 struct Row1;
9033 «ˇstr»uct Row22;
9034
9035 struct ˇRow44;
9036 struct Row5;
9037 struct «Rˇ»ow66;ˇ
9038
9039 «struˇ»ct Row88;
9040 struct Row9;
9041 struct Row1011;ˇ"#},
9042 vec![
9043 DiffHunkStatus::Modified,
9044 DiffHunkStatus::Modified,
9045 DiffHunkStatus::Modified,
9046 DiffHunkStatus::Modified,
9047 DiffHunkStatus::Modified,
9048 DiffHunkStatus::Modified,
9049 ],
9050 indoc! {r#"struct Row;
9051 ˇstruct Row1;
9052 struct Row2;
9053 ˇ
9054 struct Row4;
9055 ˇstruct Row5;
9056 struct Row6;
9057 ˇ
9058 struct Row8;
9059 ˇstruct Row9;
9060 struct Row10;ˇ"#},
9061 base_text,
9062 &mut cx,
9063 );
9064}
9065
9066#[gpui::test]
9067async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
9068 init_test(cx, |_| {});
9069 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9070 let base_text = indoc! {r#"struct Row;
9071struct Row1;
9072struct Row2;
9073
9074struct Row4;
9075struct Row5;
9076struct Row6;
9077
9078struct Row8;
9079struct Row9;
9080struct Row10;"#};
9081
9082 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
9083 assert_hunk_revert(
9084 indoc! {r#"struct Row;
9085 struct Row2;
9086
9087 ˇstruct Row4;
9088 struct Row5;
9089 struct Row6;
9090 ˇ
9091 struct Row8;
9092 struct Row10;"#},
9093 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9094 indoc! {r#"struct Row;
9095 struct Row2;
9096
9097 ˇstruct Row4;
9098 struct Row5;
9099 struct Row6;
9100 ˇ
9101 struct Row8;
9102 struct Row10;"#},
9103 base_text,
9104 &mut cx,
9105 );
9106 assert_hunk_revert(
9107 indoc! {r#"struct Row;
9108 struct Row2;
9109
9110 «ˇstruct Row4;
9111 struct» Row5;
9112 «struct Row6;
9113 ˇ»
9114 struct Row8;
9115 struct Row10;"#},
9116 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9117 indoc! {r#"struct Row;
9118 struct Row2;
9119
9120 «ˇstruct Row4;
9121 struct» Row5;
9122 «struct Row6;
9123 ˇ»
9124 struct Row8;
9125 struct Row10;"#},
9126 base_text,
9127 &mut cx,
9128 );
9129
9130 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
9131 assert_hunk_revert(
9132 indoc! {r#"struct Row;
9133 ˇstruct Row2;
9134
9135 struct Row4;
9136 struct Row5;
9137 struct Row6;
9138
9139 struct Row8;ˇ
9140 struct Row10;"#},
9141 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9142 indoc! {r#"struct Row;
9143 struct Row1;
9144 ˇstruct Row2;
9145
9146 struct Row4;
9147 struct Row5;
9148 struct Row6;
9149
9150 struct Row8;ˇ
9151 struct Row9;
9152 struct Row10;"#},
9153 base_text,
9154 &mut cx,
9155 );
9156 assert_hunk_revert(
9157 indoc! {r#"struct Row;
9158 struct Row2«ˇ;
9159 struct Row4;
9160 struct» Row5;
9161 «struct Row6;
9162
9163 struct Row8;ˇ»
9164 struct Row10;"#},
9165 vec![
9166 DiffHunkStatus::Removed,
9167 DiffHunkStatus::Removed,
9168 DiffHunkStatus::Removed,
9169 ],
9170 indoc! {r#"struct Row;
9171 struct Row1;
9172 struct Row2«ˇ;
9173
9174 struct Row4;
9175 struct» Row5;
9176 «struct Row6;
9177
9178 struct Row8;ˇ»
9179 struct Row9;
9180 struct Row10;"#},
9181 base_text,
9182 &mut cx,
9183 );
9184}
9185
9186#[gpui::test]
9187async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
9188 init_test(cx, |_| {});
9189
9190 let cols = 4;
9191 let rows = 10;
9192 let sample_text_1 = sample_text(rows, cols, 'a');
9193 assert_eq!(
9194 sample_text_1,
9195 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9196 );
9197 let sample_text_2 = sample_text(rows, cols, 'l');
9198 assert_eq!(
9199 sample_text_2,
9200 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9201 );
9202 let sample_text_3 = sample_text(rows, cols, 'v');
9203 assert_eq!(
9204 sample_text_3,
9205 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9206 );
9207
9208 fn diff_every_buffer_row(
9209 buffer: &Model<Buffer>,
9210 sample_text: String,
9211 cols: usize,
9212 cx: &mut gpui::TestAppContext,
9213 ) {
9214 // revert first character in each row, creating one large diff hunk per buffer
9215 let is_first_char = |offset: usize| offset % cols == 0;
9216 buffer.update(cx, |buffer, cx| {
9217 buffer.set_text(
9218 sample_text
9219 .chars()
9220 .enumerate()
9221 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
9222 .collect::<String>(),
9223 cx,
9224 );
9225 buffer.set_diff_base(Some(sample_text), cx);
9226 });
9227 cx.executor().run_until_parked();
9228 }
9229
9230 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9231 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9232
9233 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9234 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9235
9236 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9237 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9238
9239 let multibuffer = cx.new_model(|cx| {
9240 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9241 multibuffer.push_excerpts(
9242 buffer_1.clone(),
9243 [
9244 ExcerptRange {
9245 context: Point::new(0, 0)..Point::new(3, 0),
9246 primary: None,
9247 },
9248 ExcerptRange {
9249 context: Point::new(5, 0)..Point::new(7, 0),
9250 primary: None,
9251 },
9252 ExcerptRange {
9253 context: Point::new(9, 0)..Point::new(10, 4),
9254 primary: None,
9255 },
9256 ],
9257 cx,
9258 );
9259 multibuffer.push_excerpts(
9260 buffer_2.clone(),
9261 [
9262 ExcerptRange {
9263 context: Point::new(0, 0)..Point::new(3, 0),
9264 primary: None,
9265 },
9266 ExcerptRange {
9267 context: Point::new(5, 0)..Point::new(7, 0),
9268 primary: None,
9269 },
9270 ExcerptRange {
9271 context: Point::new(9, 0)..Point::new(10, 4),
9272 primary: None,
9273 },
9274 ],
9275 cx,
9276 );
9277 multibuffer.push_excerpts(
9278 buffer_3.clone(),
9279 [
9280 ExcerptRange {
9281 context: Point::new(0, 0)..Point::new(3, 0),
9282 primary: None,
9283 },
9284 ExcerptRange {
9285 context: Point::new(5, 0)..Point::new(7, 0),
9286 primary: None,
9287 },
9288 ExcerptRange {
9289 context: Point::new(9, 0)..Point::new(10, 4),
9290 primary: None,
9291 },
9292 ],
9293 cx,
9294 );
9295 multibuffer
9296 });
9297
9298 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9299 editor.update(cx, |editor, cx| {
9300 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");
9301 editor.select_all(&SelectAll, cx);
9302 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9303 });
9304 cx.executor().run_until_parked();
9305 // When all ranges are selected, all buffer hunks are reverted.
9306 editor.update(cx, |editor, cx| {
9307 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");
9308 });
9309 buffer_1.update(cx, |buffer, _| {
9310 assert_eq!(buffer.text(), sample_text_1);
9311 });
9312 buffer_2.update(cx, |buffer, _| {
9313 assert_eq!(buffer.text(), sample_text_2);
9314 });
9315 buffer_3.update(cx, |buffer, _| {
9316 assert_eq!(buffer.text(), sample_text_3);
9317 });
9318
9319 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9320 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9321 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9322 editor.update(cx, |editor, cx| {
9323 editor.change_selections(None, cx, |s| {
9324 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9325 });
9326 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9327 });
9328 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9329 // but not affect buffer_2 and its related excerpts.
9330 editor.update(cx, |editor, cx| {
9331 assert_eq!(
9332 editor.text(cx),
9333 "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"
9334 );
9335 });
9336 buffer_1.update(cx, |buffer, _| {
9337 assert_eq!(buffer.text(), sample_text_1);
9338 });
9339 buffer_2.update(cx, |buffer, _| {
9340 assert_eq!(
9341 buffer.text(),
9342 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9343 );
9344 });
9345 buffer_3.update(cx, |buffer, _| {
9346 assert_eq!(
9347 buffer.text(),
9348 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9349 );
9350 });
9351}
9352
9353#[gpui::test]
9354async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9355 init_test(cx, |_| {});
9356
9357 let cols = 4;
9358 let rows = 10;
9359 let sample_text_1 = sample_text(rows, cols, 'a');
9360 assert_eq!(
9361 sample_text_1,
9362 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9363 );
9364 let sample_text_2 = sample_text(rows, cols, 'l');
9365 assert_eq!(
9366 sample_text_2,
9367 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9368 );
9369 let sample_text_3 = sample_text(rows, cols, 'v');
9370 assert_eq!(
9371 sample_text_3,
9372 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9373 );
9374
9375 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9376 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9377 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9378
9379 let multi_buffer = cx.new_model(|cx| {
9380 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9381 multibuffer.push_excerpts(
9382 buffer_1.clone(),
9383 [
9384 ExcerptRange {
9385 context: Point::new(0, 0)..Point::new(3, 0),
9386 primary: None,
9387 },
9388 ExcerptRange {
9389 context: Point::new(5, 0)..Point::new(7, 0),
9390 primary: None,
9391 },
9392 ExcerptRange {
9393 context: Point::new(9, 0)..Point::new(10, 4),
9394 primary: None,
9395 },
9396 ],
9397 cx,
9398 );
9399 multibuffer.push_excerpts(
9400 buffer_2.clone(),
9401 [
9402 ExcerptRange {
9403 context: Point::new(0, 0)..Point::new(3, 0),
9404 primary: None,
9405 },
9406 ExcerptRange {
9407 context: Point::new(5, 0)..Point::new(7, 0),
9408 primary: None,
9409 },
9410 ExcerptRange {
9411 context: Point::new(9, 0)..Point::new(10, 4),
9412 primary: None,
9413 },
9414 ],
9415 cx,
9416 );
9417 multibuffer.push_excerpts(
9418 buffer_3.clone(),
9419 [
9420 ExcerptRange {
9421 context: Point::new(0, 0)..Point::new(3, 0),
9422 primary: None,
9423 },
9424 ExcerptRange {
9425 context: Point::new(5, 0)..Point::new(7, 0),
9426 primary: None,
9427 },
9428 ExcerptRange {
9429 context: Point::new(9, 0)..Point::new(10, 4),
9430 primary: None,
9431 },
9432 ],
9433 cx,
9434 );
9435 multibuffer
9436 });
9437
9438 let fs = FakeFs::new(cx.executor());
9439 fs.insert_tree(
9440 "/a",
9441 json!({
9442 "main.rs": sample_text_1,
9443 "other.rs": sample_text_2,
9444 "lib.rs": sample_text_3,
9445 }),
9446 )
9447 .await;
9448 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9449 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9450 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9451 let multi_buffer_editor = cx.new_view(|cx| {
9452 Editor::new(
9453 EditorMode::Full,
9454 multi_buffer,
9455 Some(project.clone()),
9456 true,
9457 cx,
9458 )
9459 });
9460 let multibuffer_item_id = workspace
9461 .update(cx, |workspace, cx| {
9462 assert!(
9463 workspace.active_item(cx).is_none(),
9464 "active item should be None before the first item is added"
9465 );
9466 workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
9467 let active_item = workspace
9468 .active_item(cx)
9469 .expect("should have an active item after adding the multi buffer");
9470 assert!(
9471 !active_item.is_singleton(cx),
9472 "A multi buffer was expected to active after adding"
9473 );
9474 active_item.item_id()
9475 })
9476 .unwrap();
9477 cx.executor().run_until_parked();
9478
9479 multi_buffer_editor.update(cx, |editor, cx| {
9480 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9481 editor.open_excerpts(&OpenExcerpts, cx);
9482 });
9483 cx.executor().run_until_parked();
9484 let first_item_id = workspace
9485 .update(cx, |workspace, cx| {
9486 let active_item = workspace
9487 .active_item(cx)
9488 .expect("should have an active item after navigating into the 1st buffer");
9489 let first_item_id = active_item.item_id();
9490 assert_ne!(
9491 first_item_id, multibuffer_item_id,
9492 "Should navigate into the 1st buffer and activate it"
9493 );
9494 assert!(
9495 active_item.is_singleton(cx),
9496 "New active item should be a singleton buffer"
9497 );
9498 assert_eq!(
9499 active_item
9500 .act_as::<Editor>(cx)
9501 .expect("should have navigated into an editor for the 1st buffer")
9502 .read(cx)
9503 .text(cx),
9504 sample_text_1
9505 );
9506
9507 workspace
9508 .go_back(workspace.active_pane().downgrade(), cx)
9509 .detach_and_log_err(cx);
9510
9511 first_item_id
9512 })
9513 .unwrap();
9514 cx.executor().run_until_parked();
9515 workspace
9516 .update(cx, |workspace, cx| {
9517 let active_item = workspace
9518 .active_item(cx)
9519 .expect("should have an active item after navigating back");
9520 assert_eq!(
9521 active_item.item_id(),
9522 multibuffer_item_id,
9523 "Should navigate back to the multi buffer"
9524 );
9525 assert!(!active_item.is_singleton(cx));
9526 })
9527 .unwrap();
9528
9529 multi_buffer_editor.update(cx, |editor, cx| {
9530 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9531 s.select_ranges(Some(39..40))
9532 });
9533 editor.open_excerpts(&OpenExcerpts, cx);
9534 });
9535 cx.executor().run_until_parked();
9536 let second_item_id = workspace
9537 .update(cx, |workspace, cx| {
9538 let active_item = workspace
9539 .active_item(cx)
9540 .expect("should have an active item after navigating into the 2nd buffer");
9541 let second_item_id = active_item.item_id();
9542 assert_ne!(
9543 second_item_id, multibuffer_item_id,
9544 "Should navigate away from the multibuffer"
9545 );
9546 assert_ne!(
9547 second_item_id, first_item_id,
9548 "Should navigate into the 2nd buffer and activate it"
9549 );
9550 assert!(
9551 active_item.is_singleton(cx),
9552 "New active item should be a singleton buffer"
9553 );
9554 assert_eq!(
9555 active_item
9556 .act_as::<Editor>(cx)
9557 .expect("should have navigated into an editor")
9558 .read(cx)
9559 .text(cx),
9560 sample_text_2
9561 );
9562
9563 workspace
9564 .go_back(workspace.active_pane().downgrade(), cx)
9565 .detach_and_log_err(cx);
9566
9567 second_item_id
9568 })
9569 .unwrap();
9570 cx.executor().run_until_parked();
9571 workspace
9572 .update(cx, |workspace, cx| {
9573 let active_item = workspace
9574 .active_item(cx)
9575 .expect("should have an active item after navigating back from the 2nd buffer");
9576 assert_eq!(
9577 active_item.item_id(),
9578 multibuffer_item_id,
9579 "Should navigate back from the 2nd buffer to the multi buffer"
9580 );
9581 assert!(!active_item.is_singleton(cx));
9582 })
9583 .unwrap();
9584
9585 multi_buffer_editor.update(cx, |editor, cx| {
9586 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9587 s.select_ranges(Some(60..70))
9588 });
9589 editor.open_excerpts(&OpenExcerpts, cx);
9590 });
9591 cx.executor().run_until_parked();
9592 workspace
9593 .update(cx, |workspace, cx| {
9594 let active_item = workspace
9595 .active_item(cx)
9596 .expect("should have an active item after navigating into the 3rd buffer");
9597 let third_item_id = active_item.item_id();
9598 assert_ne!(
9599 third_item_id, multibuffer_item_id,
9600 "Should navigate into the 3rd buffer and activate it"
9601 );
9602 assert_ne!(third_item_id, first_item_id);
9603 assert_ne!(third_item_id, second_item_id);
9604 assert!(
9605 active_item.is_singleton(cx),
9606 "New active item should be a singleton buffer"
9607 );
9608 assert_eq!(
9609 active_item
9610 .act_as::<Editor>(cx)
9611 .expect("should have navigated into an editor")
9612 .read(cx)
9613 .text(cx),
9614 sample_text_3
9615 );
9616
9617 workspace
9618 .go_back(workspace.active_pane().downgrade(), cx)
9619 .detach_and_log_err(cx);
9620 })
9621 .unwrap();
9622 cx.executor().run_until_parked();
9623 workspace
9624 .update(cx, |workspace, cx| {
9625 let active_item = workspace
9626 .active_item(cx)
9627 .expect("should have an active item after navigating back from the 3rd buffer");
9628 assert_eq!(
9629 active_item.item_id(),
9630 multibuffer_item_id,
9631 "Should navigate back from the 3rd buffer to the multi buffer"
9632 );
9633 assert!(!active_item.is_singleton(cx));
9634 })
9635 .unwrap();
9636}
9637
9638#[gpui::test]
9639async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9640 init_test(cx, |_| {});
9641
9642 let mut cx = EditorTestContext::new(cx).await;
9643
9644 let diff_base = r#"
9645 use some::mod;
9646
9647 const A: u32 = 42;
9648
9649 fn main() {
9650 println!("hello");
9651
9652 println!("world");
9653 }
9654 "#
9655 .unindent();
9656
9657 cx.set_state(
9658 &r#"
9659 use some::modified;
9660
9661 ˇ
9662 fn main() {
9663 println!("hello there");
9664
9665 println!("around the");
9666 println!("world");
9667 }
9668 "#
9669 .unindent(),
9670 );
9671
9672 cx.set_diff_base(Some(&diff_base));
9673 executor.run_until_parked();
9674 let unexpanded_hunks = vec![
9675 (
9676 "use some::mod;\n".to_string(),
9677 DiffHunkStatus::Modified,
9678 DisplayRow(0)..DisplayRow(1),
9679 ),
9680 (
9681 "const A: u32 = 42;\n".to_string(),
9682 DiffHunkStatus::Removed,
9683 DisplayRow(2)..DisplayRow(2),
9684 ),
9685 (
9686 " println!(\"hello\");\n".to_string(),
9687 DiffHunkStatus::Modified,
9688 DisplayRow(4)..DisplayRow(5),
9689 ),
9690 (
9691 "".to_string(),
9692 DiffHunkStatus::Added,
9693 DisplayRow(6)..DisplayRow(7),
9694 ),
9695 ];
9696 cx.update_editor(|editor, cx| {
9697 let snapshot = editor.snapshot(cx);
9698 let all_hunks = editor_hunks(editor, &snapshot, cx);
9699 assert_eq!(all_hunks, unexpanded_hunks);
9700 });
9701
9702 cx.update_editor(|editor, cx| {
9703 for _ in 0..4 {
9704 editor.go_to_hunk(&GoToHunk, cx);
9705 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
9706 }
9707 });
9708 executor.run_until_parked();
9709 cx.assert_editor_state(
9710 &r#"
9711 use some::modified;
9712
9713 ˇ
9714 fn main() {
9715 println!("hello there");
9716
9717 println!("around the");
9718 println!("world");
9719 }
9720 "#
9721 .unindent(),
9722 );
9723 cx.update_editor(|editor, cx| {
9724 let snapshot = editor.snapshot(cx);
9725 let all_hunks = editor_hunks(editor, &snapshot, cx);
9726 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
9727 assert_eq!(
9728 expanded_hunks_background_highlights(editor, cx),
9729 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
9730 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
9731 );
9732 assert_eq!(
9733 all_hunks,
9734 vec![
9735 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
9736 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
9737 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
9738 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
9739 ],
9740 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
9741 (from modified and removed hunks)"
9742 );
9743 assert_eq!(
9744 all_hunks, all_expanded_hunks,
9745 "Editor hunks should not change and all be expanded"
9746 );
9747 });
9748
9749 cx.update_editor(|editor, cx| {
9750 editor.cancel(&Cancel, cx);
9751
9752 let snapshot = editor.snapshot(cx);
9753 let all_hunks = editor_hunks(editor, &snapshot, cx);
9754 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
9755 assert_eq!(
9756 expanded_hunks_background_highlights(editor, cx),
9757 Vec::new(),
9758 "After cancelling in editor, no git highlights should be left"
9759 );
9760 assert_eq!(
9761 all_expanded_hunks,
9762 Vec::new(),
9763 "After cancelling in editor, no hunks should be expanded"
9764 );
9765 assert_eq!(
9766 all_hunks, unexpanded_hunks,
9767 "After cancelling in editor, regular hunks' coordinates should get back to normal"
9768 );
9769 });
9770}
9771
9772#[gpui::test]
9773async fn test_toggled_diff_base_change(
9774 executor: BackgroundExecutor,
9775 cx: &mut gpui::TestAppContext,
9776) {
9777 init_test(cx, |_| {});
9778
9779 let mut cx = EditorTestContext::new(cx).await;
9780
9781 let diff_base = r#"
9782 use some::mod1;
9783 use some::mod2;
9784
9785 const A: u32 = 42;
9786 const B: u32 = 42;
9787 const C: u32 = 42;
9788
9789 fn main(ˇ) {
9790 println!("hello");
9791
9792 println!("world");
9793 }
9794 "#
9795 .unindent();
9796
9797 cx.set_state(
9798 &r#"
9799 use some::mod2;
9800
9801 const A: u32 = 42;
9802 const C: u32 = 42;
9803
9804 fn main(ˇ) {
9805 //println!("hello");
9806
9807 println!("world");
9808 //
9809 //
9810 }
9811 "#
9812 .unindent(),
9813 );
9814
9815 cx.set_diff_base(Some(&diff_base));
9816 executor.run_until_parked();
9817 cx.update_editor(|editor, cx| {
9818 let snapshot = editor.snapshot(cx);
9819 let all_hunks = editor_hunks(editor, &snapshot, cx);
9820 assert_eq!(
9821 all_hunks,
9822 vec![
9823 (
9824 "use some::mod1;\n".to_string(),
9825 DiffHunkStatus::Removed,
9826 DisplayRow(0)..DisplayRow(0)
9827 ),
9828 (
9829 "const B: u32 = 42;\n".to_string(),
9830 DiffHunkStatus::Removed,
9831 DisplayRow(3)..DisplayRow(3)
9832 ),
9833 (
9834 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
9835 DiffHunkStatus::Modified,
9836 DisplayRow(5)..DisplayRow(7)
9837 ),
9838 (
9839 "".to_string(),
9840 DiffHunkStatus::Added,
9841 DisplayRow(9)..DisplayRow(11)
9842 ),
9843 ]
9844 );
9845 });
9846
9847 cx.update_editor(|editor, cx| {
9848 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
9849 });
9850 executor.run_until_parked();
9851 cx.assert_editor_state(
9852 &r#"
9853 use some::mod2;
9854
9855 const A: u32 = 42;
9856 const C: u32 = 42;
9857
9858 fn main(ˇ) {
9859 //println!("hello");
9860
9861 println!("world");
9862 //
9863 //
9864 }
9865 "#
9866 .unindent(),
9867 );
9868 cx.update_editor(|editor, cx| {
9869 let snapshot = editor.snapshot(cx);
9870 let all_hunks = editor_hunks(editor, &snapshot, cx);
9871 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
9872 assert_eq!(
9873 expanded_hunks_background_highlights(editor, cx),
9874 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
9875 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
9876 );
9877 assert_eq!(
9878 all_hunks,
9879 vec![
9880 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
9881 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
9882 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
9883 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
9884 ],
9885 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
9886 (from modified and removed hunks)"
9887 );
9888 assert_eq!(
9889 all_hunks, all_expanded_hunks,
9890 "Editor hunks should not change and all be expanded"
9891 );
9892 });
9893
9894 cx.set_diff_base(Some("new diff base!"));
9895 executor.run_until_parked();
9896
9897 cx.update_editor(|editor, cx| {
9898 let snapshot = editor.snapshot(cx);
9899 let all_hunks = editor_hunks(editor, &snapshot, cx);
9900 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
9901 assert_eq!(
9902 expanded_hunks_background_highlights(editor, cx),
9903 Vec::new(),
9904 "After diff base is changed, old git highlights should be removed"
9905 );
9906 assert_eq!(
9907 all_expanded_hunks,
9908 Vec::new(),
9909 "After diff base is changed, old git hunk expansions should be removed"
9910 );
9911 assert_eq!(
9912 all_hunks,
9913 vec![(
9914 "new diff base!".to_string(),
9915 DiffHunkStatus::Modified,
9916 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
9917 )],
9918 "After diff base is changed, hunks should update"
9919 );
9920 });
9921}
9922
9923#[gpui::test]
9924async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9925 init_test(cx, |_| {});
9926
9927 let mut cx = EditorTestContext::new(cx).await;
9928
9929 let diff_base = r#"
9930 use some::mod1;
9931 use some::mod2;
9932
9933 const A: u32 = 42;
9934 const B: u32 = 42;
9935 const C: u32 = 42;
9936
9937 fn main(ˇ) {
9938 println!("hello");
9939
9940 println!("world");
9941 }
9942
9943 fn another() {
9944 println!("another");
9945 }
9946
9947 fn another2() {
9948 println!("another2");
9949 }
9950 "#
9951 .unindent();
9952
9953 cx.set_state(
9954 &r#"
9955 «use some::mod2;
9956
9957 const A: u32 = 42;
9958 const C: u32 = 42;
9959
9960 fn main() {
9961 //println!("hello");
9962
9963 println!("world");
9964 //
9965 //ˇ»
9966 }
9967
9968 fn another() {
9969 println!("another");
9970 println!("another");
9971 }
9972
9973 println!("another2");
9974 }
9975 "#
9976 .unindent(),
9977 );
9978
9979 cx.set_diff_base(Some(&diff_base));
9980 executor.run_until_parked();
9981 cx.update_editor(|editor, cx| {
9982 let snapshot = editor.snapshot(cx);
9983 let all_hunks = editor_hunks(editor, &snapshot, cx);
9984 assert_eq!(
9985 all_hunks,
9986 vec![
9987 (
9988 "use some::mod1;\n".to_string(),
9989 DiffHunkStatus::Removed,
9990 DisplayRow(0)..DisplayRow(0)
9991 ),
9992 (
9993 "const B: u32 = 42;\n".to_string(),
9994 DiffHunkStatus::Removed,
9995 DisplayRow(3)..DisplayRow(3)
9996 ),
9997 (
9998 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
9999 DiffHunkStatus::Modified,
10000 DisplayRow(5)..DisplayRow(7)
10001 ),
10002 (
10003 "".to_string(),
10004 DiffHunkStatus::Added,
10005 DisplayRow(9)..DisplayRow(11)
10006 ),
10007 (
10008 "".to_string(),
10009 DiffHunkStatus::Added,
10010 DisplayRow(15)..DisplayRow(16)
10011 ),
10012 (
10013 "fn another2() {\n".to_string(),
10014 DiffHunkStatus::Removed,
10015 DisplayRow(18)..DisplayRow(18)
10016 ),
10017 ]
10018 );
10019 });
10020
10021 cx.update_editor(|editor, cx| {
10022 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10023 });
10024 executor.run_until_parked();
10025 cx.assert_editor_state(
10026 &r#"
10027 «use some::mod2;
10028
10029 const A: u32 = 42;
10030 const C: u32 = 42;
10031
10032 fn main() {
10033 //println!("hello");
10034
10035 println!("world");
10036 //
10037 //ˇ»
10038 }
10039
10040 fn another() {
10041 println!("another");
10042 println!("another");
10043 }
10044
10045 println!("another2");
10046 }
10047 "#
10048 .unindent(),
10049 );
10050 cx.update_editor(|editor, cx| {
10051 let snapshot = editor.snapshot(cx);
10052 let all_hunks = editor_hunks(editor, &snapshot, cx);
10053 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10054 assert_eq!(
10055 expanded_hunks_background_highlights(editor, cx),
10056 vec![
10057 DisplayRow(9)..=DisplayRow(10),
10058 DisplayRow(13)..=DisplayRow(14),
10059 DisplayRow(19)..=DisplayRow(19)
10060 ]
10061 );
10062 assert_eq!(
10063 all_hunks,
10064 vec![
10065 (
10066 "use some::mod1;\n".to_string(),
10067 DiffHunkStatus::Removed,
10068 DisplayRow(1)..DisplayRow(1)
10069 ),
10070 (
10071 "const B: u32 = 42;\n".to_string(),
10072 DiffHunkStatus::Removed,
10073 DisplayRow(5)..DisplayRow(5)
10074 ),
10075 (
10076 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10077 DiffHunkStatus::Modified,
10078 DisplayRow(9)..DisplayRow(11)
10079 ),
10080 (
10081 "".to_string(),
10082 DiffHunkStatus::Added,
10083 DisplayRow(13)..DisplayRow(15)
10084 ),
10085 (
10086 "".to_string(),
10087 DiffHunkStatus::Added,
10088 DisplayRow(19)..DisplayRow(20)
10089 ),
10090 (
10091 "fn another2() {\n".to_string(),
10092 DiffHunkStatus::Removed,
10093 DisplayRow(23)..DisplayRow(23)
10094 ),
10095 ],
10096 );
10097 assert_eq!(all_hunks, all_expanded_hunks);
10098 });
10099
10100 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10101 cx.executor().run_until_parked();
10102 cx.assert_editor_state(
10103 &r#"
10104 «use some::mod2;
10105
10106 const A: u32 = 42;
10107 const C: u32 = 42;
10108
10109 fn main() {
10110 //println!("hello");
10111
10112 println!("world");
10113 //
10114 //ˇ»
10115 }
10116
10117 fn another() {
10118 println!("another");
10119 println!("another");
10120 }
10121
10122 println!("another2");
10123 }
10124 "#
10125 .unindent(),
10126 );
10127 cx.update_editor(|editor, cx| {
10128 let snapshot = editor.snapshot(cx);
10129 let all_hunks = editor_hunks(editor, &snapshot, cx);
10130 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10131 assert_eq!(
10132 expanded_hunks_background_highlights(editor, cx),
10133 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10134 "Only one hunk is left not folded, its highlight should be visible"
10135 );
10136 assert_eq!(
10137 all_hunks,
10138 vec![
10139 (
10140 "use some::mod1;\n".to_string(),
10141 DiffHunkStatus::Removed,
10142 DisplayRow(0)..DisplayRow(0)
10143 ),
10144 (
10145 "const B: u32 = 42;\n".to_string(),
10146 DiffHunkStatus::Removed,
10147 DisplayRow(0)..DisplayRow(0)
10148 ),
10149 (
10150 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10151 DiffHunkStatus::Modified,
10152 DisplayRow(0)..DisplayRow(0)
10153 ),
10154 (
10155 "".to_string(),
10156 DiffHunkStatus::Added,
10157 DisplayRow(0)..DisplayRow(1)
10158 ),
10159 (
10160 "".to_string(),
10161 DiffHunkStatus::Added,
10162 DisplayRow(5)..DisplayRow(6)
10163 ),
10164 (
10165 "fn another2() {\n".to_string(),
10166 DiffHunkStatus::Removed,
10167 DisplayRow(9)..DisplayRow(9)
10168 ),
10169 ],
10170 "Hunk list should still return shifted folded hunks"
10171 );
10172 assert_eq!(
10173 all_expanded_hunks,
10174 vec![
10175 (
10176 "".to_string(),
10177 DiffHunkStatus::Added,
10178 DisplayRow(5)..DisplayRow(6)
10179 ),
10180 (
10181 "fn another2() {\n".to_string(),
10182 DiffHunkStatus::Removed,
10183 DisplayRow(9)..DisplayRow(9)
10184 ),
10185 ],
10186 "Only non-folded hunks should be left expanded"
10187 );
10188 });
10189
10190 cx.update_editor(|editor, cx| {
10191 editor.select_all(&SelectAll, cx);
10192 editor.unfold_lines(&UnfoldLines, cx);
10193 });
10194 cx.executor().run_until_parked();
10195 cx.assert_editor_state(
10196 &r#"
10197 «use some::mod2;
10198
10199 const A: u32 = 42;
10200 const C: u32 = 42;
10201
10202 fn main() {
10203 //println!("hello");
10204
10205 println!("world");
10206 //
10207 //
10208 }
10209
10210 fn another() {
10211 println!("another");
10212 println!("another");
10213 }
10214
10215 println!("another2");
10216 }
10217 ˇ»"#
10218 .unindent(),
10219 );
10220 cx.update_editor(|editor, cx| {
10221 let snapshot = editor.snapshot(cx);
10222 let all_hunks = editor_hunks(editor, &snapshot, cx);
10223 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10224 assert_eq!(
10225 expanded_hunks_background_highlights(editor, cx),
10226 vec![
10227 DisplayRow(9)..=DisplayRow(10),
10228 DisplayRow(13)..=DisplayRow(14),
10229 DisplayRow(19)..=DisplayRow(19)
10230 ],
10231 "After unfolding, all hunk diffs should be visible again"
10232 );
10233 assert_eq!(
10234 all_hunks,
10235 vec![
10236 (
10237 "use some::mod1;\n".to_string(),
10238 DiffHunkStatus::Removed,
10239 DisplayRow(1)..DisplayRow(1)
10240 ),
10241 (
10242 "const B: u32 = 42;\n".to_string(),
10243 DiffHunkStatus::Removed,
10244 DisplayRow(5)..DisplayRow(5)
10245 ),
10246 (
10247 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10248 DiffHunkStatus::Modified,
10249 DisplayRow(9)..DisplayRow(11)
10250 ),
10251 (
10252 "".to_string(),
10253 DiffHunkStatus::Added,
10254 DisplayRow(13)..DisplayRow(15)
10255 ),
10256 (
10257 "".to_string(),
10258 DiffHunkStatus::Added,
10259 DisplayRow(19)..DisplayRow(20)
10260 ),
10261 (
10262 "fn another2() {\n".to_string(),
10263 DiffHunkStatus::Removed,
10264 DisplayRow(23)..DisplayRow(23)
10265 ),
10266 ],
10267 );
10268 assert_eq!(all_hunks, all_expanded_hunks);
10269 });
10270}
10271
10272#[gpui::test]
10273async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10274 init_test(cx, |_| {});
10275
10276 let cols = 4;
10277 let rows = 10;
10278 let sample_text_1 = sample_text(rows, cols, 'a');
10279 assert_eq!(
10280 sample_text_1,
10281 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10282 );
10283 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10284 let sample_text_2 = sample_text(rows, cols, 'l');
10285 assert_eq!(
10286 sample_text_2,
10287 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10288 );
10289 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10290 let sample_text_3 = sample_text(rows, cols, 'v');
10291 assert_eq!(
10292 sample_text_3,
10293 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10294 );
10295 let modified_sample_text_3 =
10296 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10297 let buffer_1 = cx.new_model(|cx| {
10298 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10299 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10300 buffer
10301 });
10302 let buffer_2 = cx.new_model(|cx| {
10303 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10304 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10305 buffer
10306 });
10307 let buffer_3 = cx.new_model(|cx| {
10308 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10309 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10310 buffer
10311 });
10312
10313 let multi_buffer = cx.new_model(|cx| {
10314 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10315 multibuffer.push_excerpts(
10316 buffer_1.clone(),
10317 [
10318 ExcerptRange {
10319 context: Point::new(0, 0)..Point::new(3, 0),
10320 primary: None,
10321 },
10322 ExcerptRange {
10323 context: Point::new(5, 0)..Point::new(7, 0),
10324 primary: None,
10325 },
10326 ExcerptRange {
10327 context: Point::new(9, 0)..Point::new(10, 4),
10328 primary: None,
10329 },
10330 ],
10331 cx,
10332 );
10333 multibuffer.push_excerpts(
10334 buffer_2.clone(),
10335 [
10336 ExcerptRange {
10337 context: Point::new(0, 0)..Point::new(3, 0),
10338 primary: None,
10339 },
10340 ExcerptRange {
10341 context: Point::new(5, 0)..Point::new(7, 0),
10342 primary: None,
10343 },
10344 ExcerptRange {
10345 context: Point::new(9, 0)..Point::new(10, 4),
10346 primary: None,
10347 },
10348 ],
10349 cx,
10350 );
10351 multibuffer.push_excerpts(
10352 buffer_3.clone(),
10353 [
10354 ExcerptRange {
10355 context: Point::new(0, 0)..Point::new(3, 0),
10356 primary: None,
10357 },
10358 ExcerptRange {
10359 context: Point::new(5, 0)..Point::new(7, 0),
10360 primary: None,
10361 },
10362 ExcerptRange {
10363 context: Point::new(9, 0)..Point::new(10, 4),
10364 primary: None,
10365 },
10366 ],
10367 cx,
10368 );
10369 multibuffer
10370 });
10371
10372 let fs = FakeFs::new(cx.executor());
10373 fs.insert_tree(
10374 "/a",
10375 json!({
10376 "main.rs": modified_sample_text_1,
10377 "other.rs": modified_sample_text_2,
10378 "lib.rs": modified_sample_text_3,
10379 }),
10380 )
10381 .await;
10382
10383 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10384 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10385 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10386 let multi_buffer_editor = cx.new_view(|cx| {
10387 Editor::new(
10388 EditorMode::Full,
10389 multi_buffer,
10390 Some(project.clone()),
10391 true,
10392 cx,
10393 )
10394 });
10395 cx.executor().run_until_parked();
10396
10397 let expected_all_hunks = vec![
10398 (
10399 "bbbb\n".to_string(),
10400 DiffHunkStatus::Removed,
10401 DisplayRow(4)..DisplayRow(4),
10402 ),
10403 (
10404 "nnnn\n".to_string(),
10405 DiffHunkStatus::Modified,
10406 DisplayRow(21)..DisplayRow(22),
10407 ),
10408 (
10409 "".to_string(),
10410 DiffHunkStatus::Added,
10411 DisplayRow(41)..DisplayRow(42),
10412 ),
10413 ];
10414 let expected_all_hunks_shifted = vec![
10415 (
10416 "bbbb\n".to_string(),
10417 DiffHunkStatus::Removed,
10418 DisplayRow(5)..DisplayRow(5),
10419 ),
10420 (
10421 "nnnn\n".to_string(),
10422 DiffHunkStatus::Modified,
10423 DisplayRow(23)..DisplayRow(24),
10424 ),
10425 (
10426 "".to_string(),
10427 DiffHunkStatus::Added,
10428 DisplayRow(43)..DisplayRow(44),
10429 ),
10430 ];
10431
10432 multi_buffer_editor.update(cx, |editor, cx| {
10433 let snapshot = editor.snapshot(cx);
10434 let all_hunks = editor_hunks(editor, &snapshot, cx);
10435 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10436 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10437 assert_eq!(all_hunks, expected_all_hunks);
10438 assert_eq!(all_expanded_hunks, Vec::new());
10439 });
10440
10441 multi_buffer_editor.update(cx, |editor, cx| {
10442 editor.select_all(&SelectAll, cx);
10443 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10444 });
10445 cx.executor().run_until_parked();
10446 multi_buffer_editor.update(cx, |editor, cx| {
10447 let snapshot = editor.snapshot(cx);
10448 let all_hunks = editor_hunks(editor, &snapshot, cx);
10449 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10450 assert_eq!(
10451 expanded_hunks_background_highlights(editor, cx),
10452 vec![
10453 DisplayRow(23)..=DisplayRow(23),
10454 DisplayRow(43)..=DisplayRow(43)
10455 ],
10456 );
10457 assert_eq!(all_hunks, expected_all_hunks_shifted);
10458 assert_eq!(all_hunks, all_expanded_hunks);
10459 });
10460
10461 multi_buffer_editor.update(cx, |editor, cx| {
10462 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10463 });
10464 cx.executor().run_until_parked();
10465 multi_buffer_editor.update(cx, |editor, cx| {
10466 let snapshot = editor.snapshot(cx);
10467 let all_hunks = editor_hunks(editor, &snapshot, cx);
10468 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10469 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10470 assert_eq!(all_hunks, expected_all_hunks);
10471 assert_eq!(all_expanded_hunks, Vec::new());
10472 });
10473
10474 multi_buffer_editor.update(cx, |editor, cx| {
10475 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10476 });
10477 cx.executor().run_until_parked();
10478 multi_buffer_editor.update(cx, |editor, cx| {
10479 let snapshot = editor.snapshot(cx);
10480 let all_hunks = editor_hunks(editor, &snapshot, cx);
10481 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10482 assert_eq!(
10483 expanded_hunks_background_highlights(editor, cx),
10484 vec![
10485 DisplayRow(23)..=DisplayRow(23),
10486 DisplayRow(43)..=DisplayRow(43)
10487 ],
10488 );
10489 assert_eq!(all_hunks, expected_all_hunks_shifted);
10490 assert_eq!(all_hunks, all_expanded_hunks);
10491 });
10492
10493 multi_buffer_editor.update(cx, |editor, cx| {
10494 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10495 });
10496 cx.executor().run_until_parked();
10497 multi_buffer_editor.update(cx, |editor, cx| {
10498 let snapshot = editor.snapshot(cx);
10499 let all_hunks = editor_hunks(editor, &snapshot, cx);
10500 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10501 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10502 assert_eq!(all_hunks, expected_all_hunks);
10503 assert_eq!(all_expanded_hunks, Vec::new());
10504 });
10505}
10506
10507#[gpui::test]
10508async fn test_edits_around_toggled_additions(
10509 executor: BackgroundExecutor,
10510 cx: &mut gpui::TestAppContext,
10511) {
10512 init_test(cx, |_| {});
10513
10514 let mut cx = EditorTestContext::new(cx).await;
10515
10516 let diff_base = r#"
10517 use some::mod1;
10518 use some::mod2;
10519
10520 const A: u32 = 42;
10521
10522 fn main() {
10523 println!("hello");
10524
10525 println!("world");
10526 }
10527 "#
10528 .unindent();
10529 executor.run_until_parked();
10530 cx.set_state(
10531 &r#"
10532 use some::mod1;
10533 use some::mod2;
10534
10535 const A: u32 = 42;
10536 const B: u32 = 42;
10537 const C: u32 = 42;
10538 ˇ
10539
10540 fn main() {
10541 println!("hello");
10542
10543 println!("world");
10544 }
10545 "#
10546 .unindent(),
10547 );
10548
10549 cx.set_diff_base(Some(&diff_base));
10550 executor.run_until_parked();
10551 cx.update_editor(|editor, cx| {
10552 let snapshot = editor.snapshot(cx);
10553 let all_hunks = editor_hunks(editor, &snapshot, cx);
10554 assert_eq!(
10555 all_hunks,
10556 vec![(
10557 "".to_string(),
10558 DiffHunkStatus::Added,
10559 DisplayRow(4)..DisplayRow(7)
10560 )]
10561 );
10562 });
10563 cx.update_editor(|editor, cx| {
10564 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10565 });
10566 executor.run_until_parked();
10567 cx.assert_editor_state(
10568 &r#"
10569 use some::mod1;
10570 use some::mod2;
10571
10572 const A: u32 = 42;
10573 const B: u32 = 42;
10574 const C: u32 = 42;
10575 ˇ
10576
10577 fn main() {
10578 println!("hello");
10579
10580 println!("world");
10581 }
10582 "#
10583 .unindent(),
10584 );
10585 cx.update_editor(|editor, cx| {
10586 let snapshot = editor.snapshot(cx);
10587 let all_hunks = editor_hunks(editor, &snapshot, cx);
10588 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10589 assert_eq!(
10590 all_hunks,
10591 vec![(
10592 "".to_string(),
10593 DiffHunkStatus::Added,
10594 DisplayRow(4)..DisplayRow(7)
10595 )]
10596 );
10597 assert_eq!(
10598 expanded_hunks_background_highlights(editor, cx),
10599 vec![DisplayRow(4)..=DisplayRow(6)]
10600 );
10601 assert_eq!(all_hunks, all_expanded_hunks);
10602 });
10603
10604 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10605 executor.run_until_parked();
10606 cx.assert_editor_state(
10607 &r#"
10608 use some::mod1;
10609 use some::mod2;
10610
10611 const A: u32 = 42;
10612 const B: u32 = 42;
10613 const C: u32 = 42;
10614 const D: u32 = 42;
10615 ˇ
10616
10617 fn main() {
10618 println!("hello");
10619
10620 println!("world");
10621 }
10622 "#
10623 .unindent(),
10624 );
10625 cx.update_editor(|editor, cx| {
10626 let snapshot = editor.snapshot(cx);
10627 let all_hunks = editor_hunks(editor, &snapshot, cx);
10628 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10629 assert_eq!(
10630 all_hunks,
10631 vec![(
10632 "".to_string(),
10633 DiffHunkStatus::Added,
10634 DisplayRow(4)..DisplayRow(8)
10635 )]
10636 );
10637 assert_eq!(
10638 expanded_hunks_background_highlights(editor, cx),
10639 vec![DisplayRow(4)..=DisplayRow(6)],
10640 "Edited hunk should have one more line added"
10641 );
10642 assert_eq!(
10643 all_hunks, all_expanded_hunks,
10644 "Expanded hunk should also grow with the addition"
10645 );
10646 });
10647
10648 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10649 executor.run_until_parked();
10650 cx.assert_editor_state(
10651 &r#"
10652 use some::mod1;
10653 use some::mod2;
10654
10655 const A: u32 = 42;
10656 const B: u32 = 42;
10657 const C: u32 = 42;
10658 const D: u32 = 42;
10659 const E: u32 = 42;
10660 ˇ
10661
10662 fn main() {
10663 println!("hello");
10664
10665 println!("world");
10666 }
10667 "#
10668 .unindent(),
10669 );
10670 cx.update_editor(|editor, cx| {
10671 let snapshot = editor.snapshot(cx);
10672 let all_hunks = editor_hunks(editor, &snapshot, cx);
10673 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10674 assert_eq!(
10675 all_hunks,
10676 vec![(
10677 "".to_string(),
10678 DiffHunkStatus::Added,
10679 DisplayRow(4)..DisplayRow(9)
10680 )]
10681 );
10682 assert_eq!(
10683 expanded_hunks_background_highlights(editor, cx),
10684 vec![DisplayRow(4)..=DisplayRow(6)],
10685 "Edited hunk should have one more line added"
10686 );
10687 assert_eq!(all_hunks, all_expanded_hunks);
10688 });
10689
10690 cx.update_editor(|editor, cx| {
10691 editor.move_up(&MoveUp, cx);
10692 editor.delete_line(&DeleteLine, cx);
10693 });
10694 executor.run_until_parked();
10695 cx.assert_editor_state(
10696 &r#"
10697 use some::mod1;
10698 use some::mod2;
10699
10700 const A: u32 = 42;
10701 const B: u32 = 42;
10702 const C: u32 = 42;
10703 const D: u32 = 42;
10704 ˇ
10705
10706 fn main() {
10707 println!("hello");
10708
10709 println!("world");
10710 }
10711 "#
10712 .unindent(),
10713 );
10714 cx.update_editor(|editor, cx| {
10715 let snapshot = editor.snapshot(cx);
10716 let all_hunks = editor_hunks(editor, &snapshot, cx);
10717 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10718 assert_eq!(
10719 all_hunks,
10720 vec![(
10721 "".to_string(),
10722 DiffHunkStatus::Added,
10723 DisplayRow(4)..DisplayRow(8)
10724 )]
10725 );
10726 assert_eq!(
10727 expanded_hunks_background_highlights(editor, cx),
10728 vec![DisplayRow(4)..=DisplayRow(6)],
10729 "Deleting a line should shrint the hunk"
10730 );
10731 assert_eq!(
10732 all_hunks, all_expanded_hunks,
10733 "Expanded hunk should also shrink with the addition"
10734 );
10735 });
10736
10737 cx.update_editor(|editor, cx| {
10738 editor.move_up(&MoveUp, cx);
10739 editor.delete_line(&DeleteLine, cx);
10740 editor.move_up(&MoveUp, cx);
10741 editor.delete_line(&DeleteLine, cx);
10742 editor.move_up(&MoveUp, cx);
10743 editor.delete_line(&DeleteLine, cx);
10744 });
10745 executor.run_until_parked();
10746 cx.assert_editor_state(
10747 &r#"
10748 use some::mod1;
10749 use some::mod2;
10750
10751 const A: u32 = 42;
10752 ˇ
10753
10754 fn main() {
10755 println!("hello");
10756
10757 println!("world");
10758 }
10759 "#
10760 .unindent(),
10761 );
10762 cx.update_editor(|editor, cx| {
10763 let snapshot = editor.snapshot(cx);
10764 let all_hunks = editor_hunks(editor, &snapshot, cx);
10765 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10766 assert_eq!(
10767 all_hunks,
10768 vec![(
10769 "".to_string(),
10770 DiffHunkStatus::Added,
10771 DisplayRow(5)..DisplayRow(6)
10772 )]
10773 );
10774 assert_eq!(
10775 expanded_hunks_background_highlights(editor, cx),
10776 vec![DisplayRow(5)..=DisplayRow(5)]
10777 );
10778 assert_eq!(all_hunks, all_expanded_hunks);
10779 });
10780
10781 cx.update_editor(|editor, cx| {
10782 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
10783 editor.delete_line(&DeleteLine, cx);
10784 });
10785 executor.run_until_parked();
10786 cx.assert_editor_state(
10787 &r#"
10788 ˇ
10789
10790 fn main() {
10791 println!("hello");
10792
10793 println!("world");
10794 }
10795 "#
10796 .unindent(),
10797 );
10798 cx.update_editor(|editor, cx| {
10799 let snapshot = editor.snapshot(cx);
10800 let all_hunks = editor_hunks(editor, &snapshot, cx);
10801 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10802 assert_eq!(
10803 all_hunks,
10804 vec![
10805 (
10806 "use some::mod1;\nuse some::mod2;\n".to_string(),
10807 DiffHunkStatus::Removed,
10808 DisplayRow(0)..DisplayRow(0)
10809 ),
10810 (
10811 "const A: u32 = 42;\n".to_string(),
10812 DiffHunkStatus::Removed,
10813 DisplayRow(2)..DisplayRow(2)
10814 )
10815 ]
10816 );
10817 assert_eq!(
10818 expanded_hunks_background_highlights(editor, cx),
10819 Vec::new(),
10820 "Should close all stale expanded addition hunks"
10821 );
10822 assert_eq!(
10823 all_expanded_hunks,
10824 vec![(
10825 "const A: u32 = 42;\n".to_string(),
10826 DiffHunkStatus::Removed,
10827 DisplayRow(2)..DisplayRow(2)
10828 )],
10829 "Should open hunks that were adjacent to the stale addition one"
10830 );
10831 });
10832}
10833
10834#[gpui::test]
10835async fn test_edits_around_toggled_deletions(
10836 executor: BackgroundExecutor,
10837 cx: &mut gpui::TestAppContext,
10838) {
10839 init_test(cx, |_| {});
10840
10841 let mut cx = EditorTestContext::new(cx).await;
10842
10843 let diff_base = r#"
10844 use some::mod1;
10845 use some::mod2;
10846
10847 const A: u32 = 42;
10848 const B: u32 = 42;
10849 const C: u32 = 42;
10850
10851
10852 fn main() {
10853 println!("hello");
10854
10855 println!("world");
10856 }
10857 "#
10858 .unindent();
10859 executor.run_until_parked();
10860 cx.set_state(
10861 &r#"
10862 use some::mod1;
10863 use some::mod2;
10864
10865 ˇconst B: u32 = 42;
10866 const C: u32 = 42;
10867
10868
10869 fn main() {
10870 println!("hello");
10871
10872 println!("world");
10873 }
10874 "#
10875 .unindent(),
10876 );
10877
10878 cx.set_diff_base(Some(&diff_base));
10879 executor.run_until_parked();
10880 cx.update_editor(|editor, cx| {
10881 let snapshot = editor.snapshot(cx);
10882 let all_hunks = editor_hunks(editor, &snapshot, cx);
10883 assert_eq!(
10884 all_hunks,
10885 vec![(
10886 "const A: u32 = 42;\n".to_string(),
10887 DiffHunkStatus::Removed,
10888 DisplayRow(3)..DisplayRow(3)
10889 )]
10890 );
10891 });
10892 cx.update_editor(|editor, cx| {
10893 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10894 });
10895 executor.run_until_parked();
10896 cx.assert_editor_state(
10897 &r#"
10898 use some::mod1;
10899 use some::mod2;
10900
10901 ˇconst B: u32 = 42;
10902 const C: u32 = 42;
10903
10904
10905 fn main() {
10906 println!("hello");
10907
10908 println!("world");
10909 }
10910 "#
10911 .unindent(),
10912 );
10913 cx.update_editor(|editor, cx| {
10914 let snapshot = editor.snapshot(cx);
10915 let all_hunks = editor_hunks(editor, &snapshot, cx);
10916 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10917 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10918 assert_eq!(
10919 all_hunks,
10920 vec![(
10921 "const A: u32 = 42;\n".to_string(),
10922 DiffHunkStatus::Removed,
10923 DisplayRow(4)..DisplayRow(4)
10924 )]
10925 );
10926 assert_eq!(all_hunks, all_expanded_hunks);
10927 });
10928
10929 cx.update_editor(|editor, cx| {
10930 editor.delete_line(&DeleteLine, cx);
10931 });
10932 executor.run_until_parked();
10933 cx.assert_editor_state(
10934 &r#"
10935 use some::mod1;
10936 use some::mod2;
10937
10938 ˇconst C: u32 = 42;
10939
10940
10941 fn main() {
10942 println!("hello");
10943
10944 println!("world");
10945 }
10946 "#
10947 .unindent(),
10948 );
10949 cx.update_editor(|editor, cx| {
10950 let snapshot = editor.snapshot(cx);
10951 let all_hunks = editor_hunks(editor, &snapshot, cx);
10952 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10953 assert_eq!(
10954 expanded_hunks_background_highlights(editor, cx),
10955 Vec::new(),
10956 "Deleted hunks do not highlight current editor's background"
10957 );
10958 assert_eq!(
10959 all_hunks,
10960 vec![(
10961 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
10962 DiffHunkStatus::Removed,
10963 DisplayRow(5)..DisplayRow(5)
10964 )]
10965 );
10966 assert_eq!(all_hunks, all_expanded_hunks);
10967 });
10968
10969 cx.update_editor(|editor, cx| {
10970 editor.delete_line(&DeleteLine, cx);
10971 });
10972 executor.run_until_parked();
10973 cx.assert_editor_state(
10974 &r#"
10975 use some::mod1;
10976 use some::mod2;
10977
10978 ˇ
10979
10980 fn main() {
10981 println!("hello");
10982
10983 println!("world");
10984 }
10985 "#
10986 .unindent(),
10987 );
10988 cx.update_editor(|editor, cx| {
10989 let snapshot = editor.snapshot(cx);
10990 let all_hunks = editor_hunks(editor, &snapshot, cx);
10991 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10992 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10993 assert_eq!(
10994 all_hunks,
10995 vec![(
10996 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10997 DiffHunkStatus::Removed,
10998 DisplayRow(6)..DisplayRow(6)
10999 )]
11000 );
11001 assert_eq!(all_hunks, all_expanded_hunks);
11002 });
11003
11004 cx.update_editor(|editor, cx| {
11005 editor.handle_input("replacement", cx);
11006 });
11007 executor.run_until_parked();
11008 cx.assert_editor_state(
11009 &r#"
11010 use some::mod1;
11011 use some::mod2;
11012
11013 replacementˇ
11014
11015 fn main() {
11016 println!("hello");
11017
11018 println!("world");
11019 }
11020 "#
11021 .unindent(),
11022 );
11023 cx.update_editor(|editor, cx| {
11024 let snapshot = editor.snapshot(cx);
11025 let all_hunks = editor_hunks(editor, &snapshot, cx);
11026 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11027 assert_eq!(
11028 all_hunks,
11029 vec![(
11030 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
11031 DiffHunkStatus::Modified,
11032 DisplayRow(7)..DisplayRow(8)
11033 )]
11034 );
11035 assert_eq!(
11036 expanded_hunks_background_highlights(editor, cx),
11037 vec![DisplayRow(7)..=DisplayRow(7)],
11038 "Modified expanded hunks should display additions and highlight their background"
11039 );
11040 assert_eq!(all_hunks, all_expanded_hunks);
11041 });
11042}
11043
11044#[gpui::test]
11045async fn test_edits_around_toggled_modifications(
11046 executor: BackgroundExecutor,
11047 cx: &mut gpui::TestAppContext,
11048) {
11049 init_test(cx, |_| {});
11050
11051 let mut cx = EditorTestContext::new(cx).await;
11052
11053 let diff_base = r#"
11054 use some::mod1;
11055 use some::mod2;
11056
11057 const A: u32 = 42;
11058 const B: u32 = 42;
11059 const C: u32 = 42;
11060 const D: u32 = 42;
11061
11062
11063 fn main() {
11064 println!("hello");
11065
11066 println!("world");
11067 }"#
11068 .unindent();
11069 executor.run_until_parked();
11070 cx.set_state(
11071 &r#"
11072 use some::mod1;
11073 use some::mod2;
11074
11075 const A: u32 = 42;
11076 const B: u32 = 42;
11077 const C: u32 = 43ˇ
11078 const D: u32 = 42;
11079
11080
11081 fn main() {
11082 println!("hello");
11083
11084 println!("world");
11085 }"#
11086 .unindent(),
11087 );
11088
11089 cx.set_diff_base(Some(&diff_base));
11090 executor.run_until_parked();
11091 cx.update_editor(|editor, cx| {
11092 let snapshot = editor.snapshot(cx);
11093 let all_hunks = editor_hunks(editor, &snapshot, cx);
11094 assert_eq!(
11095 all_hunks,
11096 vec![(
11097 "const C: u32 = 42;\n".to_string(),
11098 DiffHunkStatus::Modified,
11099 DisplayRow(5)..DisplayRow(6)
11100 )]
11101 );
11102 });
11103 cx.update_editor(|editor, cx| {
11104 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11105 });
11106 executor.run_until_parked();
11107 cx.assert_editor_state(
11108 &r#"
11109 use some::mod1;
11110 use some::mod2;
11111
11112 const A: u32 = 42;
11113 const B: u32 = 42;
11114 const C: u32 = 43ˇ
11115 const D: u32 = 42;
11116
11117
11118 fn main() {
11119 println!("hello");
11120
11121 println!("world");
11122 }"#
11123 .unindent(),
11124 );
11125 cx.update_editor(|editor, cx| {
11126 let snapshot = editor.snapshot(cx);
11127 let all_hunks = editor_hunks(editor, &snapshot, cx);
11128 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11129 assert_eq!(
11130 expanded_hunks_background_highlights(editor, cx),
11131 vec![DisplayRow(6)..=DisplayRow(6)],
11132 );
11133 assert_eq!(
11134 all_hunks,
11135 vec![(
11136 "const C: u32 = 42;\n".to_string(),
11137 DiffHunkStatus::Modified,
11138 DisplayRow(6)..DisplayRow(7)
11139 )]
11140 );
11141 assert_eq!(all_hunks, all_expanded_hunks);
11142 });
11143
11144 cx.update_editor(|editor, cx| {
11145 editor.handle_input("\nnew_line\n", cx);
11146 });
11147 executor.run_until_parked();
11148 cx.assert_editor_state(
11149 &r#"
11150 use some::mod1;
11151 use some::mod2;
11152
11153 const A: u32 = 42;
11154 const B: u32 = 42;
11155 const C: u32 = 43
11156 new_line
11157 ˇ
11158 const D: u32 = 42;
11159
11160
11161 fn main() {
11162 println!("hello");
11163
11164 println!("world");
11165 }"#
11166 .unindent(),
11167 );
11168 cx.update_editor(|editor, cx| {
11169 let snapshot = editor.snapshot(cx);
11170 let all_hunks = editor_hunks(editor, &snapshot, cx);
11171 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11172 assert_eq!(
11173 expanded_hunks_background_highlights(editor, cx),
11174 vec![DisplayRow(6)..=DisplayRow(6)],
11175 "Modified hunk should grow highlighted lines on more text additions"
11176 );
11177 assert_eq!(
11178 all_hunks,
11179 vec![(
11180 "const C: u32 = 42;\n".to_string(),
11181 DiffHunkStatus::Modified,
11182 DisplayRow(6)..DisplayRow(9)
11183 )]
11184 );
11185 assert_eq!(all_hunks, all_expanded_hunks);
11186 });
11187
11188 cx.update_editor(|editor, cx| {
11189 editor.move_up(&MoveUp, cx);
11190 editor.move_up(&MoveUp, cx);
11191 editor.move_up(&MoveUp, cx);
11192 editor.delete_line(&DeleteLine, cx);
11193 });
11194 executor.run_until_parked();
11195 cx.assert_editor_state(
11196 &r#"
11197 use some::mod1;
11198 use some::mod2;
11199
11200 const A: u32 = 42;
11201 ˇconst C: u32 = 43
11202 new_line
11203
11204 const D: u32 = 42;
11205
11206
11207 fn main() {
11208 println!("hello");
11209
11210 println!("world");
11211 }"#
11212 .unindent(),
11213 );
11214 cx.update_editor(|editor, cx| {
11215 let snapshot = editor.snapshot(cx);
11216 let all_hunks = editor_hunks(editor, &snapshot, cx);
11217 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11218 assert_eq!(
11219 expanded_hunks_background_highlights(editor, cx),
11220 vec![DisplayRow(6)..=DisplayRow(8)],
11221 );
11222 assert_eq!(
11223 all_hunks,
11224 vec![(
11225 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11226 DiffHunkStatus::Modified,
11227 DisplayRow(6)..DisplayRow(9)
11228 )],
11229 "Modified hunk should grow deleted lines on text deletions above"
11230 );
11231 assert_eq!(all_hunks, all_expanded_hunks);
11232 });
11233
11234 cx.update_editor(|editor, cx| {
11235 editor.move_up(&MoveUp, cx);
11236 editor.handle_input("v", cx);
11237 });
11238 executor.run_until_parked();
11239 cx.assert_editor_state(
11240 &r#"
11241 use some::mod1;
11242 use some::mod2;
11243
11244 vˇconst A: u32 = 42;
11245 const C: u32 = 43
11246 new_line
11247
11248 const D: u32 = 42;
11249
11250
11251 fn main() {
11252 println!("hello");
11253
11254 println!("world");
11255 }"#
11256 .unindent(),
11257 );
11258 cx.update_editor(|editor, cx| {
11259 let snapshot = editor.snapshot(cx);
11260 let all_hunks = editor_hunks(editor, &snapshot, cx);
11261 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11262 assert_eq!(
11263 expanded_hunks_background_highlights(editor, cx),
11264 vec![DisplayRow(6)..=DisplayRow(9)],
11265 "Modified hunk should grow deleted lines on text modifications above"
11266 );
11267 assert_eq!(
11268 all_hunks,
11269 vec![(
11270 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11271 DiffHunkStatus::Modified,
11272 DisplayRow(6)..DisplayRow(10)
11273 )]
11274 );
11275 assert_eq!(all_hunks, all_expanded_hunks);
11276 });
11277
11278 cx.update_editor(|editor, cx| {
11279 editor.move_down(&MoveDown, cx);
11280 editor.move_down(&MoveDown, cx);
11281 editor.delete_line(&DeleteLine, cx)
11282 });
11283 executor.run_until_parked();
11284 cx.assert_editor_state(
11285 &r#"
11286 use some::mod1;
11287 use some::mod2;
11288
11289 vconst A: u32 = 42;
11290 const C: u32 = 43
11291 ˇ
11292 const D: u32 = 42;
11293
11294
11295 fn main() {
11296 println!("hello");
11297
11298 println!("world");
11299 }"#
11300 .unindent(),
11301 );
11302 cx.update_editor(|editor, cx| {
11303 let snapshot = editor.snapshot(cx);
11304 let all_hunks = editor_hunks(editor, &snapshot, cx);
11305 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11306 assert_eq!(
11307 expanded_hunks_background_highlights(editor, cx),
11308 vec![DisplayRow(6)..=DisplayRow(8)],
11309 "Modified hunk should grow shrink lines on modification lines removal"
11310 );
11311 assert_eq!(
11312 all_hunks,
11313 vec![(
11314 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11315 DiffHunkStatus::Modified,
11316 DisplayRow(6)..DisplayRow(9)
11317 )]
11318 );
11319 assert_eq!(all_hunks, all_expanded_hunks);
11320 });
11321
11322 cx.update_editor(|editor, cx| {
11323 editor.move_up(&MoveUp, cx);
11324 editor.move_up(&MoveUp, cx);
11325 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11326 editor.delete_line(&DeleteLine, cx)
11327 });
11328 executor.run_until_parked();
11329 cx.assert_editor_state(
11330 &r#"
11331 use some::mod1;
11332 use some::mod2;
11333
11334 ˇ
11335
11336 fn main() {
11337 println!("hello");
11338
11339 println!("world");
11340 }"#
11341 .unindent(),
11342 );
11343 cx.update_editor(|editor, cx| {
11344 let snapshot = editor.snapshot(cx);
11345 let all_hunks = editor_hunks(editor, &snapshot, cx);
11346 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11347 assert_eq!(
11348 expanded_hunks_background_highlights(editor, cx),
11349 Vec::new(),
11350 "Modified hunk should turn into a removed one on all modified lines removal"
11351 );
11352 assert_eq!(
11353 all_hunks,
11354 vec![(
11355 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11356 .to_string(),
11357 DiffHunkStatus::Removed,
11358 DisplayRow(7)..DisplayRow(7)
11359 )]
11360 );
11361 assert_eq!(all_hunks, all_expanded_hunks);
11362 });
11363}
11364
11365#[gpui::test]
11366async fn test_multiple_expanded_hunks_merge(
11367 executor: BackgroundExecutor,
11368 cx: &mut gpui::TestAppContext,
11369) {
11370 init_test(cx, |_| {});
11371
11372 let mut cx = EditorTestContext::new(cx).await;
11373
11374 let diff_base = r#"
11375 use some::mod1;
11376 use some::mod2;
11377
11378 const A: u32 = 42;
11379 const B: u32 = 42;
11380 const C: u32 = 42;
11381 const D: u32 = 42;
11382
11383
11384 fn main() {
11385 println!("hello");
11386
11387 println!("world");
11388 }"#
11389 .unindent();
11390 executor.run_until_parked();
11391 cx.set_state(
11392 &r#"
11393 use some::mod1;
11394 use some::mod2;
11395
11396 const A: u32 = 42;
11397 const B: u32 = 42;
11398 const C: u32 = 43ˇ
11399 const D: u32 = 42;
11400
11401
11402 fn main() {
11403 println!("hello");
11404
11405 println!("world");
11406 }"#
11407 .unindent(),
11408 );
11409
11410 cx.set_diff_base(Some(&diff_base));
11411 executor.run_until_parked();
11412 cx.update_editor(|editor, cx| {
11413 let snapshot = editor.snapshot(cx);
11414 let all_hunks = editor_hunks(editor, &snapshot, cx);
11415 assert_eq!(
11416 all_hunks,
11417 vec![(
11418 "const C: u32 = 42;\n".to_string(),
11419 DiffHunkStatus::Modified,
11420 DisplayRow(5)..DisplayRow(6)
11421 )]
11422 );
11423 });
11424 cx.update_editor(|editor, cx| {
11425 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11426 });
11427 executor.run_until_parked();
11428 cx.assert_editor_state(
11429 &r#"
11430 use some::mod1;
11431 use some::mod2;
11432
11433 const A: u32 = 42;
11434 const B: u32 = 42;
11435 const C: u32 = 43ˇ
11436 const D: u32 = 42;
11437
11438
11439 fn main() {
11440 println!("hello");
11441
11442 println!("world");
11443 }"#
11444 .unindent(),
11445 );
11446 cx.update_editor(|editor, cx| {
11447 let snapshot = editor.snapshot(cx);
11448 let all_hunks = editor_hunks(editor, &snapshot, cx);
11449 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11450 assert_eq!(
11451 expanded_hunks_background_highlights(editor, cx),
11452 vec![DisplayRow(6)..=DisplayRow(6)],
11453 );
11454 assert_eq!(
11455 all_hunks,
11456 vec![(
11457 "const C: u32 = 42;\n".to_string(),
11458 DiffHunkStatus::Modified,
11459 DisplayRow(6)..DisplayRow(7)
11460 )]
11461 );
11462 assert_eq!(all_hunks, all_expanded_hunks);
11463 });
11464
11465 cx.update_editor(|editor, cx| {
11466 editor.handle_input("\nnew_line\n", cx);
11467 });
11468 executor.run_until_parked();
11469 cx.assert_editor_state(
11470 &r#"
11471 use some::mod1;
11472 use some::mod2;
11473
11474 const A: u32 = 42;
11475 const B: u32 = 42;
11476 const C: u32 = 43
11477 new_line
11478 ˇ
11479 const D: u32 = 42;
11480
11481
11482 fn main() {
11483 println!("hello");
11484
11485 println!("world");
11486 }"#
11487 .unindent(),
11488 );
11489}
11490
11491async fn setup_indent_guides_editor(
11492 text: &str,
11493 cx: &mut gpui::TestAppContext,
11494) -> (BufferId, EditorTestContext) {
11495 init_test(cx, |_| {});
11496
11497 let mut cx = EditorTestContext::new(cx).await;
11498
11499 let buffer_id = cx.update_editor(|editor, cx| {
11500 editor.set_text(text, cx);
11501 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11502 let buffer_id = buffer_ids[0];
11503 buffer_id
11504 });
11505
11506 (buffer_id, cx)
11507}
11508
11509fn assert_indent_guides(
11510 range: Range<u32>,
11511 expected: Vec<IndentGuide>,
11512 active_indices: Option<Vec<usize>>,
11513 cx: &mut EditorTestContext,
11514) {
11515 let indent_guides = cx.update_editor(|editor, cx| {
11516 let snapshot = editor.snapshot(cx).display_snapshot;
11517 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11518 MultiBufferRow(range.start)..MultiBufferRow(range.end),
11519 true,
11520 &snapshot,
11521 cx,
11522 );
11523
11524 indent_guides.sort_by(|a, b| {
11525 a.depth.cmp(&b.depth).then(
11526 a.start_row
11527 .cmp(&b.start_row)
11528 .then(a.end_row.cmp(&b.end_row)),
11529 )
11530 });
11531 indent_guides
11532 });
11533
11534 if let Some(expected) = active_indices {
11535 let active_indices = cx.update_editor(|editor, cx| {
11536 let snapshot = editor.snapshot(cx).display_snapshot;
11537 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11538 });
11539
11540 assert_eq!(
11541 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11542 expected,
11543 "Active indent guide indices do not match"
11544 );
11545 }
11546
11547 let expected: Vec<_> = expected
11548 .into_iter()
11549 .map(|guide| MultiBufferIndentGuide {
11550 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11551 buffer: guide,
11552 })
11553 .collect();
11554
11555 assert_eq!(indent_guides, expected, "Indent guides do not match");
11556}
11557
11558fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
11559 IndentGuide {
11560 buffer_id,
11561 start_row,
11562 end_row,
11563 depth,
11564 tab_size: 4,
11565 settings: IndentGuideSettings {
11566 enabled: true,
11567 line_width: 1,
11568 ..Default::default()
11569 },
11570 }
11571}
11572
11573#[gpui::test]
11574async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11575 let (buffer_id, mut cx) = setup_indent_guides_editor(
11576 &"
11577 fn main() {
11578 let a = 1;
11579 }"
11580 .unindent(),
11581 cx,
11582 )
11583 .await;
11584
11585 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11586}
11587
11588#[gpui::test]
11589async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11590 let (buffer_id, mut cx) = setup_indent_guides_editor(
11591 &"
11592 fn main() {
11593 let a = 1;
11594 let b = 2;
11595 }"
11596 .unindent(),
11597 cx,
11598 )
11599 .await;
11600
11601 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
11602}
11603
11604#[gpui::test]
11605async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11606 let (buffer_id, mut cx) = setup_indent_guides_editor(
11607 &"
11608 fn main() {
11609 let a = 1;
11610 if a == 3 {
11611 let b = 2;
11612 } else {
11613 let c = 3;
11614 }
11615 }"
11616 .unindent(),
11617 cx,
11618 )
11619 .await;
11620
11621 assert_indent_guides(
11622 0..8,
11623 vec![
11624 indent_guide(buffer_id, 1, 6, 0),
11625 indent_guide(buffer_id, 3, 3, 1),
11626 indent_guide(buffer_id, 5, 5, 1),
11627 ],
11628 None,
11629 &mut cx,
11630 );
11631}
11632
11633#[gpui::test]
11634async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11635 let (buffer_id, mut cx) = setup_indent_guides_editor(
11636 &"
11637 fn main() {
11638 let a = 1;
11639 let b = 2;
11640 let c = 3;
11641 }"
11642 .unindent(),
11643 cx,
11644 )
11645 .await;
11646
11647 assert_indent_guides(
11648 0..5,
11649 vec![
11650 indent_guide(buffer_id, 1, 3, 0),
11651 indent_guide(buffer_id, 2, 2, 1),
11652 ],
11653 None,
11654 &mut cx,
11655 );
11656}
11657
11658#[gpui::test]
11659async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
11660 let (buffer_id, mut cx) = setup_indent_guides_editor(
11661 &"
11662 fn main() {
11663 let a = 1;
11664
11665 let c = 3;
11666 }"
11667 .unindent(),
11668 cx,
11669 )
11670 .await;
11671
11672 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
11673}
11674
11675#[gpui::test]
11676async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
11677 let (buffer_id, mut cx) = setup_indent_guides_editor(
11678 &"
11679 fn main() {
11680 let a = 1;
11681
11682 let c = 3;
11683
11684 if a == 3 {
11685 let b = 2;
11686 } else {
11687 let c = 3;
11688 }
11689 }"
11690 .unindent(),
11691 cx,
11692 )
11693 .await;
11694
11695 assert_indent_guides(
11696 0..11,
11697 vec![
11698 indent_guide(buffer_id, 1, 9, 0),
11699 indent_guide(buffer_id, 6, 6, 1),
11700 indent_guide(buffer_id, 8, 8, 1),
11701 ],
11702 None,
11703 &mut cx,
11704 );
11705}
11706
11707#[gpui::test]
11708async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
11709 let (buffer_id, mut cx) = setup_indent_guides_editor(
11710 &"
11711 fn main() {
11712 let a = 1;
11713
11714 let c = 3;
11715
11716 if a == 3 {
11717 let b = 2;
11718 } else {
11719 let c = 3;
11720 }
11721 }"
11722 .unindent(),
11723 cx,
11724 )
11725 .await;
11726
11727 assert_indent_guides(
11728 1..11,
11729 vec![
11730 indent_guide(buffer_id, 1, 9, 0),
11731 indent_guide(buffer_id, 6, 6, 1),
11732 indent_guide(buffer_id, 8, 8, 1),
11733 ],
11734 None,
11735 &mut cx,
11736 );
11737}
11738
11739#[gpui::test]
11740async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
11741 let (buffer_id, mut cx) = setup_indent_guides_editor(
11742 &"
11743 fn main() {
11744 let a = 1;
11745
11746 let c = 3;
11747
11748 if a == 3 {
11749 let b = 2;
11750 } else {
11751 let c = 3;
11752 }
11753 }"
11754 .unindent(),
11755 cx,
11756 )
11757 .await;
11758
11759 assert_indent_guides(
11760 1..10,
11761 vec![
11762 indent_guide(buffer_id, 1, 9, 0),
11763 indent_guide(buffer_id, 6, 6, 1),
11764 indent_guide(buffer_id, 8, 8, 1),
11765 ],
11766 None,
11767 &mut cx,
11768 );
11769}
11770
11771#[gpui::test]
11772async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
11773 let (buffer_id, mut cx) = setup_indent_guides_editor(
11774 &"
11775 block1
11776 block2
11777 block3
11778 block4
11779 block2
11780 block1
11781 block1"
11782 .unindent(),
11783 cx,
11784 )
11785 .await;
11786
11787 assert_indent_guides(
11788 1..10,
11789 vec![
11790 indent_guide(buffer_id, 1, 4, 0),
11791 indent_guide(buffer_id, 2, 3, 1),
11792 indent_guide(buffer_id, 3, 3, 2),
11793 ],
11794 None,
11795 &mut cx,
11796 );
11797}
11798
11799#[gpui::test]
11800async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
11801 let (buffer_id, mut cx) = setup_indent_guides_editor(
11802 &"
11803 block1
11804 block2
11805 block3
11806
11807 block1
11808 block1"
11809 .unindent(),
11810 cx,
11811 )
11812 .await;
11813
11814 assert_indent_guides(
11815 0..6,
11816 vec![
11817 indent_guide(buffer_id, 1, 2, 0),
11818 indent_guide(buffer_id, 2, 2, 1),
11819 ],
11820 None,
11821 &mut cx,
11822 );
11823}
11824
11825#[gpui::test]
11826async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
11827 let (buffer_id, mut cx) = setup_indent_guides_editor(
11828 &"
11829 block1
11830
11831
11832
11833 block2
11834 "
11835 .unindent(),
11836 cx,
11837 )
11838 .await;
11839
11840 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11841}
11842
11843#[gpui::test]
11844async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
11845 let (buffer_id, mut cx) = setup_indent_guides_editor(
11846 &"
11847 def a:
11848 \tb = 3
11849 \tif True:
11850 \t\tc = 4
11851 \t\td = 5
11852 \tprint(b)
11853 "
11854 .unindent(),
11855 cx,
11856 )
11857 .await;
11858
11859 assert_indent_guides(
11860 0..6,
11861 vec![
11862 indent_guide(buffer_id, 1, 6, 0),
11863 indent_guide(buffer_id, 3, 4, 1),
11864 ],
11865 None,
11866 &mut cx,
11867 );
11868}
11869
11870#[gpui::test]
11871async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11872 let (buffer_id, mut cx) = setup_indent_guides_editor(
11873 &"
11874 fn main() {
11875 let a = 1;
11876 }"
11877 .unindent(),
11878 cx,
11879 )
11880 .await;
11881
11882 cx.update_editor(|editor, cx| {
11883 editor.change_selections(None, cx, |s| {
11884 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
11885 });
11886 });
11887
11888 assert_indent_guides(
11889 0..3,
11890 vec![indent_guide(buffer_id, 1, 1, 0)],
11891 Some(vec![0]),
11892 &mut cx,
11893 );
11894}
11895
11896#[gpui::test]
11897async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
11898 let (buffer_id, mut cx) = setup_indent_guides_editor(
11899 &"
11900 fn main() {
11901 if 1 == 2 {
11902 let a = 1;
11903 }
11904 }"
11905 .unindent(),
11906 cx,
11907 )
11908 .await;
11909
11910 cx.update_editor(|editor, cx| {
11911 editor.change_selections(None, cx, |s| {
11912 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
11913 });
11914 });
11915
11916 assert_indent_guides(
11917 0..4,
11918 vec![
11919 indent_guide(buffer_id, 1, 3, 0),
11920 indent_guide(buffer_id, 2, 2, 1),
11921 ],
11922 Some(vec![1]),
11923 &mut cx,
11924 );
11925
11926 cx.update_editor(|editor, cx| {
11927 editor.change_selections(None, cx, |s| {
11928 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
11929 });
11930 });
11931
11932 assert_indent_guides(
11933 0..4,
11934 vec![
11935 indent_guide(buffer_id, 1, 3, 0),
11936 indent_guide(buffer_id, 2, 2, 1),
11937 ],
11938 Some(vec![1]),
11939 &mut cx,
11940 );
11941
11942 cx.update_editor(|editor, cx| {
11943 editor.change_selections(None, cx, |s| {
11944 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
11945 });
11946 });
11947
11948 assert_indent_guides(
11949 0..4,
11950 vec![
11951 indent_guide(buffer_id, 1, 3, 0),
11952 indent_guide(buffer_id, 2, 2, 1),
11953 ],
11954 Some(vec![0]),
11955 &mut cx,
11956 );
11957}
11958
11959#[gpui::test]
11960async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
11961 let (buffer_id, mut cx) = setup_indent_guides_editor(
11962 &"
11963 fn main() {
11964 let a = 1;
11965
11966 let b = 2;
11967 }"
11968 .unindent(),
11969 cx,
11970 )
11971 .await;
11972
11973 cx.update_editor(|editor, cx| {
11974 editor.change_selections(None, cx, |s| {
11975 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
11976 });
11977 });
11978
11979 assert_indent_guides(
11980 0..5,
11981 vec![indent_guide(buffer_id, 1, 3, 0)],
11982 Some(vec![0]),
11983 &mut cx,
11984 );
11985}
11986
11987#[gpui::test]
11988async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
11989 let (buffer_id, mut cx) = setup_indent_guides_editor(
11990 &"
11991 def m:
11992 a = 1
11993 pass"
11994 .unindent(),
11995 cx,
11996 )
11997 .await;
11998
11999 cx.update_editor(|editor, cx| {
12000 editor.change_selections(None, cx, |s| {
12001 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12002 });
12003 });
12004
12005 assert_indent_guides(
12006 0..3,
12007 vec![indent_guide(buffer_id, 1, 2, 0)],
12008 Some(vec![0]),
12009 &mut cx,
12010 );
12011}
12012
12013#[gpui::test]
12014fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
12015 init_test(cx, |_| {});
12016
12017 let editor = cx.add_window(|cx| {
12018 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12019 build_editor(buffer, cx)
12020 });
12021
12022 let render_args = Arc::new(Mutex::new(None));
12023 let snapshot = editor
12024 .update(cx, |editor, cx| {
12025 let snapshot = editor.buffer().read(cx).snapshot(cx);
12026 let range =
12027 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12028
12029 struct RenderArgs {
12030 row: MultiBufferRow,
12031 folded: bool,
12032 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12033 }
12034
12035 let flap = Flap::new(
12036 range,
12037 FoldPlaceholder::test(),
12038 {
12039 let toggle_callback = render_args.clone();
12040 move |row, folded, callback, _cx| {
12041 *toggle_callback.lock() = Some(RenderArgs {
12042 row,
12043 folded,
12044 callback,
12045 });
12046 div()
12047 }
12048 },
12049 |_row, _folded, _cx| div(),
12050 );
12051
12052 editor.insert_flaps(Some(flap), cx);
12053 let snapshot = editor.snapshot(cx);
12054 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12055 snapshot
12056 })
12057 .unwrap();
12058
12059 let render_args = render_args.lock().take().unwrap();
12060 assert_eq!(render_args.row, MultiBufferRow(1));
12061 assert_eq!(render_args.folded, false);
12062 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12063
12064 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12065 .unwrap();
12066 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12067 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12068
12069 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12070 .unwrap();
12071 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12072 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12073}
12074
12075fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12076 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12077 point..point
12078}
12079
12080fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12081 let (text, ranges) = marked_text_ranges(marked_text, true);
12082 assert_eq!(view.text(cx), text);
12083 assert_eq!(
12084 view.selections.ranges(cx),
12085 ranges,
12086 "Assert selections are {}",
12087 marked_text
12088 );
12089}
12090
12091/// Handle completion request passing a marked string specifying where the completion
12092/// should be triggered from using '|' character, what range should be replaced, and what completions
12093/// should be returned using '<' and '>' to delimit the range
12094pub fn handle_completion_request(
12095 cx: &mut EditorLspTestContext,
12096 marked_string: &str,
12097 completions: Vec<&'static str>,
12098 counter: Arc<AtomicUsize>,
12099) -> impl Future<Output = ()> {
12100 let complete_from_marker: TextRangeMarker = '|'.into();
12101 let replace_range_marker: TextRangeMarker = ('<', '>').into();
12102 let (_, mut marked_ranges) = marked_text_ranges_by(
12103 marked_string,
12104 vec![complete_from_marker.clone(), replace_range_marker.clone()],
12105 );
12106
12107 let complete_from_position =
12108 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12109 let replace_range =
12110 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12111
12112 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12113 let completions = completions.clone();
12114 counter.fetch_add(1, atomic::Ordering::Release);
12115 async move {
12116 assert_eq!(params.text_document_position.text_document.uri, url.clone());
12117 assert_eq!(
12118 params.text_document_position.position,
12119 complete_from_position
12120 );
12121 Ok(Some(lsp::CompletionResponse::Array(
12122 completions
12123 .iter()
12124 .map(|completion_text| lsp::CompletionItem {
12125 label: completion_text.to_string(),
12126 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12127 range: replace_range,
12128 new_text: completion_text.to_string(),
12129 })),
12130 ..Default::default()
12131 })
12132 .collect(),
12133 )))
12134 }
12135 });
12136
12137 async move {
12138 request.next().await;
12139 }
12140}
12141
12142fn handle_resolve_completion_request(
12143 cx: &mut EditorLspTestContext,
12144 edits: Option<Vec<(&'static str, &'static str)>>,
12145) -> impl Future<Output = ()> {
12146 let edits = edits.map(|edits| {
12147 edits
12148 .iter()
12149 .map(|(marked_string, new_text)| {
12150 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12151 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12152 lsp::TextEdit::new(replace_range, new_text.to_string())
12153 })
12154 .collect::<Vec<_>>()
12155 });
12156
12157 let mut request =
12158 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12159 let edits = edits.clone();
12160 async move {
12161 Ok(lsp::CompletionItem {
12162 additional_text_edits: edits,
12163 ..Default::default()
12164 })
12165 }
12166 });
12167
12168 async move {
12169 request.next().await;
12170 }
12171}
12172
12173pub(crate) fn update_test_language_settings(
12174 cx: &mut TestAppContext,
12175 f: impl Fn(&mut AllLanguageSettingsContent),
12176) {
12177 _ = cx.update(|cx| {
12178 SettingsStore::update_global(cx, |store, cx| {
12179 store.update_user_settings::<AllLanguageSettings>(cx, f);
12180 });
12181 });
12182}
12183
12184pub(crate) fn update_test_project_settings(
12185 cx: &mut TestAppContext,
12186 f: impl Fn(&mut ProjectSettings),
12187) {
12188 _ = cx.update(|cx| {
12189 SettingsStore::update_global(cx, |store, cx| {
12190 store.update_user_settings::<ProjectSettings>(cx, f);
12191 });
12192 });
12193}
12194
12195pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12196 _ = cx.update(|cx| {
12197 cx.text_system()
12198 .add_fonts(vec![assets::Assets
12199 .load("fonts/zed-mono/zed-mono-extended.ttf")
12200 .unwrap()
12201 .unwrap()])
12202 .unwrap();
12203 let store = SettingsStore::test(cx);
12204 cx.set_global(store);
12205 theme::init(theme::LoadThemes::JustBase, cx);
12206 release_channel::init(SemanticVersion::default(), cx);
12207 client::init_settings(cx);
12208 language::init(cx);
12209 Project::init_settings(cx);
12210 workspace::init_settings(cx);
12211 crate::init(cx);
12212 });
12213
12214 update_test_language_settings(cx, f);
12215}
12216
12217pub(crate) fn rust_lang() -> Arc<Language> {
12218 Arc::new(Language::new(
12219 LanguageConfig {
12220 name: "Rust".into(),
12221 matcher: LanguageMatcher {
12222 path_suffixes: vec!["rs".to_string()],
12223 ..Default::default()
12224 },
12225 ..Default::default()
12226 },
12227 Some(tree_sitter_rust::language()),
12228 ))
12229}
12230
12231#[track_caller]
12232fn assert_hunk_revert(
12233 not_reverted_text_with_selections: &str,
12234 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12235 expected_reverted_text_with_selections: &str,
12236 base_text: &str,
12237 cx: &mut EditorLspTestContext,
12238) {
12239 cx.set_state(not_reverted_text_with_selections);
12240 cx.update_editor(|editor, cx| {
12241 editor
12242 .buffer()
12243 .read(cx)
12244 .as_singleton()
12245 .unwrap()
12246 .update(cx, |buffer, cx| {
12247 buffer.set_diff_base(Some(base_text.into()), cx);
12248 });
12249 });
12250 cx.executor().run_until_parked();
12251
12252 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12253 let snapshot = editor.buffer().read(cx).snapshot(cx);
12254 let reverted_hunk_statuses = snapshot
12255 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12256 .map(|hunk| hunk_status(&hunk))
12257 .collect::<Vec<_>>();
12258
12259 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12260 reverted_hunk_statuses
12261 });
12262 cx.executor().run_until_parked();
12263 cx.assert_editor_state(expected_reverted_text_with_selections);
12264 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12265}