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