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