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