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