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