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