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