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