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