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 =
6037 cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
6038
6039 multi_buffer_editor.update(cx, |editor, cx| {
6040 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6041 editor.insert("|one|two|three|", cx);
6042 });
6043 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6044 multi_buffer_editor.update(cx, |editor, cx| {
6045 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6046 s.select_ranges(Some(60..70))
6047 });
6048 editor.insert("|four|five|six|", cx);
6049 });
6050 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6051
6052 // First two buffers should be edited, but not the third one.
6053 assert_eq!(
6054 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6055 "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}",
6056 );
6057 buffer_1.update(cx, |buffer, _| {
6058 assert!(buffer.is_dirty());
6059 assert_eq!(
6060 buffer.text(),
6061 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6062 )
6063 });
6064 buffer_2.update(cx, |buffer, _| {
6065 assert!(buffer.is_dirty());
6066 assert_eq!(
6067 buffer.text(),
6068 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6069 )
6070 });
6071 buffer_3.update(cx, |buffer, _| {
6072 assert!(!buffer.is_dirty());
6073 assert_eq!(buffer.text(), sample_text_3,)
6074 });
6075
6076 cx.executor().start_waiting();
6077 let save = multi_buffer_editor
6078 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6079 .unwrap();
6080
6081 let fake_server = fake_servers.next().await.unwrap();
6082 fake_server
6083 .server
6084 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6085 Ok(Some(vec![lsp::TextEdit::new(
6086 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6087 format!("[{} formatted]", params.text_document.uri),
6088 )]))
6089 })
6090 .detach();
6091 save.await;
6092
6093 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6094 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6095 assert_eq!(
6096 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6097 "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}",
6098 );
6099 buffer_1.update(cx, |buffer, _| {
6100 assert!(!buffer.is_dirty());
6101 assert_eq!(
6102 buffer.text(),
6103 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6104 )
6105 });
6106 buffer_2.update(cx, |buffer, _| {
6107 assert!(!buffer.is_dirty());
6108 assert_eq!(
6109 buffer.text(),
6110 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6111 )
6112 });
6113 buffer_3.update(cx, |buffer, _| {
6114 assert!(!buffer.is_dirty());
6115 assert_eq!(buffer.text(), sample_text_3,)
6116 });
6117}
6118
6119#[gpui::test]
6120async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6121 init_test(cx, |_| {});
6122
6123 let fs = FakeFs::new(cx.executor());
6124 fs.insert_file("/file.rs", Default::default()).await;
6125
6126 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6127
6128 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6129 language_registry.add(rust_lang());
6130 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6131 "Rust",
6132 FakeLspAdapter {
6133 capabilities: lsp::ServerCapabilities {
6134 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6135 ..Default::default()
6136 },
6137 ..Default::default()
6138 },
6139 );
6140
6141 let buffer = project
6142 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6143 .await
6144 .unwrap();
6145
6146 cx.executor().start_waiting();
6147 let fake_server = fake_servers.next().await.unwrap();
6148
6149 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6150 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6151 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6152 assert!(cx.read(|cx| editor.is_dirty(cx)));
6153
6154 let save = editor
6155 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6156 .unwrap();
6157 fake_server
6158 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6159 assert_eq!(
6160 params.text_document.uri,
6161 lsp::Url::from_file_path("/file.rs").unwrap()
6162 );
6163 assert_eq!(params.options.tab_size, 4);
6164 Ok(Some(vec![lsp::TextEdit::new(
6165 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6166 ", ".to_string(),
6167 )]))
6168 })
6169 .next()
6170 .await;
6171 cx.executor().start_waiting();
6172 save.await;
6173 assert_eq!(
6174 editor.update(cx, |editor, cx| editor.text(cx)),
6175 "one, two\nthree\n"
6176 );
6177 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6178
6179 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6180 assert!(cx.read(|cx| editor.is_dirty(cx)));
6181
6182 // Ensure we can still save even if formatting hangs.
6183 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6184 move |params, _| async move {
6185 assert_eq!(
6186 params.text_document.uri,
6187 lsp::Url::from_file_path("/file.rs").unwrap()
6188 );
6189 futures::future::pending::<()>().await;
6190 unreachable!()
6191 },
6192 );
6193 let save = editor
6194 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6195 .unwrap();
6196 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6197 cx.executor().start_waiting();
6198 save.await;
6199 assert_eq!(
6200 editor.update(cx, |editor, cx| editor.text(cx)),
6201 "one\ntwo\nthree\n"
6202 );
6203 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6204
6205 // For non-dirty buffer, no formatting request should be sent
6206 let save = editor
6207 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6208 .unwrap();
6209 let _pending_format_request = fake_server
6210 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6211 panic!("Should not be invoked on non-dirty buffer");
6212 })
6213 .next();
6214 cx.executor().start_waiting();
6215 save.await;
6216
6217 // Set Rust language override and assert overridden tabsize is sent to language server
6218 update_test_language_settings(cx, |settings| {
6219 settings.languages.insert(
6220 "Rust".into(),
6221 LanguageSettingsContent {
6222 tab_size: NonZeroU32::new(8),
6223 ..Default::default()
6224 },
6225 );
6226 });
6227
6228 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6229 assert!(cx.read(|cx| editor.is_dirty(cx)));
6230 let save = editor
6231 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6232 .unwrap();
6233 fake_server
6234 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6235 assert_eq!(
6236 params.text_document.uri,
6237 lsp::Url::from_file_path("/file.rs").unwrap()
6238 );
6239 assert_eq!(params.options.tab_size, 8);
6240 Ok(Some(vec![]))
6241 })
6242 .next()
6243 .await;
6244 cx.executor().start_waiting();
6245 save.await;
6246}
6247
6248#[gpui::test]
6249async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6250 init_test(cx, |settings| {
6251 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
6252 });
6253
6254 let fs = FakeFs::new(cx.executor());
6255 fs.insert_file("/file.rs", Default::default()).await;
6256
6257 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6258
6259 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6260 language_registry.add(Arc::new(Language::new(
6261 LanguageConfig {
6262 name: "Rust".into(),
6263 matcher: LanguageMatcher {
6264 path_suffixes: vec!["rs".to_string()],
6265 ..Default::default()
6266 },
6267 ..LanguageConfig::default()
6268 },
6269 Some(tree_sitter_rust::language()),
6270 )));
6271 update_test_language_settings(cx, |settings| {
6272 // Enable Prettier formatting for the same buffer, and ensure
6273 // LSP is called instead of Prettier.
6274 settings.defaults.prettier = Some(PrettierSettings {
6275 allowed: true,
6276 ..PrettierSettings::default()
6277 });
6278 });
6279 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6280 "Rust",
6281 FakeLspAdapter {
6282 capabilities: lsp::ServerCapabilities {
6283 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6284 ..Default::default()
6285 },
6286 ..Default::default()
6287 },
6288 );
6289
6290 let buffer = project
6291 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6292 .await
6293 .unwrap();
6294
6295 cx.executor().start_waiting();
6296 let fake_server = fake_servers.next().await.unwrap();
6297
6298 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6299 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6300 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6301
6302 let format = editor
6303 .update(cx, |editor, cx| {
6304 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6305 })
6306 .unwrap();
6307 fake_server
6308 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6309 assert_eq!(
6310 params.text_document.uri,
6311 lsp::Url::from_file_path("/file.rs").unwrap()
6312 );
6313 assert_eq!(params.options.tab_size, 4);
6314 Ok(Some(vec![lsp::TextEdit::new(
6315 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6316 ", ".to_string(),
6317 )]))
6318 })
6319 .next()
6320 .await;
6321 cx.executor().start_waiting();
6322 format.await;
6323 assert_eq!(
6324 editor.update(cx, |editor, cx| editor.text(cx)),
6325 "one, two\nthree\n"
6326 );
6327
6328 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6329 // Ensure we don't lock if formatting hangs.
6330 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6331 assert_eq!(
6332 params.text_document.uri,
6333 lsp::Url::from_file_path("/file.rs").unwrap()
6334 );
6335 futures::future::pending::<()>().await;
6336 unreachable!()
6337 });
6338 let format = editor
6339 .update(cx, |editor, cx| {
6340 editor.perform_format(project, FormatTrigger::Manual, cx)
6341 })
6342 .unwrap();
6343 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6344 cx.executor().start_waiting();
6345 format.await;
6346 assert_eq!(
6347 editor.update(cx, |editor, cx| editor.text(cx)),
6348 "one\ntwo\nthree\n"
6349 );
6350}
6351
6352#[gpui::test]
6353async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6354 init_test(cx, |_| {});
6355
6356 let mut cx = EditorLspTestContext::new_rust(
6357 lsp::ServerCapabilities {
6358 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6359 ..Default::default()
6360 },
6361 cx,
6362 )
6363 .await;
6364
6365 cx.set_state(indoc! {"
6366 one.twoˇ
6367 "});
6368
6369 // The format request takes a long time. When it completes, it inserts
6370 // a newline and an indent before the `.`
6371 cx.lsp
6372 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6373 let executor = cx.background_executor().clone();
6374 async move {
6375 executor.timer(Duration::from_millis(100)).await;
6376 Ok(Some(vec![lsp::TextEdit {
6377 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6378 new_text: "\n ".into(),
6379 }]))
6380 }
6381 });
6382
6383 // Submit a format request.
6384 let format_1 = cx
6385 .update_editor(|editor, cx| editor.format(&Format, cx))
6386 .unwrap();
6387 cx.executor().run_until_parked();
6388
6389 // Submit a second format request.
6390 let format_2 = cx
6391 .update_editor(|editor, cx| editor.format(&Format, cx))
6392 .unwrap();
6393 cx.executor().run_until_parked();
6394
6395 // Wait for both format requests to complete
6396 cx.executor().advance_clock(Duration::from_millis(200));
6397 cx.executor().start_waiting();
6398 format_1.await.unwrap();
6399 cx.executor().start_waiting();
6400 format_2.await.unwrap();
6401
6402 // The formatting edits only happens once.
6403 cx.assert_editor_state(indoc! {"
6404 one
6405 .twoˇ
6406 "});
6407}
6408
6409#[gpui::test]
6410async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6411 init_test(cx, |settings| {
6412 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
6413 });
6414
6415 let mut cx = EditorLspTestContext::new_rust(
6416 lsp::ServerCapabilities {
6417 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6418 ..Default::default()
6419 },
6420 cx,
6421 )
6422 .await;
6423
6424 // Set up a buffer white some trailing whitespace and no trailing newline.
6425 cx.set_state(
6426 &[
6427 "one ", //
6428 "twoˇ", //
6429 "three ", //
6430 "four", //
6431 ]
6432 .join("\n"),
6433 );
6434
6435 // Submit a format request.
6436 let format = cx
6437 .update_editor(|editor, cx| editor.format(&Format, cx))
6438 .unwrap();
6439
6440 // Record which buffer changes have been sent to the language server
6441 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6442 cx.lsp
6443 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6444 let buffer_changes = buffer_changes.clone();
6445 move |params, _| {
6446 buffer_changes.lock().extend(
6447 params
6448 .content_changes
6449 .into_iter()
6450 .map(|e| (e.range.unwrap(), e.text)),
6451 );
6452 }
6453 });
6454
6455 // Handle formatting requests to the language server.
6456 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6457 let buffer_changes = buffer_changes.clone();
6458 move |_, _| {
6459 // When formatting is requested, trailing whitespace has already been stripped,
6460 // and the trailing newline has already been added.
6461 assert_eq!(
6462 &buffer_changes.lock()[1..],
6463 &[
6464 (
6465 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6466 "".into()
6467 ),
6468 (
6469 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6470 "".into()
6471 ),
6472 (
6473 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6474 "\n".into()
6475 ),
6476 ]
6477 );
6478
6479 // Insert blank lines between each line of the buffer.
6480 async move {
6481 Ok(Some(vec![
6482 lsp::TextEdit {
6483 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6484 new_text: "\n".into(),
6485 },
6486 lsp::TextEdit {
6487 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6488 new_text: "\n".into(),
6489 },
6490 ]))
6491 }
6492 }
6493 });
6494
6495 // After formatting the buffer, the trailing whitespace is stripped,
6496 // a newline is appended, and the edits provided by the language server
6497 // have been applied.
6498 format.await.unwrap();
6499 cx.assert_editor_state(
6500 &[
6501 "one", //
6502 "", //
6503 "twoˇ", //
6504 "", //
6505 "three", //
6506 "four", //
6507 "", //
6508 ]
6509 .join("\n"),
6510 );
6511
6512 // Undoing the formatting undoes the trailing whitespace removal, the
6513 // trailing newline, and the LSP edits.
6514 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6515 cx.assert_editor_state(
6516 &[
6517 "one ", //
6518 "twoˇ", //
6519 "three ", //
6520 "four", //
6521 ]
6522 .join("\n"),
6523 );
6524}
6525
6526#[gpui::test]
6527async fn test_completion(cx: &mut gpui::TestAppContext) {
6528 init_test(cx, |_| {});
6529
6530 let mut cx = EditorLspTestContext::new_rust(
6531 lsp::ServerCapabilities {
6532 completion_provider: Some(lsp::CompletionOptions {
6533 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6534 resolve_provider: Some(true),
6535 ..Default::default()
6536 }),
6537 ..Default::default()
6538 },
6539 cx,
6540 )
6541 .await;
6542 let counter = Arc::new(AtomicUsize::new(0));
6543
6544 cx.set_state(indoc! {"
6545 oneˇ
6546 two
6547 three
6548 "});
6549 cx.simulate_keystroke(".");
6550 handle_completion_request(
6551 &mut cx,
6552 indoc! {"
6553 one.|<>
6554 two
6555 three
6556 "},
6557 vec!["first_completion", "second_completion"],
6558 counter.clone(),
6559 )
6560 .await;
6561 cx.condition(|editor, _| editor.context_menu_visible())
6562 .await;
6563 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
6564
6565 let apply_additional_edits = cx.update_editor(|editor, cx| {
6566 editor.context_menu_next(&Default::default(), cx);
6567 editor
6568 .confirm_completion(&ConfirmCompletion::default(), cx)
6569 .unwrap()
6570 });
6571 cx.assert_editor_state(indoc! {"
6572 one.second_completionˇ
6573 two
6574 three
6575 "});
6576
6577 handle_resolve_completion_request(
6578 &mut cx,
6579 Some(vec![
6580 (
6581 //This overlaps with the primary completion edit which is
6582 //misbehavior from the LSP spec, test that we filter it out
6583 indoc! {"
6584 one.second_ˇcompletion
6585 two
6586 threeˇ
6587 "},
6588 "overlapping additional edit",
6589 ),
6590 (
6591 indoc! {"
6592 one.second_completion
6593 two
6594 threeˇ
6595 "},
6596 "\nadditional edit",
6597 ),
6598 ]),
6599 )
6600 .await;
6601 apply_additional_edits.await.unwrap();
6602 cx.assert_editor_state(indoc! {"
6603 one.second_completionˇ
6604 two
6605 three
6606 additional edit
6607 "});
6608
6609 cx.set_state(indoc! {"
6610 one.second_completion
6611 twoˇ
6612 threeˇ
6613 additional edit
6614 "});
6615 cx.simulate_keystroke(" ");
6616 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6617 cx.simulate_keystroke("s");
6618 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6619
6620 cx.assert_editor_state(indoc! {"
6621 one.second_completion
6622 two sˇ
6623 three sˇ
6624 additional edit
6625 "});
6626 handle_completion_request(
6627 &mut cx,
6628 indoc! {"
6629 one.second_completion
6630 two s
6631 three <s|>
6632 additional edit
6633 "},
6634 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6635 counter.clone(),
6636 )
6637 .await;
6638 cx.condition(|editor, _| editor.context_menu_visible())
6639 .await;
6640 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
6641
6642 cx.simulate_keystroke("i");
6643
6644 handle_completion_request(
6645 &mut cx,
6646 indoc! {"
6647 one.second_completion
6648 two si
6649 three <si|>
6650 additional edit
6651 "},
6652 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6653 counter.clone(),
6654 )
6655 .await;
6656 cx.condition(|editor, _| editor.context_menu_visible())
6657 .await;
6658 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
6659
6660 let apply_additional_edits = cx.update_editor(|editor, cx| {
6661 editor
6662 .confirm_completion(&ConfirmCompletion::default(), cx)
6663 .unwrap()
6664 });
6665 cx.assert_editor_state(indoc! {"
6666 one.second_completion
6667 two sixth_completionˇ
6668 three sixth_completionˇ
6669 additional edit
6670 "});
6671
6672 handle_resolve_completion_request(&mut cx, None).await;
6673 apply_additional_edits.await.unwrap();
6674
6675 _ = cx.update(|cx| {
6676 cx.update_global::<SettingsStore, _>(|settings, cx| {
6677 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6678 settings.show_completions_on_input = Some(false);
6679 });
6680 })
6681 });
6682 cx.set_state("editorˇ");
6683 cx.simulate_keystroke(".");
6684 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6685 cx.simulate_keystroke("c");
6686 cx.simulate_keystroke("l");
6687 cx.simulate_keystroke("o");
6688 cx.assert_editor_state("editor.cloˇ");
6689 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6690 cx.update_editor(|editor, cx| {
6691 editor.show_completions(&ShowCompletions, cx);
6692 });
6693 handle_completion_request(
6694 &mut cx,
6695 "editor.<clo|>",
6696 vec!["close", "clobber"],
6697 counter.clone(),
6698 )
6699 .await;
6700 cx.condition(|editor, _| editor.context_menu_visible())
6701 .await;
6702 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
6703
6704 let apply_additional_edits = cx.update_editor(|editor, cx| {
6705 editor
6706 .confirm_completion(&ConfirmCompletion::default(), cx)
6707 .unwrap()
6708 });
6709 cx.assert_editor_state("editor.closeˇ");
6710 handle_resolve_completion_request(&mut cx, None).await;
6711 apply_additional_edits.await.unwrap();
6712}
6713
6714#[gpui::test]
6715async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
6716 init_test(cx, |_| {});
6717
6718 let mut cx = EditorLspTestContext::new_rust(
6719 lsp::ServerCapabilities {
6720 completion_provider: Some(lsp::CompletionOptions {
6721 trigger_characters: Some(vec![".".to_string()]),
6722 resolve_provider: Some(true),
6723 ..Default::default()
6724 }),
6725 ..Default::default()
6726 },
6727 cx,
6728 )
6729 .await;
6730
6731 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
6732 cx.simulate_keystroke(".");
6733 let completion_item = lsp::CompletionItem {
6734 label: "Some".into(),
6735 kind: Some(lsp::CompletionItemKind::SNIPPET),
6736 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
6737 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
6738 kind: lsp::MarkupKind::Markdown,
6739 value: "```rust\nSome(2)\n```".to_string(),
6740 })),
6741 deprecated: Some(false),
6742 sort_text: Some("Some".to_string()),
6743 filter_text: Some("Some".to_string()),
6744 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
6745 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6746 range: lsp::Range {
6747 start: lsp::Position {
6748 line: 0,
6749 character: 22,
6750 },
6751 end: lsp::Position {
6752 line: 0,
6753 character: 22,
6754 },
6755 },
6756 new_text: "Some(2)".to_string(),
6757 })),
6758 additional_text_edits: Some(vec![lsp::TextEdit {
6759 range: lsp::Range {
6760 start: lsp::Position {
6761 line: 0,
6762 character: 20,
6763 },
6764 end: lsp::Position {
6765 line: 0,
6766 character: 22,
6767 },
6768 },
6769 new_text: "".to_string(),
6770 }]),
6771 ..Default::default()
6772 };
6773
6774 let closure_completion_item = completion_item.clone();
6775 let counter = Arc::new(AtomicUsize::new(0));
6776 let counter_clone = counter.clone();
6777 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
6778 let task_completion_item = closure_completion_item.clone();
6779 counter_clone.fetch_add(1, atomic::Ordering::Release);
6780 async move {
6781 Ok(Some(lsp::CompletionResponse::Array(vec![
6782 task_completion_item,
6783 ])))
6784 }
6785 });
6786
6787 cx.condition(|editor, _| editor.context_menu_visible())
6788 .await;
6789 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
6790 assert!(request.next().await.is_some());
6791 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
6792
6793 cx.simulate_keystroke("S");
6794 cx.simulate_keystroke("o");
6795 cx.simulate_keystroke("m");
6796 cx.condition(|editor, _| editor.context_menu_visible())
6797 .await;
6798 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
6799 assert!(request.next().await.is_some());
6800 assert!(request.next().await.is_some());
6801 assert!(request.next().await.is_some());
6802 request.close();
6803 assert!(request.next().await.is_none());
6804 assert_eq!(
6805 counter.load(atomic::Ordering::Acquire),
6806 4,
6807 "With the completions menu open, only one LSP request should happen per input"
6808 );
6809}
6810
6811#[gpui::test]
6812async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
6813 init_test(cx, |_| {});
6814 let mut cx = EditorTestContext::new(cx).await;
6815 let language = Arc::new(Language::new(
6816 LanguageConfig {
6817 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
6818 ..Default::default()
6819 },
6820 Some(tree_sitter_rust::language()),
6821 ));
6822 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
6823
6824 // If multiple selections intersect a line, the line is only toggled once.
6825 cx.set_state(indoc! {"
6826 fn a() {
6827 «//b();
6828 ˇ»// «c();
6829 //ˇ» d();
6830 }
6831 "});
6832
6833 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6834
6835 cx.assert_editor_state(indoc! {"
6836 fn a() {
6837 «b();
6838 c();
6839 ˇ» d();
6840 }
6841 "});
6842
6843 // The comment prefix is inserted at the same column for every line in a
6844 // selection.
6845 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6846
6847 cx.assert_editor_state(indoc! {"
6848 fn a() {
6849 // «b();
6850 // c();
6851 ˇ»// d();
6852 }
6853 "});
6854
6855 // If a selection ends at the beginning of a line, that line is not toggled.
6856 cx.set_selections_state(indoc! {"
6857 fn a() {
6858 // b();
6859 «// c();
6860 ˇ» // d();
6861 }
6862 "});
6863
6864 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6865
6866 cx.assert_editor_state(indoc! {"
6867 fn a() {
6868 // b();
6869 «c();
6870 ˇ» // d();
6871 }
6872 "});
6873
6874 // If a selection span a single line and is empty, the line is toggled.
6875 cx.set_state(indoc! {"
6876 fn a() {
6877 a();
6878 b();
6879 ˇ
6880 }
6881 "});
6882
6883 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6884
6885 cx.assert_editor_state(indoc! {"
6886 fn a() {
6887 a();
6888 b();
6889 //•ˇ
6890 }
6891 "});
6892
6893 // If a selection span multiple lines, empty lines are not toggled.
6894 cx.set_state(indoc! {"
6895 fn a() {
6896 «a();
6897
6898 c();ˇ»
6899 }
6900 "});
6901
6902 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6903
6904 cx.assert_editor_state(indoc! {"
6905 fn a() {
6906 // «a();
6907
6908 // c();ˇ»
6909 }
6910 "});
6911
6912 // If a selection includes multiple comment prefixes, all lines are uncommented.
6913 cx.set_state(indoc! {"
6914 fn a() {
6915 «// a();
6916 /// b();
6917 //! c();ˇ»
6918 }
6919 "});
6920
6921 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6922
6923 cx.assert_editor_state(indoc! {"
6924 fn a() {
6925 «a();
6926 b();
6927 c();ˇ»
6928 }
6929 "});
6930}
6931
6932#[gpui::test]
6933async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
6934 init_test(cx, |_| {});
6935
6936 let language = Arc::new(Language::new(
6937 LanguageConfig {
6938 line_comments: vec!["// ".into()],
6939 ..Default::default()
6940 },
6941 Some(tree_sitter_rust::language()),
6942 ));
6943
6944 let mut cx = EditorTestContext::new(cx).await;
6945
6946 cx.language_registry().add(language.clone());
6947 cx.update_buffer(|buffer, cx| {
6948 buffer.set_language(Some(language), cx);
6949 });
6950
6951 let toggle_comments = &ToggleComments {
6952 advance_downwards: true,
6953 };
6954
6955 // Single cursor on one line -> advance
6956 // Cursor moves horizontally 3 characters as well on non-blank line
6957 cx.set_state(indoc!(
6958 "fn a() {
6959 ˇdog();
6960 cat();
6961 }"
6962 ));
6963 cx.update_editor(|editor, cx| {
6964 editor.toggle_comments(toggle_comments, cx);
6965 });
6966 cx.assert_editor_state(indoc!(
6967 "fn a() {
6968 // dog();
6969 catˇ();
6970 }"
6971 ));
6972
6973 // Single selection on one line -> don't advance
6974 cx.set_state(indoc!(
6975 "fn a() {
6976 «dog()ˇ»;
6977 cat();
6978 }"
6979 ));
6980 cx.update_editor(|editor, cx| {
6981 editor.toggle_comments(toggle_comments, cx);
6982 });
6983 cx.assert_editor_state(indoc!(
6984 "fn a() {
6985 // «dog()ˇ»;
6986 cat();
6987 }"
6988 ));
6989
6990 // Multiple cursors on one line -> advance
6991 cx.set_state(indoc!(
6992 "fn a() {
6993 ˇdˇog();
6994 cat();
6995 }"
6996 ));
6997 cx.update_editor(|editor, cx| {
6998 editor.toggle_comments(toggle_comments, cx);
6999 });
7000 cx.assert_editor_state(indoc!(
7001 "fn a() {
7002 // dog();
7003 catˇ(ˇ);
7004 }"
7005 ));
7006
7007 // Multiple cursors on one line, with selection -> don't advance
7008 cx.set_state(indoc!(
7009 "fn a() {
7010 ˇdˇog«()ˇ»;
7011 cat();
7012 }"
7013 ));
7014 cx.update_editor(|editor, cx| {
7015 editor.toggle_comments(toggle_comments, cx);
7016 });
7017 cx.assert_editor_state(indoc!(
7018 "fn a() {
7019 // ˇdˇog«()ˇ»;
7020 cat();
7021 }"
7022 ));
7023
7024 // Single cursor on one line -> advance
7025 // Cursor moves to column 0 on blank line
7026 cx.set_state(indoc!(
7027 "fn a() {
7028 ˇdog();
7029
7030 cat();
7031 }"
7032 ));
7033 cx.update_editor(|editor, cx| {
7034 editor.toggle_comments(toggle_comments, cx);
7035 });
7036 cx.assert_editor_state(indoc!(
7037 "fn a() {
7038 // dog();
7039 ˇ
7040 cat();
7041 }"
7042 ));
7043
7044 // Single cursor on one line -> advance
7045 // Cursor starts and ends at column 0
7046 cx.set_state(indoc!(
7047 "fn a() {
7048 ˇ dog();
7049 cat();
7050 }"
7051 ));
7052 cx.update_editor(|editor, cx| {
7053 editor.toggle_comments(toggle_comments, cx);
7054 });
7055 cx.assert_editor_state(indoc!(
7056 "fn a() {
7057 // dog();
7058 ˇ cat();
7059 }"
7060 ));
7061}
7062
7063#[gpui::test]
7064async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
7065 init_test(cx, |_| {});
7066
7067 let mut cx = EditorTestContext::new(cx).await;
7068
7069 let html_language = Arc::new(
7070 Language::new(
7071 LanguageConfig {
7072 name: "HTML".into(),
7073 block_comment: Some(("<!-- ".into(), " -->".into())),
7074 ..Default::default()
7075 },
7076 Some(tree_sitter_html::language()),
7077 )
7078 .with_injection_query(
7079 r#"
7080 (script_element
7081 (raw_text) @content
7082 (#set! "language" "javascript"))
7083 "#,
7084 )
7085 .unwrap(),
7086 );
7087
7088 let javascript_language = Arc::new(Language::new(
7089 LanguageConfig {
7090 name: "JavaScript".into(),
7091 line_comments: vec!["// ".into()],
7092 ..Default::default()
7093 },
7094 Some(tree_sitter_typescript::language_tsx()),
7095 ));
7096
7097 cx.language_registry().add(html_language.clone());
7098 cx.language_registry().add(javascript_language.clone());
7099 cx.update_buffer(|buffer, cx| {
7100 buffer.set_language(Some(html_language), cx);
7101 });
7102
7103 // Toggle comments for empty selections
7104 cx.set_state(
7105 &r#"
7106 <p>A</p>ˇ
7107 <p>B</p>ˇ
7108 <p>C</p>ˇ
7109 "#
7110 .unindent(),
7111 );
7112 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7113 cx.assert_editor_state(
7114 &r#"
7115 <!-- <p>A</p>ˇ -->
7116 <!-- <p>B</p>ˇ -->
7117 <!-- <p>C</p>ˇ -->
7118 "#
7119 .unindent(),
7120 );
7121 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7122 cx.assert_editor_state(
7123 &r#"
7124 <p>A</p>ˇ
7125 <p>B</p>ˇ
7126 <p>C</p>ˇ
7127 "#
7128 .unindent(),
7129 );
7130
7131 // Toggle comments for mixture of empty and non-empty selections, where
7132 // multiple selections occupy a given line.
7133 cx.set_state(
7134 &r#"
7135 <p>A«</p>
7136 <p>ˇ»B</p>ˇ
7137 <p>C«</p>
7138 <p>ˇ»D</p>ˇ
7139 "#
7140 .unindent(),
7141 );
7142
7143 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7144 cx.assert_editor_state(
7145 &r#"
7146 <!-- <p>A«</p>
7147 <p>ˇ»B</p>ˇ -->
7148 <!-- <p>C«</p>
7149 <p>ˇ»D</p>ˇ -->
7150 "#
7151 .unindent(),
7152 );
7153 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7154 cx.assert_editor_state(
7155 &r#"
7156 <p>A«</p>
7157 <p>ˇ»B</p>ˇ
7158 <p>C«</p>
7159 <p>ˇ»D</p>ˇ
7160 "#
7161 .unindent(),
7162 );
7163
7164 // Toggle comments when different languages are active for different
7165 // selections.
7166 cx.set_state(
7167 &r#"
7168 ˇ<script>
7169 ˇvar x = new Y();
7170 ˇ</script>
7171 "#
7172 .unindent(),
7173 );
7174 cx.executor().run_until_parked();
7175 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7176 cx.assert_editor_state(
7177 &r#"
7178 <!-- ˇ<script> -->
7179 // ˇvar x = new Y();
7180 <!-- ˇ</script> -->
7181 "#
7182 .unindent(),
7183 );
7184}
7185
7186#[gpui::test]
7187fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
7188 init_test(cx, |_| {});
7189
7190 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7191 let multibuffer = cx.new_model(|cx| {
7192 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7193 multibuffer.push_excerpts(
7194 buffer.clone(),
7195 [
7196 ExcerptRange {
7197 context: Point::new(0, 0)..Point::new(0, 4),
7198 primary: None,
7199 },
7200 ExcerptRange {
7201 context: Point::new(1, 0)..Point::new(1, 4),
7202 primary: None,
7203 },
7204 ],
7205 cx,
7206 );
7207 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
7208 multibuffer
7209 });
7210
7211 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7212 _ = view.update(cx, |view, cx| {
7213 assert_eq!(view.text(cx), "aaaa\nbbbb");
7214 view.change_selections(None, cx, |s| {
7215 s.select_ranges([
7216 Point::new(0, 0)..Point::new(0, 0),
7217 Point::new(1, 0)..Point::new(1, 0),
7218 ])
7219 });
7220
7221 view.handle_input("X", cx);
7222 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
7223 assert_eq!(
7224 view.selections.ranges(cx),
7225 [
7226 Point::new(0, 1)..Point::new(0, 1),
7227 Point::new(1, 1)..Point::new(1, 1),
7228 ]
7229 );
7230
7231 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
7232 view.change_selections(None, cx, |s| {
7233 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
7234 });
7235 view.backspace(&Default::default(), cx);
7236 assert_eq!(view.text(cx), "Xa\nbbb");
7237 assert_eq!(
7238 view.selections.ranges(cx),
7239 [Point::new(1, 0)..Point::new(1, 0)]
7240 );
7241
7242 view.change_selections(None, cx, |s| {
7243 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
7244 });
7245 view.backspace(&Default::default(), cx);
7246 assert_eq!(view.text(cx), "X\nbb");
7247 assert_eq!(
7248 view.selections.ranges(cx),
7249 [Point::new(0, 1)..Point::new(0, 1)]
7250 );
7251 });
7252}
7253
7254#[gpui::test]
7255fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
7256 init_test(cx, |_| {});
7257
7258 let markers = vec![('[', ']').into(), ('(', ')').into()];
7259 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
7260 indoc! {"
7261 [aaaa
7262 (bbbb]
7263 cccc)",
7264 },
7265 markers.clone(),
7266 );
7267 let excerpt_ranges = markers.into_iter().map(|marker| {
7268 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
7269 ExcerptRange {
7270 context,
7271 primary: None,
7272 }
7273 });
7274 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
7275 let multibuffer = cx.new_model(|cx| {
7276 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7277 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
7278 multibuffer
7279 });
7280
7281 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7282 _ = view.update(cx, |view, cx| {
7283 let (expected_text, selection_ranges) = marked_text_ranges(
7284 indoc! {"
7285 aaaa
7286 bˇbbb
7287 bˇbbˇb
7288 cccc"
7289 },
7290 true,
7291 );
7292 assert_eq!(view.text(cx), expected_text);
7293 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
7294
7295 view.handle_input("X", cx);
7296
7297 let (expected_text, expected_selections) = marked_text_ranges(
7298 indoc! {"
7299 aaaa
7300 bXˇbbXb
7301 bXˇbbXˇb
7302 cccc"
7303 },
7304 false,
7305 );
7306 assert_eq!(view.text(cx), expected_text);
7307 assert_eq!(view.selections.ranges(cx), expected_selections);
7308
7309 view.newline(&Newline, cx);
7310 let (expected_text, expected_selections) = marked_text_ranges(
7311 indoc! {"
7312 aaaa
7313 bX
7314 ˇbbX
7315 b
7316 bX
7317 ˇbbX
7318 ˇb
7319 cccc"
7320 },
7321 false,
7322 );
7323 assert_eq!(view.text(cx), expected_text);
7324 assert_eq!(view.selections.ranges(cx), expected_selections);
7325 });
7326}
7327
7328#[gpui::test]
7329fn test_refresh_selections(cx: &mut TestAppContext) {
7330 init_test(cx, |_| {});
7331
7332 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7333 let mut excerpt1_id = None;
7334 let multibuffer = cx.new_model(|cx| {
7335 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7336 excerpt1_id = multibuffer
7337 .push_excerpts(
7338 buffer.clone(),
7339 [
7340 ExcerptRange {
7341 context: Point::new(0, 0)..Point::new(1, 4),
7342 primary: None,
7343 },
7344 ExcerptRange {
7345 context: Point::new(1, 0)..Point::new(2, 4),
7346 primary: None,
7347 },
7348 ],
7349 cx,
7350 )
7351 .into_iter()
7352 .next();
7353 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7354 multibuffer
7355 });
7356
7357 let editor = cx.add_window(|cx| {
7358 let mut editor = build_editor(multibuffer.clone(), cx);
7359 let snapshot = editor.snapshot(cx);
7360 editor.change_selections(None, cx, |s| {
7361 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
7362 });
7363 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
7364 assert_eq!(
7365 editor.selections.ranges(cx),
7366 [
7367 Point::new(1, 3)..Point::new(1, 3),
7368 Point::new(2, 1)..Point::new(2, 1),
7369 ]
7370 );
7371 editor
7372 });
7373
7374 // Refreshing selections is a no-op when excerpts haven't changed.
7375 _ = editor.update(cx, |editor, cx| {
7376 editor.change_selections(None, cx, |s| s.refresh());
7377 assert_eq!(
7378 editor.selections.ranges(cx),
7379 [
7380 Point::new(1, 3)..Point::new(1, 3),
7381 Point::new(2, 1)..Point::new(2, 1),
7382 ]
7383 );
7384 });
7385
7386 _ = multibuffer.update(cx, |multibuffer, cx| {
7387 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7388 });
7389 _ = editor.update(cx, |editor, cx| {
7390 // Removing an excerpt causes the first selection to become degenerate.
7391 assert_eq!(
7392 editor.selections.ranges(cx),
7393 [
7394 Point::new(0, 0)..Point::new(0, 0),
7395 Point::new(0, 1)..Point::new(0, 1)
7396 ]
7397 );
7398
7399 // Refreshing selections will relocate the first selection to the original buffer
7400 // location.
7401 editor.change_selections(None, cx, |s| s.refresh());
7402 assert_eq!(
7403 editor.selections.ranges(cx),
7404 [
7405 Point::new(0, 1)..Point::new(0, 1),
7406 Point::new(0, 3)..Point::new(0, 3)
7407 ]
7408 );
7409 assert!(editor.selections.pending_anchor().is_some());
7410 });
7411}
7412
7413#[gpui::test]
7414fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
7415 init_test(cx, |_| {});
7416
7417 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7418 let mut excerpt1_id = None;
7419 let multibuffer = cx.new_model(|cx| {
7420 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7421 excerpt1_id = multibuffer
7422 .push_excerpts(
7423 buffer.clone(),
7424 [
7425 ExcerptRange {
7426 context: Point::new(0, 0)..Point::new(1, 4),
7427 primary: None,
7428 },
7429 ExcerptRange {
7430 context: Point::new(1, 0)..Point::new(2, 4),
7431 primary: None,
7432 },
7433 ],
7434 cx,
7435 )
7436 .into_iter()
7437 .next();
7438 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7439 multibuffer
7440 });
7441
7442 let editor = cx.add_window(|cx| {
7443 let mut editor = build_editor(multibuffer.clone(), cx);
7444 let snapshot = editor.snapshot(cx);
7445 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
7446 assert_eq!(
7447 editor.selections.ranges(cx),
7448 [Point::new(1, 3)..Point::new(1, 3)]
7449 );
7450 editor
7451 });
7452
7453 _ = multibuffer.update(cx, |multibuffer, cx| {
7454 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7455 });
7456 _ = editor.update(cx, |editor, cx| {
7457 assert_eq!(
7458 editor.selections.ranges(cx),
7459 [Point::new(0, 0)..Point::new(0, 0)]
7460 );
7461
7462 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
7463 editor.change_selections(None, cx, |s| s.refresh());
7464 assert_eq!(
7465 editor.selections.ranges(cx),
7466 [Point::new(0, 3)..Point::new(0, 3)]
7467 );
7468 assert!(editor.selections.pending_anchor().is_some());
7469 });
7470}
7471
7472#[gpui::test]
7473async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
7474 init_test(cx, |_| {});
7475
7476 let language = Arc::new(
7477 Language::new(
7478 LanguageConfig {
7479 brackets: BracketPairConfig {
7480 pairs: vec![
7481 BracketPair {
7482 start: "{".to_string(),
7483 end: "}".to_string(),
7484 close: true,
7485 newline: true,
7486 },
7487 BracketPair {
7488 start: "/* ".to_string(),
7489 end: " */".to_string(),
7490 close: true,
7491 newline: true,
7492 },
7493 ],
7494 ..Default::default()
7495 },
7496 ..Default::default()
7497 },
7498 Some(tree_sitter_rust::language()),
7499 )
7500 .with_indents_query("")
7501 .unwrap(),
7502 );
7503
7504 let text = concat!(
7505 "{ }\n", //
7506 " x\n", //
7507 " /* */\n", //
7508 "x\n", //
7509 "{{} }\n", //
7510 );
7511
7512 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
7513 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7514 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7515 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
7516 .await;
7517
7518 _ = view.update(cx, |view, cx| {
7519 view.change_selections(None, cx, |s| {
7520 s.select_display_ranges([
7521 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
7522 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
7523 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
7524 ])
7525 });
7526 view.newline(&Newline, cx);
7527
7528 assert_eq!(
7529 view.buffer().read(cx).read(cx).text(),
7530 concat!(
7531 "{ \n", // Suppress rustfmt
7532 "\n", //
7533 "}\n", //
7534 " x\n", //
7535 " /* \n", //
7536 " \n", //
7537 " */\n", //
7538 "x\n", //
7539 "{{} \n", //
7540 "}\n", //
7541 )
7542 );
7543 });
7544}
7545
7546#[gpui::test]
7547fn test_highlighted_ranges(cx: &mut TestAppContext) {
7548 init_test(cx, |_| {});
7549
7550 let editor = cx.add_window(|cx| {
7551 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
7552 build_editor(buffer.clone(), cx)
7553 });
7554
7555 _ = editor.update(cx, |editor, cx| {
7556 struct Type1;
7557 struct Type2;
7558
7559 let buffer = editor.buffer.read(cx).snapshot(cx);
7560
7561 let anchor_range =
7562 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
7563
7564 editor.highlight_background::<Type1>(
7565 &[
7566 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
7567 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
7568 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
7569 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
7570 ],
7571 |_| Hsla::red(),
7572 cx,
7573 );
7574 editor.highlight_background::<Type2>(
7575 &[
7576 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
7577 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
7578 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
7579 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
7580 ],
7581 |_| Hsla::green(),
7582 cx,
7583 );
7584
7585 let snapshot = editor.snapshot(cx);
7586 let mut highlighted_ranges = editor.background_highlights_in_range(
7587 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7588 &snapshot,
7589 cx.theme().colors(),
7590 );
7591 // Enforce a consistent ordering based on color without relying on the ordering of the
7592 // highlight's `TypeId` which is non-executor.
7593 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7594 assert_eq!(
7595 highlighted_ranges,
7596 &[
7597 (
7598 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
7599 Hsla::red(),
7600 ),
7601 (
7602 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7603 Hsla::red(),
7604 ),
7605 (
7606 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
7607 Hsla::green(),
7608 ),
7609 (
7610 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
7611 Hsla::green(),
7612 ),
7613 ]
7614 );
7615 assert_eq!(
7616 editor.background_highlights_in_range(
7617 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7618 &snapshot,
7619 cx.theme().colors(),
7620 ),
7621 &[(
7622 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7623 Hsla::red(),
7624 )]
7625 );
7626 });
7627}
7628
7629#[gpui::test]
7630async fn test_following(cx: &mut gpui::TestAppContext) {
7631 init_test(cx, |_| {});
7632
7633 let fs = FakeFs::new(cx.executor());
7634 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7635
7636 let buffer = project.update(cx, |project, cx| {
7637 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
7638 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7639 });
7640 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7641 let follower = cx.update(|cx| {
7642 cx.open_window(
7643 WindowOptions {
7644 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
7645 gpui::Point::new(0.into(), 0.into()),
7646 gpui::Point::new(10.into(), 80.into()),
7647 ))),
7648 ..Default::default()
7649 },
7650 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7651 )
7652 });
7653
7654 let is_still_following = Rc::new(RefCell::new(true));
7655 let follower_edit_event_count = Rc::new(RefCell::new(0));
7656 let pending_update = Rc::new(RefCell::new(None));
7657 _ = follower.update(cx, {
7658 let update = pending_update.clone();
7659 let is_still_following = is_still_following.clone();
7660 let follower_edit_event_count = follower_edit_event_count.clone();
7661 |_, cx| {
7662 cx.subscribe(
7663 &leader.root_view(cx).unwrap(),
7664 move |_, leader, event, cx| {
7665 leader
7666 .read(cx)
7667 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7668 },
7669 )
7670 .detach();
7671
7672 cx.subscribe(
7673 &follower.root_view(cx).unwrap(),
7674 move |_, _, event: &EditorEvent, _cx| {
7675 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7676 *is_still_following.borrow_mut() = false;
7677 }
7678
7679 if let EditorEvent::BufferEdited = event {
7680 *follower_edit_event_count.borrow_mut() += 1;
7681 }
7682 },
7683 )
7684 .detach();
7685 }
7686 });
7687
7688 // Update the selections only
7689 _ = leader.update(cx, |leader, cx| {
7690 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7691 });
7692 follower
7693 .update(cx, |follower, cx| {
7694 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7695 })
7696 .unwrap()
7697 .await
7698 .unwrap();
7699 _ = follower.update(cx, |follower, cx| {
7700 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
7701 });
7702 assert_eq!(*is_still_following.borrow(), true);
7703 assert_eq!(*follower_edit_event_count.borrow(), 0);
7704
7705 // Update the scroll position only
7706 _ = leader.update(cx, |leader, cx| {
7707 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7708 });
7709 follower
7710 .update(cx, |follower, cx| {
7711 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7712 })
7713 .unwrap()
7714 .await
7715 .unwrap();
7716 assert_eq!(
7717 follower
7718 .update(cx, |follower, cx| follower.scroll_position(cx))
7719 .unwrap(),
7720 gpui::Point::new(1.5, 3.5)
7721 );
7722 assert_eq!(*is_still_following.borrow(), true);
7723 assert_eq!(*follower_edit_event_count.borrow(), 0);
7724
7725 // Update the selections and scroll position. The follower's scroll position is updated
7726 // via autoscroll, not via the leader's exact scroll position.
7727 _ = leader.update(cx, |leader, cx| {
7728 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
7729 leader.request_autoscroll(Autoscroll::newest(), cx);
7730 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7731 });
7732 follower
7733 .update(cx, |follower, cx| {
7734 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7735 })
7736 .unwrap()
7737 .await
7738 .unwrap();
7739 _ = follower.update(cx, |follower, cx| {
7740 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
7741 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
7742 });
7743 assert_eq!(*is_still_following.borrow(), true);
7744
7745 // Creating a pending selection that precedes another selection
7746 _ = leader.update(cx, |leader, cx| {
7747 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7748 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
7749 });
7750 follower
7751 .update(cx, |follower, cx| {
7752 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7753 })
7754 .unwrap()
7755 .await
7756 .unwrap();
7757 _ = follower.update(cx, |follower, cx| {
7758 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
7759 });
7760 assert_eq!(*is_still_following.borrow(), true);
7761
7762 // Extend the pending selection so that it surrounds another selection
7763 _ = leader.update(cx, |leader, cx| {
7764 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
7765 });
7766 follower
7767 .update(cx, |follower, cx| {
7768 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7769 })
7770 .unwrap()
7771 .await
7772 .unwrap();
7773 _ = follower.update(cx, |follower, cx| {
7774 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7775 });
7776
7777 // Scrolling locally breaks the follow
7778 _ = follower.update(cx, |follower, cx| {
7779 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7780 follower.set_scroll_anchor(
7781 ScrollAnchor {
7782 anchor: top_anchor,
7783 offset: gpui::Point::new(0.0, 0.5),
7784 },
7785 cx,
7786 );
7787 });
7788 assert_eq!(*is_still_following.borrow(), false);
7789}
7790
7791#[gpui::test]
7792async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7793 init_test(cx, |_| {});
7794
7795 let fs = FakeFs::new(cx.executor());
7796 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7797 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7798 let pane = workspace
7799 .update(cx, |workspace, _| workspace.active_pane().clone())
7800 .unwrap();
7801
7802 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7803
7804 let leader = pane.update(cx, |_, cx| {
7805 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7806 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7807 });
7808
7809 // Start following the editor when it has no excerpts.
7810 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7811 let follower_1 = cx
7812 .update_window(*workspace.deref(), |_, cx| {
7813 Editor::from_state_proto(
7814 pane.clone(),
7815 workspace.root_view(cx).unwrap(),
7816 ViewId {
7817 creator: Default::default(),
7818 id: 0,
7819 },
7820 &mut state_message,
7821 cx,
7822 )
7823 })
7824 .unwrap()
7825 .unwrap()
7826 .await
7827 .unwrap();
7828
7829 let update_message = Rc::new(RefCell::new(None));
7830 follower_1.update(cx, {
7831 let update = update_message.clone();
7832 |_, cx| {
7833 cx.subscribe(&leader, move |_, leader, event, cx| {
7834 leader
7835 .read(cx)
7836 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7837 })
7838 .detach();
7839 }
7840 });
7841
7842 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7843 (
7844 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
7845 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
7846 )
7847 });
7848
7849 // Insert some excerpts.
7850 _ = leader.update(cx, |leader, cx| {
7851 leader.buffer.update(cx, |multibuffer, cx| {
7852 let excerpt_ids = multibuffer.push_excerpts(
7853 buffer_1.clone(),
7854 [
7855 ExcerptRange {
7856 context: 1..6,
7857 primary: None,
7858 },
7859 ExcerptRange {
7860 context: 12..15,
7861 primary: None,
7862 },
7863 ExcerptRange {
7864 context: 0..3,
7865 primary: None,
7866 },
7867 ],
7868 cx,
7869 );
7870 multibuffer.insert_excerpts_after(
7871 excerpt_ids[0],
7872 buffer_2.clone(),
7873 [
7874 ExcerptRange {
7875 context: 8..12,
7876 primary: None,
7877 },
7878 ExcerptRange {
7879 context: 0..6,
7880 primary: None,
7881 },
7882 ],
7883 cx,
7884 );
7885 });
7886 });
7887
7888 // Apply the update of adding the excerpts.
7889 follower_1
7890 .update(cx, |follower, cx| {
7891 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7892 })
7893 .await
7894 .unwrap();
7895 assert_eq!(
7896 follower_1.update(cx, |editor, cx| editor.text(cx)),
7897 leader.update(cx, |editor, cx| editor.text(cx))
7898 );
7899 update_message.borrow_mut().take();
7900
7901 // Start following separately after it already has excerpts.
7902 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7903 let follower_2 = cx
7904 .update_window(*workspace.deref(), |_, cx| {
7905 Editor::from_state_proto(
7906 pane.clone(),
7907 workspace.root_view(cx).unwrap().clone(),
7908 ViewId {
7909 creator: Default::default(),
7910 id: 0,
7911 },
7912 &mut state_message,
7913 cx,
7914 )
7915 })
7916 .unwrap()
7917 .unwrap()
7918 .await
7919 .unwrap();
7920 assert_eq!(
7921 follower_2.update(cx, |editor, cx| editor.text(cx)),
7922 leader.update(cx, |editor, cx| editor.text(cx))
7923 );
7924
7925 // Remove some excerpts.
7926 _ = leader.update(cx, |leader, cx| {
7927 leader.buffer.update(cx, |multibuffer, cx| {
7928 let excerpt_ids = multibuffer.excerpt_ids();
7929 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7930 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7931 });
7932 });
7933
7934 // Apply the update of removing the excerpts.
7935 follower_1
7936 .update(cx, |follower, cx| {
7937 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7938 })
7939 .await
7940 .unwrap();
7941 follower_2
7942 .update(cx, |follower, cx| {
7943 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7944 })
7945 .await
7946 .unwrap();
7947 update_message.borrow_mut().take();
7948 assert_eq!(
7949 follower_1.update(cx, |editor, cx| editor.text(cx)),
7950 leader.update(cx, |editor, cx| editor.text(cx))
7951 );
7952}
7953
7954#[gpui::test]
7955async fn go_to_prev_overlapping_diagnostic(
7956 executor: BackgroundExecutor,
7957 cx: &mut gpui::TestAppContext,
7958) {
7959 init_test(cx, |_| {});
7960
7961 let mut cx = EditorTestContext::new(cx).await;
7962 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7963
7964 cx.set_state(indoc! {"
7965 ˇfn func(abc def: i32) -> u32 {
7966 }
7967 "});
7968
7969 _ = cx.update(|cx| {
7970 _ = project.update(cx, |project, cx| {
7971 project
7972 .update_diagnostics(
7973 LanguageServerId(0),
7974 lsp::PublishDiagnosticsParams {
7975 uri: lsp::Url::from_file_path("/root/file").unwrap(),
7976 version: None,
7977 diagnostics: vec![
7978 lsp::Diagnostic {
7979 range: lsp::Range::new(
7980 lsp::Position::new(0, 11),
7981 lsp::Position::new(0, 12),
7982 ),
7983 severity: Some(lsp::DiagnosticSeverity::ERROR),
7984 ..Default::default()
7985 },
7986 lsp::Diagnostic {
7987 range: lsp::Range::new(
7988 lsp::Position::new(0, 12),
7989 lsp::Position::new(0, 15),
7990 ),
7991 severity: Some(lsp::DiagnosticSeverity::ERROR),
7992 ..Default::default()
7993 },
7994 lsp::Diagnostic {
7995 range: lsp::Range::new(
7996 lsp::Position::new(0, 25),
7997 lsp::Position::new(0, 28),
7998 ),
7999 severity: Some(lsp::DiagnosticSeverity::ERROR),
8000 ..Default::default()
8001 },
8002 ],
8003 },
8004 &[],
8005 cx,
8006 )
8007 .unwrap()
8008 });
8009 });
8010
8011 executor.run_until_parked();
8012
8013 cx.update_editor(|editor, cx| {
8014 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8015 });
8016
8017 cx.assert_editor_state(indoc! {"
8018 fn func(abc def: i32) -> ˇu32 {
8019 }
8020 "});
8021
8022 cx.update_editor(|editor, cx| {
8023 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8024 });
8025
8026 cx.assert_editor_state(indoc! {"
8027 fn func(abc ˇdef: i32) -> u32 {
8028 }
8029 "});
8030
8031 cx.update_editor(|editor, cx| {
8032 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8033 });
8034
8035 cx.assert_editor_state(indoc! {"
8036 fn func(abcˇ def: i32) -> u32 {
8037 }
8038 "});
8039
8040 cx.update_editor(|editor, cx| {
8041 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8042 });
8043
8044 cx.assert_editor_state(indoc! {"
8045 fn func(abc def: i32) -> ˇu32 {
8046 }
8047 "});
8048}
8049
8050#[gpui::test]
8051async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
8052 init_test(cx, |_| {});
8053
8054 let mut cx = EditorTestContext::new(cx).await;
8055
8056 let diff_base = r#"
8057 use some::mod;
8058
8059 const A: u32 = 42;
8060
8061 fn main() {
8062 println!("hello");
8063
8064 println!("world");
8065 }
8066 "#
8067 .unindent();
8068
8069 // Edits are modified, removed, modified, added
8070 cx.set_state(
8071 &r#"
8072 use some::modified;
8073
8074 ˇ
8075 fn main() {
8076 println!("hello there");
8077
8078 println!("around the");
8079 println!("world");
8080 }
8081 "#
8082 .unindent(),
8083 );
8084
8085 cx.set_diff_base(Some(&diff_base));
8086 executor.run_until_parked();
8087
8088 cx.update_editor(|editor, cx| {
8089 //Wrap around the bottom of the buffer
8090 for _ in 0..3 {
8091 editor.go_to_hunk(&GoToHunk, cx);
8092 }
8093 });
8094
8095 cx.assert_editor_state(
8096 &r#"
8097 ˇuse some::modified;
8098
8099
8100 fn main() {
8101 println!("hello there");
8102
8103 println!("around the");
8104 println!("world");
8105 }
8106 "#
8107 .unindent(),
8108 );
8109
8110 cx.update_editor(|editor, cx| {
8111 //Wrap around the top of the buffer
8112 for _ in 0..2 {
8113 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8114 }
8115 });
8116
8117 cx.assert_editor_state(
8118 &r#"
8119 use some::modified;
8120
8121
8122 fn main() {
8123 ˇ println!("hello there");
8124
8125 println!("around the");
8126 println!("world");
8127 }
8128 "#
8129 .unindent(),
8130 );
8131
8132 cx.update_editor(|editor, cx| {
8133 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8134 });
8135
8136 cx.assert_editor_state(
8137 &r#"
8138 use some::modified;
8139
8140 ˇ
8141 fn main() {
8142 println!("hello there");
8143
8144 println!("around the");
8145 println!("world");
8146 }
8147 "#
8148 .unindent(),
8149 );
8150
8151 cx.update_editor(|editor, cx| {
8152 for _ in 0..3 {
8153 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8154 }
8155 });
8156
8157 cx.assert_editor_state(
8158 &r#"
8159 use some::modified;
8160
8161
8162 fn main() {
8163 ˇ println!("hello there");
8164
8165 println!("around the");
8166 println!("world");
8167 }
8168 "#
8169 .unindent(),
8170 );
8171
8172 cx.update_editor(|editor, cx| {
8173 editor.fold(&Fold, cx);
8174
8175 //Make sure that the fold only gets one hunk
8176 for _ in 0..4 {
8177 editor.go_to_hunk(&GoToHunk, cx);
8178 }
8179 });
8180
8181 cx.assert_editor_state(
8182 &r#"
8183 ˇuse some::modified;
8184
8185
8186 fn main() {
8187 println!("hello there");
8188
8189 println!("around the");
8190 println!("world");
8191 }
8192 "#
8193 .unindent(),
8194 );
8195}
8196
8197#[test]
8198fn test_split_words() {
8199 fn split(text: &str) -> Vec<&str> {
8200 split_words(text).collect()
8201 }
8202
8203 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
8204 assert_eq!(split("hello_world"), &["hello_", "world"]);
8205 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
8206 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
8207 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
8208 assert_eq!(split("helloworld"), &["helloworld"]);
8209
8210 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
8211}
8212
8213#[gpui::test]
8214async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
8215 init_test(cx, |_| {});
8216
8217 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
8218 let mut assert = |before, after| {
8219 let _state_context = cx.set_state(before);
8220 cx.update_editor(|editor, cx| {
8221 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
8222 });
8223 cx.assert_editor_state(after);
8224 };
8225
8226 // Outside bracket jumps to outside of matching bracket
8227 assert("console.logˇ(var);", "console.log(var)ˇ;");
8228 assert("console.log(var)ˇ;", "console.logˇ(var);");
8229
8230 // Inside bracket jumps to inside of matching bracket
8231 assert("console.log(ˇvar);", "console.log(varˇ);");
8232 assert("console.log(varˇ);", "console.log(ˇvar);");
8233
8234 // When outside a bracket and inside, favor jumping to the inside bracket
8235 assert(
8236 "console.log('foo', [1, 2, 3]ˇ);",
8237 "console.log(ˇ'foo', [1, 2, 3]);",
8238 );
8239 assert(
8240 "console.log(ˇ'foo', [1, 2, 3]);",
8241 "console.log('foo', [1, 2, 3]ˇ);",
8242 );
8243
8244 // Bias forward if two options are equally likely
8245 assert(
8246 "let result = curried_fun()ˇ();",
8247 "let result = curried_fun()()ˇ;",
8248 );
8249
8250 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
8251 assert(
8252 indoc! {"
8253 function test() {
8254 console.log('test')ˇ
8255 }"},
8256 indoc! {"
8257 function test() {
8258 console.logˇ('test')
8259 }"},
8260 );
8261}
8262
8263#[gpui::test]
8264async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
8265 init_test(cx, |_| {});
8266
8267 let fs = FakeFs::new(cx.executor());
8268 fs.insert_tree(
8269 "/a",
8270 json!({
8271 "main.rs": "fn main() { let a = 5; }",
8272 "other.rs": "// Test file",
8273 }),
8274 )
8275 .await;
8276 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8277
8278 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8279 language_registry.add(Arc::new(Language::new(
8280 LanguageConfig {
8281 name: "Rust".into(),
8282 matcher: LanguageMatcher {
8283 path_suffixes: vec!["rs".to_string()],
8284 ..Default::default()
8285 },
8286 brackets: BracketPairConfig {
8287 pairs: vec![BracketPair {
8288 start: "{".to_string(),
8289 end: "}".to_string(),
8290 close: true,
8291 newline: true,
8292 }],
8293 disabled_scopes_by_bracket_ix: Vec::new(),
8294 },
8295 ..Default::default()
8296 },
8297 Some(tree_sitter_rust::language()),
8298 )));
8299 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8300 "Rust",
8301 FakeLspAdapter {
8302 capabilities: lsp::ServerCapabilities {
8303 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8304 first_trigger_character: "{".to_string(),
8305 more_trigger_character: None,
8306 }),
8307 ..Default::default()
8308 },
8309 ..Default::default()
8310 },
8311 );
8312
8313 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8314
8315 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8316
8317 let worktree_id = workspace
8318 .update(cx, |workspace, cx| {
8319 workspace.project().update(cx, |project, cx| {
8320 project.worktrees().next().unwrap().read(cx).id()
8321 })
8322 })
8323 .unwrap();
8324
8325 let buffer = project
8326 .update(cx, |project, cx| {
8327 project.open_local_buffer("/a/main.rs", cx)
8328 })
8329 .await
8330 .unwrap();
8331 cx.executor().run_until_parked();
8332 cx.executor().start_waiting();
8333 let fake_server = fake_servers.next().await.unwrap();
8334 let editor_handle = workspace
8335 .update(cx, |workspace, cx| {
8336 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8337 })
8338 .unwrap()
8339 .await
8340 .unwrap()
8341 .downcast::<Editor>()
8342 .unwrap();
8343
8344 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8345 assert_eq!(
8346 params.text_document_position.text_document.uri,
8347 lsp::Url::from_file_path("/a/main.rs").unwrap(),
8348 );
8349 assert_eq!(
8350 params.text_document_position.position,
8351 lsp::Position::new(0, 21),
8352 );
8353
8354 Ok(Some(vec![lsp::TextEdit {
8355 new_text: "]".to_string(),
8356 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8357 }]))
8358 });
8359
8360 editor_handle.update(cx, |editor, cx| {
8361 editor.focus(cx);
8362 editor.change_selections(None, cx, |s| {
8363 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8364 });
8365 editor.handle_input("{", cx);
8366 });
8367
8368 cx.executor().run_until_parked();
8369
8370 _ = buffer.update(cx, |buffer, _| {
8371 assert_eq!(
8372 buffer.text(),
8373 "fn main() { let a = {5}; }",
8374 "No extra braces from on type formatting should appear in the buffer"
8375 )
8376 });
8377}
8378
8379#[gpui::test]
8380async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8381 init_test(cx, |_| {});
8382
8383 let fs = FakeFs::new(cx.executor());
8384 fs.insert_tree(
8385 "/a",
8386 json!({
8387 "main.rs": "fn main() { let a = 5; }",
8388 "other.rs": "// Test file",
8389 }),
8390 )
8391 .await;
8392
8393 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8394
8395 let server_restarts = Arc::new(AtomicUsize::new(0));
8396 let closure_restarts = Arc::clone(&server_restarts);
8397 let language_server_name = "test language server";
8398 let language_name: Arc<str> = "Rust".into();
8399
8400 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8401 language_registry.add(Arc::new(Language::new(
8402 LanguageConfig {
8403 name: Arc::clone(&language_name),
8404 matcher: LanguageMatcher {
8405 path_suffixes: vec!["rs".to_string()],
8406 ..Default::default()
8407 },
8408 ..Default::default()
8409 },
8410 Some(tree_sitter_rust::language()),
8411 )));
8412 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8413 "Rust",
8414 FakeLspAdapter {
8415 name: language_server_name,
8416 initialization_options: Some(json!({
8417 "testOptionValue": true
8418 })),
8419 initializer: Some(Box::new(move |fake_server| {
8420 let task_restarts = Arc::clone(&closure_restarts);
8421 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8422 task_restarts.fetch_add(1, atomic::Ordering::Release);
8423 futures::future::ready(Ok(()))
8424 });
8425 })),
8426 ..Default::default()
8427 },
8428 );
8429
8430 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8431 let _buffer = project
8432 .update(cx, |project, cx| {
8433 project.open_local_buffer("/a/main.rs", cx)
8434 })
8435 .await
8436 .unwrap();
8437 let _fake_server = fake_servers.next().await.unwrap();
8438 update_test_language_settings(cx, |language_settings| {
8439 language_settings.languages.insert(
8440 Arc::clone(&language_name),
8441 LanguageSettingsContent {
8442 tab_size: NonZeroU32::new(8),
8443 ..Default::default()
8444 },
8445 );
8446 });
8447 cx.executor().run_until_parked();
8448 assert_eq!(
8449 server_restarts.load(atomic::Ordering::Acquire),
8450 0,
8451 "Should not restart LSP server on an unrelated change"
8452 );
8453
8454 update_test_project_settings(cx, |project_settings| {
8455 project_settings.lsp.insert(
8456 "Some other server name".into(),
8457 LspSettings {
8458 binary: None,
8459 settings: None,
8460 initialization_options: Some(json!({
8461 "some other init value": false
8462 })),
8463 },
8464 );
8465 });
8466 cx.executor().run_until_parked();
8467 assert_eq!(
8468 server_restarts.load(atomic::Ordering::Acquire),
8469 0,
8470 "Should not restart LSP server on an unrelated LSP settings change"
8471 );
8472
8473 update_test_project_settings(cx, |project_settings| {
8474 project_settings.lsp.insert(
8475 language_server_name.into(),
8476 LspSettings {
8477 binary: None,
8478 settings: None,
8479 initialization_options: Some(json!({
8480 "anotherInitValue": false
8481 })),
8482 },
8483 );
8484 });
8485 cx.executor().run_until_parked();
8486 assert_eq!(
8487 server_restarts.load(atomic::Ordering::Acquire),
8488 1,
8489 "Should restart LSP server on a related LSP settings change"
8490 );
8491
8492 update_test_project_settings(cx, |project_settings| {
8493 project_settings.lsp.insert(
8494 language_server_name.into(),
8495 LspSettings {
8496 binary: None,
8497 settings: None,
8498 initialization_options: Some(json!({
8499 "anotherInitValue": false
8500 })),
8501 },
8502 );
8503 });
8504 cx.executor().run_until_parked();
8505 assert_eq!(
8506 server_restarts.load(atomic::Ordering::Acquire),
8507 1,
8508 "Should not restart LSP server on a related LSP settings change that is the same"
8509 );
8510
8511 update_test_project_settings(cx, |project_settings| {
8512 project_settings.lsp.insert(
8513 language_server_name.into(),
8514 LspSettings {
8515 binary: None,
8516 settings: None,
8517 initialization_options: None,
8518 },
8519 );
8520 });
8521 cx.executor().run_until_parked();
8522 assert_eq!(
8523 server_restarts.load(atomic::Ordering::Acquire),
8524 2,
8525 "Should restart LSP server on another related LSP settings change"
8526 );
8527}
8528
8529#[gpui::test]
8530async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8531 init_test(cx, |_| {});
8532
8533 let mut cx = EditorLspTestContext::new_rust(
8534 lsp::ServerCapabilities {
8535 completion_provider: Some(lsp::CompletionOptions {
8536 trigger_characters: Some(vec![".".to_string()]),
8537 resolve_provider: Some(true),
8538 ..Default::default()
8539 }),
8540 ..Default::default()
8541 },
8542 cx,
8543 )
8544 .await;
8545
8546 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8547 cx.simulate_keystroke(".");
8548 let completion_item = lsp::CompletionItem {
8549 label: "some".into(),
8550 kind: Some(lsp::CompletionItemKind::SNIPPET),
8551 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8552 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8553 kind: lsp::MarkupKind::Markdown,
8554 value: "```rust\nSome(2)\n```".to_string(),
8555 })),
8556 deprecated: Some(false),
8557 sort_text: Some("fffffff2".to_string()),
8558 filter_text: Some("some".to_string()),
8559 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8560 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8561 range: lsp::Range {
8562 start: lsp::Position {
8563 line: 0,
8564 character: 22,
8565 },
8566 end: lsp::Position {
8567 line: 0,
8568 character: 22,
8569 },
8570 },
8571 new_text: "Some(2)".to_string(),
8572 })),
8573 additional_text_edits: Some(vec![lsp::TextEdit {
8574 range: lsp::Range {
8575 start: lsp::Position {
8576 line: 0,
8577 character: 20,
8578 },
8579 end: lsp::Position {
8580 line: 0,
8581 character: 22,
8582 },
8583 },
8584 new_text: "".to_string(),
8585 }]),
8586 ..Default::default()
8587 };
8588
8589 let closure_completion_item = completion_item.clone();
8590 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8591 let task_completion_item = closure_completion_item.clone();
8592 async move {
8593 Ok(Some(lsp::CompletionResponse::Array(vec![
8594 task_completion_item,
8595 ])))
8596 }
8597 });
8598
8599 request.next().await;
8600
8601 cx.condition(|editor, _| editor.context_menu_visible())
8602 .await;
8603 let apply_additional_edits = cx.update_editor(|editor, cx| {
8604 editor
8605 .confirm_completion(&ConfirmCompletion::default(), cx)
8606 .unwrap()
8607 });
8608 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8609
8610 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8611 let task_completion_item = completion_item.clone();
8612 async move { Ok(task_completion_item) }
8613 })
8614 .next()
8615 .await
8616 .unwrap();
8617 apply_additional_edits.await.unwrap();
8618 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8619}
8620
8621#[gpui::test]
8622async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8623 init_test(cx, |_| {});
8624
8625 let mut cx = EditorLspTestContext::new(
8626 Language::new(
8627 LanguageConfig {
8628 matcher: LanguageMatcher {
8629 path_suffixes: vec!["jsx".into()],
8630 ..Default::default()
8631 },
8632 overrides: [(
8633 "element".into(),
8634 LanguageConfigOverride {
8635 word_characters: Override::Set(['-'].into_iter().collect()),
8636 ..Default::default()
8637 },
8638 )]
8639 .into_iter()
8640 .collect(),
8641 ..Default::default()
8642 },
8643 Some(tree_sitter_typescript::language_tsx()),
8644 )
8645 .with_override_query("(jsx_self_closing_element) @element")
8646 .unwrap(),
8647 lsp::ServerCapabilities {
8648 completion_provider: Some(lsp::CompletionOptions {
8649 trigger_characters: Some(vec![":".to_string()]),
8650 ..Default::default()
8651 }),
8652 ..Default::default()
8653 },
8654 cx,
8655 )
8656 .await;
8657
8658 cx.lsp
8659 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8660 Ok(Some(lsp::CompletionResponse::Array(vec![
8661 lsp::CompletionItem {
8662 label: "bg-blue".into(),
8663 ..Default::default()
8664 },
8665 lsp::CompletionItem {
8666 label: "bg-red".into(),
8667 ..Default::default()
8668 },
8669 lsp::CompletionItem {
8670 label: "bg-yellow".into(),
8671 ..Default::default()
8672 },
8673 ])))
8674 });
8675
8676 cx.set_state(r#"<p class="bgˇ" />"#);
8677
8678 // Trigger completion when typing a dash, because the dash is an extra
8679 // word character in the 'element' scope, which contains the cursor.
8680 cx.simulate_keystroke("-");
8681 cx.executor().run_until_parked();
8682 cx.update_editor(|editor, _| {
8683 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8684 assert_eq!(
8685 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8686 &["bg-red", "bg-blue", "bg-yellow"]
8687 );
8688 } else {
8689 panic!("expected completion menu to be open");
8690 }
8691 });
8692
8693 cx.simulate_keystroke("l");
8694 cx.executor().run_until_parked();
8695 cx.update_editor(|editor, _| {
8696 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8697 assert_eq!(
8698 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8699 &["bg-blue", "bg-yellow"]
8700 );
8701 } else {
8702 panic!("expected completion menu to be open");
8703 }
8704 });
8705
8706 // When filtering completions, consider the character after the '-' to
8707 // be the start of a subword.
8708 cx.set_state(r#"<p class="yelˇ" />"#);
8709 cx.simulate_keystroke("l");
8710 cx.executor().run_until_parked();
8711 cx.update_editor(|editor, _| {
8712 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8713 assert_eq!(
8714 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8715 &["bg-yellow"]
8716 );
8717 } else {
8718 panic!("expected completion menu to be open");
8719 }
8720 });
8721}
8722
8723#[gpui::test]
8724async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8725 init_test(cx, |settings| {
8726 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8727 });
8728
8729 let fs = FakeFs::new(cx.executor());
8730 fs.insert_file("/file.ts", Default::default()).await;
8731
8732 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
8733 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8734
8735 language_registry.add(Arc::new(Language::new(
8736 LanguageConfig {
8737 name: "TypeScript".into(),
8738 matcher: LanguageMatcher {
8739 path_suffixes: vec!["ts".to_string()],
8740 ..Default::default()
8741 },
8742 ..Default::default()
8743 },
8744 Some(tree_sitter_rust::language()),
8745 )));
8746 update_test_language_settings(cx, |settings| {
8747 settings.defaults.prettier = Some(PrettierSettings {
8748 allowed: true,
8749 ..PrettierSettings::default()
8750 });
8751 });
8752
8753 let test_plugin = "test_plugin";
8754 let _ = language_registry.register_fake_lsp_adapter(
8755 "TypeScript",
8756 FakeLspAdapter {
8757 prettier_plugins: vec![test_plugin],
8758 ..Default::default()
8759 },
8760 );
8761
8762 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8763 let buffer = project
8764 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
8765 .await
8766 .unwrap();
8767
8768 let buffer_text = "one\ntwo\nthree\n";
8769 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8770 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8771 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8772
8773 editor
8774 .update(cx, |editor, cx| {
8775 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8776 })
8777 .unwrap()
8778 .await;
8779 assert_eq!(
8780 editor.update(cx, |editor, cx| editor.text(cx)),
8781 buffer_text.to_string() + prettier_format_suffix,
8782 "Test prettier formatting was not applied to the original buffer text",
8783 );
8784
8785 update_test_language_settings(cx, |settings| {
8786 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8787 });
8788 let format = editor.update(cx, |editor, cx| {
8789 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8790 });
8791 format.await.unwrap();
8792 assert_eq!(
8793 editor.update(cx, |editor, cx| editor.text(cx)),
8794 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8795 "Autoformatting (via test prettier) was not applied to the original buffer text",
8796 );
8797}
8798
8799#[gpui::test]
8800async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8801 init_test(cx, |_| {});
8802 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8803 let base_text = indoc! {r#"struct Row;
8804struct Row1;
8805struct Row2;
8806
8807struct Row4;
8808struct Row5;
8809struct Row6;
8810
8811struct Row8;
8812struct Row9;
8813struct Row10;"#};
8814
8815 // When addition hunks are not adjacent to carets, no hunk revert is performed
8816 assert_hunk_revert(
8817 indoc! {r#"struct Row;
8818 struct Row1;
8819 struct Row1.1;
8820 struct Row1.2;
8821 struct Row2;ˇ
8822
8823 struct Row4;
8824 struct Row5;
8825 struct Row6;
8826
8827 struct Row8;
8828 ˇstruct Row9;
8829 struct Row9.1;
8830 struct Row9.2;
8831 struct Row9.3;
8832 struct Row10;"#},
8833 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8834 indoc! {r#"struct Row;
8835 struct Row1;
8836 struct Row1.1;
8837 struct Row1.2;
8838 struct Row2;ˇ
8839
8840 struct Row4;
8841 struct Row5;
8842 struct Row6;
8843
8844 struct Row8;
8845 ˇstruct Row9;
8846 struct Row9.1;
8847 struct Row9.2;
8848 struct Row9.3;
8849 struct Row10;"#},
8850 base_text,
8851 &mut cx,
8852 );
8853 // Same for selections
8854 assert_hunk_revert(
8855 indoc! {r#"struct Row;
8856 struct Row1;
8857 struct Row2;
8858 struct Row2.1;
8859 struct Row2.2;
8860 «ˇ
8861 struct Row4;
8862 struct» Row5;
8863 «struct Row6;
8864 ˇ»
8865 struct Row9.1;
8866 struct Row9.2;
8867 struct Row9.3;
8868 struct Row8;
8869 struct Row9;
8870 struct Row10;"#},
8871 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8872 indoc! {r#"struct Row;
8873 struct Row1;
8874 struct Row2;
8875 struct Row2.1;
8876 struct Row2.2;
8877 «ˇ
8878 struct Row4;
8879 struct» Row5;
8880 «struct Row6;
8881 ˇ»
8882 struct Row9.1;
8883 struct Row9.2;
8884 struct Row9.3;
8885 struct Row8;
8886 struct Row9;
8887 struct Row10;"#},
8888 base_text,
8889 &mut cx,
8890 );
8891
8892 // When carets and selections intersect the addition hunks, those are reverted.
8893 // Adjacent carets got merged.
8894 assert_hunk_revert(
8895 indoc! {r#"struct Row;
8896 ˇ// something on the top
8897 struct Row1;
8898 struct Row2;
8899 struct Roˇw3.1;
8900 struct Row2.2;
8901 struct Row2.3;ˇ
8902
8903 struct Row4;
8904 struct ˇRow5.1;
8905 struct Row5.2;
8906 struct «Rowˇ»5.3;
8907 struct Row5;
8908 struct Row6;
8909 ˇ
8910 struct Row9.1;
8911 struct «Rowˇ»9.2;
8912 struct «ˇRow»9.3;
8913 struct Row8;
8914 struct Row9;
8915 «ˇ// something on bottom»
8916 struct Row10;"#},
8917 vec![
8918 DiffHunkStatus::Added,
8919 DiffHunkStatus::Added,
8920 DiffHunkStatus::Added,
8921 DiffHunkStatus::Added,
8922 DiffHunkStatus::Added,
8923 ],
8924 indoc! {r#"struct Row;
8925 ˇstruct Row1;
8926 struct Row2;
8927 ˇ
8928 struct Row4;
8929 ˇstruct Row5;
8930 struct Row6;
8931 ˇ
8932 ˇstruct Row8;
8933 struct Row9;
8934 ˇstruct Row10;"#},
8935 base_text,
8936 &mut cx,
8937 );
8938}
8939
8940#[gpui::test]
8941async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8942 init_test(cx, |_| {});
8943 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8944 let base_text = indoc! {r#"struct Row;
8945struct Row1;
8946struct Row2;
8947
8948struct Row4;
8949struct Row5;
8950struct Row6;
8951
8952struct Row8;
8953struct Row9;
8954struct Row10;"#};
8955
8956 // Modification hunks behave the same as the addition ones.
8957 assert_hunk_revert(
8958 indoc! {r#"struct Row;
8959 struct Row1;
8960 struct Row33;
8961 ˇ
8962 struct Row4;
8963 struct Row5;
8964 struct Row6;
8965 ˇ
8966 struct Row99;
8967 struct Row9;
8968 struct Row10;"#},
8969 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8970 indoc! {r#"struct Row;
8971 struct Row1;
8972 struct Row33;
8973 ˇ
8974 struct Row4;
8975 struct Row5;
8976 struct Row6;
8977 ˇ
8978 struct Row99;
8979 struct Row9;
8980 struct Row10;"#},
8981 base_text,
8982 &mut cx,
8983 );
8984 assert_hunk_revert(
8985 indoc! {r#"struct Row;
8986 struct Row1;
8987 struct Row33;
8988 «ˇ
8989 struct Row4;
8990 struct» Row5;
8991 «struct Row6;
8992 ˇ»
8993 struct Row99;
8994 struct Row9;
8995 struct Row10;"#},
8996 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8997 indoc! {r#"struct Row;
8998 struct Row1;
8999 struct Row33;
9000 «ˇ
9001 struct Row4;
9002 struct» Row5;
9003 «struct Row6;
9004 ˇ»
9005 struct Row99;
9006 struct Row9;
9007 struct Row10;"#},
9008 base_text,
9009 &mut cx,
9010 );
9011
9012 assert_hunk_revert(
9013 indoc! {r#"ˇstruct Row1.1;
9014 struct Row1;
9015 «ˇstr»uct Row22;
9016
9017 struct ˇRow44;
9018 struct Row5;
9019 struct «Rˇ»ow66;ˇ
9020
9021 «struˇ»ct Row88;
9022 struct Row9;
9023 struct Row1011;ˇ"#},
9024 vec![
9025 DiffHunkStatus::Modified,
9026 DiffHunkStatus::Modified,
9027 DiffHunkStatus::Modified,
9028 DiffHunkStatus::Modified,
9029 DiffHunkStatus::Modified,
9030 DiffHunkStatus::Modified,
9031 ],
9032 indoc! {r#"struct Row;
9033 ˇstruct Row1;
9034 struct Row2;
9035 ˇ
9036 struct Row4;
9037 ˇstruct Row5;
9038 struct Row6;
9039 ˇ
9040 struct Row8;
9041 ˇstruct Row9;
9042 struct Row10;ˇ"#},
9043 base_text,
9044 &mut cx,
9045 );
9046}
9047
9048#[gpui::test]
9049async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
9050 init_test(cx, |_| {});
9051 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9052 let base_text = indoc! {r#"struct Row;
9053struct Row1;
9054struct Row2;
9055
9056struct Row4;
9057struct Row5;
9058struct Row6;
9059
9060struct Row8;
9061struct Row9;
9062struct Row10;"#};
9063
9064 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
9065 assert_hunk_revert(
9066 indoc! {r#"struct Row;
9067 struct Row2;
9068
9069 ˇstruct Row4;
9070 struct Row5;
9071 struct Row6;
9072 ˇ
9073 struct Row8;
9074 struct Row10;"#},
9075 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9076 indoc! {r#"struct Row;
9077 struct Row2;
9078
9079 ˇstruct Row4;
9080 struct Row5;
9081 struct Row6;
9082 ˇ
9083 struct Row8;
9084 struct Row10;"#},
9085 base_text,
9086 &mut cx,
9087 );
9088 assert_hunk_revert(
9089 indoc! {r#"struct Row;
9090 struct Row2;
9091
9092 «ˇstruct Row4;
9093 struct» Row5;
9094 «struct Row6;
9095 ˇ»
9096 struct Row8;
9097 struct Row10;"#},
9098 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9099 indoc! {r#"struct Row;
9100 struct Row2;
9101
9102 «ˇstruct Row4;
9103 struct» Row5;
9104 «struct Row6;
9105 ˇ»
9106 struct Row8;
9107 struct Row10;"#},
9108 base_text,
9109 &mut cx,
9110 );
9111
9112 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
9113 assert_hunk_revert(
9114 indoc! {r#"struct Row;
9115 ˇstruct Row2;
9116
9117 struct Row4;
9118 struct Row5;
9119 struct Row6;
9120
9121 struct Row8;ˇ
9122 struct Row10;"#},
9123 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9124 indoc! {r#"struct Row;
9125 struct Row1;
9126 ˇstruct Row2;
9127
9128 struct Row4;
9129 struct Row5;
9130 struct Row6;
9131
9132 struct Row8;ˇ
9133 struct Row9;
9134 struct Row10;"#},
9135 base_text,
9136 &mut cx,
9137 );
9138 assert_hunk_revert(
9139 indoc! {r#"struct Row;
9140 struct Row2«ˇ;
9141 struct Row4;
9142 struct» Row5;
9143 «struct Row6;
9144
9145 struct Row8;ˇ»
9146 struct Row10;"#},
9147 vec![
9148 DiffHunkStatus::Removed,
9149 DiffHunkStatus::Removed,
9150 DiffHunkStatus::Removed,
9151 ],
9152 indoc! {r#"struct Row;
9153 struct Row1;
9154 struct Row2«ˇ;
9155
9156 struct Row4;
9157 struct» Row5;
9158 «struct Row6;
9159
9160 struct Row8;ˇ»
9161 struct Row9;
9162 struct Row10;"#},
9163 base_text,
9164 &mut cx,
9165 );
9166}
9167
9168#[gpui::test]
9169async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
9170 init_test(cx, |_| {});
9171
9172 let cols = 4;
9173 let rows = 10;
9174 let sample_text_1 = sample_text(rows, cols, 'a');
9175 assert_eq!(
9176 sample_text_1,
9177 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9178 );
9179 let sample_text_2 = sample_text(rows, cols, 'l');
9180 assert_eq!(
9181 sample_text_2,
9182 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9183 );
9184 let sample_text_3 = sample_text(rows, cols, 'v');
9185 assert_eq!(
9186 sample_text_3,
9187 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9188 );
9189
9190 fn diff_every_buffer_row(
9191 buffer: &Model<Buffer>,
9192 sample_text: String,
9193 cols: usize,
9194 cx: &mut gpui::TestAppContext,
9195 ) {
9196 // revert first character in each row, creating one large diff hunk per buffer
9197 let is_first_char = |offset: usize| offset % cols == 0;
9198 buffer.update(cx, |buffer, cx| {
9199 buffer.set_text(
9200 sample_text
9201 .chars()
9202 .enumerate()
9203 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
9204 .collect::<String>(),
9205 cx,
9206 );
9207 buffer.set_diff_base(Some(sample_text), cx);
9208 });
9209 cx.executor().run_until_parked();
9210 }
9211
9212 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9213 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9214
9215 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9216 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9217
9218 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9219 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9220
9221 let multibuffer = cx.new_model(|cx| {
9222 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9223 multibuffer.push_excerpts(
9224 buffer_1.clone(),
9225 [
9226 ExcerptRange {
9227 context: Point::new(0, 0)..Point::new(3, 0),
9228 primary: None,
9229 },
9230 ExcerptRange {
9231 context: Point::new(5, 0)..Point::new(7, 0),
9232 primary: None,
9233 },
9234 ExcerptRange {
9235 context: Point::new(9, 0)..Point::new(10, 4),
9236 primary: None,
9237 },
9238 ],
9239 cx,
9240 );
9241 multibuffer.push_excerpts(
9242 buffer_2.clone(),
9243 [
9244 ExcerptRange {
9245 context: Point::new(0, 0)..Point::new(3, 0),
9246 primary: None,
9247 },
9248 ExcerptRange {
9249 context: Point::new(5, 0)..Point::new(7, 0),
9250 primary: None,
9251 },
9252 ExcerptRange {
9253 context: Point::new(9, 0)..Point::new(10, 4),
9254 primary: None,
9255 },
9256 ],
9257 cx,
9258 );
9259 multibuffer.push_excerpts(
9260 buffer_3.clone(),
9261 [
9262 ExcerptRange {
9263 context: Point::new(0, 0)..Point::new(3, 0),
9264 primary: None,
9265 },
9266 ExcerptRange {
9267 context: Point::new(5, 0)..Point::new(7, 0),
9268 primary: None,
9269 },
9270 ExcerptRange {
9271 context: Point::new(9, 0)..Point::new(10, 4),
9272 primary: None,
9273 },
9274 ],
9275 cx,
9276 );
9277 multibuffer
9278 });
9279
9280 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9281 editor.update(cx, |editor, cx| {
9282 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");
9283 editor.select_all(&SelectAll, cx);
9284 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9285 });
9286 cx.executor().run_until_parked();
9287 // When all ranges are selected, all buffer hunks are reverted.
9288 editor.update(cx, |editor, cx| {
9289 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");
9290 });
9291 buffer_1.update(cx, |buffer, _| {
9292 assert_eq!(buffer.text(), sample_text_1);
9293 });
9294 buffer_2.update(cx, |buffer, _| {
9295 assert_eq!(buffer.text(), sample_text_2);
9296 });
9297 buffer_3.update(cx, |buffer, _| {
9298 assert_eq!(buffer.text(), sample_text_3);
9299 });
9300
9301 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9302 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9303 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9304 editor.update(cx, |editor, cx| {
9305 editor.change_selections(None, cx, |s| {
9306 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9307 });
9308 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9309 });
9310 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9311 // but not affect buffer_2 and its related excerpts.
9312 editor.update(cx, |editor, cx| {
9313 assert_eq!(
9314 editor.text(cx),
9315 "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"
9316 );
9317 });
9318 buffer_1.update(cx, |buffer, _| {
9319 assert_eq!(buffer.text(), sample_text_1);
9320 });
9321 buffer_2.update(cx, |buffer, _| {
9322 assert_eq!(
9323 buffer.text(),
9324 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9325 );
9326 });
9327 buffer_3.update(cx, |buffer, _| {
9328 assert_eq!(
9329 buffer.text(),
9330 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9331 );
9332 });
9333}
9334
9335#[gpui::test]
9336async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9337 init_test(cx, |_| {});
9338
9339 let cols = 4;
9340 let rows = 10;
9341 let sample_text_1 = sample_text(rows, cols, 'a');
9342 assert_eq!(
9343 sample_text_1,
9344 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9345 );
9346 let sample_text_2 = sample_text(rows, cols, 'l');
9347 assert_eq!(
9348 sample_text_2,
9349 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9350 );
9351 let sample_text_3 = sample_text(rows, cols, 'v');
9352 assert_eq!(
9353 sample_text_3,
9354 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9355 );
9356
9357 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9358 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9359 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9360
9361 let multi_buffer = cx.new_model(|cx| {
9362 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9363 multibuffer.push_excerpts(
9364 buffer_1.clone(),
9365 [
9366 ExcerptRange {
9367 context: Point::new(0, 0)..Point::new(3, 0),
9368 primary: None,
9369 },
9370 ExcerptRange {
9371 context: Point::new(5, 0)..Point::new(7, 0),
9372 primary: None,
9373 },
9374 ExcerptRange {
9375 context: Point::new(9, 0)..Point::new(10, 4),
9376 primary: None,
9377 },
9378 ],
9379 cx,
9380 );
9381 multibuffer.push_excerpts(
9382 buffer_2.clone(),
9383 [
9384 ExcerptRange {
9385 context: Point::new(0, 0)..Point::new(3, 0),
9386 primary: None,
9387 },
9388 ExcerptRange {
9389 context: Point::new(5, 0)..Point::new(7, 0),
9390 primary: None,
9391 },
9392 ExcerptRange {
9393 context: Point::new(9, 0)..Point::new(10, 4),
9394 primary: None,
9395 },
9396 ],
9397 cx,
9398 );
9399 multibuffer.push_excerpts(
9400 buffer_3.clone(),
9401 [
9402 ExcerptRange {
9403 context: Point::new(0, 0)..Point::new(3, 0),
9404 primary: None,
9405 },
9406 ExcerptRange {
9407 context: Point::new(5, 0)..Point::new(7, 0),
9408 primary: None,
9409 },
9410 ExcerptRange {
9411 context: Point::new(9, 0)..Point::new(10, 4),
9412 primary: None,
9413 },
9414 ],
9415 cx,
9416 );
9417 multibuffer
9418 });
9419
9420 let fs = FakeFs::new(cx.executor());
9421 fs.insert_tree(
9422 "/a",
9423 json!({
9424 "main.rs": sample_text_1,
9425 "other.rs": sample_text_2,
9426 "lib.rs": sample_text_3,
9427 }),
9428 )
9429 .await;
9430 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9431 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9432 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9433 let multi_buffer_editor =
9434 cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
9435 let multibuffer_item_id = workspace
9436 .update(cx, |workspace, cx| {
9437 assert!(
9438 workspace.active_item(cx).is_none(),
9439 "active item should be None before the first item is added"
9440 );
9441 workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
9442 let active_item = workspace
9443 .active_item(cx)
9444 .expect("should have an active item after adding the multi buffer");
9445 assert!(
9446 !active_item.is_singleton(cx),
9447 "A multi buffer was expected to active after adding"
9448 );
9449 active_item.item_id()
9450 })
9451 .unwrap();
9452 cx.executor().run_until_parked();
9453
9454 multi_buffer_editor.update(cx, |editor, cx| {
9455 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9456 editor.open_excerpts(&OpenExcerpts, cx);
9457 });
9458 cx.executor().run_until_parked();
9459 let first_item_id = workspace
9460 .update(cx, |workspace, cx| {
9461 let active_item = workspace
9462 .active_item(cx)
9463 .expect("should have an active item after navigating into the 1st buffer");
9464 let first_item_id = active_item.item_id();
9465 assert_ne!(
9466 first_item_id, multibuffer_item_id,
9467 "Should navigate into the 1st buffer and activate it"
9468 );
9469 assert!(
9470 active_item.is_singleton(cx),
9471 "New active item should be a singleton buffer"
9472 );
9473 assert_eq!(
9474 active_item
9475 .act_as::<Editor>(cx)
9476 .expect("should have navigated into an editor for the 1st buffer")
9477 .read(cx)
9478 .text(cx),
9479 sample_text_1
9480 );
9481
9482 workspace
9483 .go_back(workspace.active_pane().downgrade(), cx)
9484 .detach_and_log_err(cx);
9485
9486 first_item_id
9487 })
9488 .unwrap();
9489 cx.executor().run_until_parked();
9490 workspace
9491 .update(cx, |workspace, cx| {
9492 let active_item = workspace
9493 .active_item(cx)
9494 .expect("should have an active item after navigating back");
9495 assert_eq!(
9496 active_item.item_id(),
9497 multibuffer_item_id,
9498 "Should navigate back to the multi buffer"
9499 );
9500 assert!(!active_item.is_singleton(cx));
9501 })
9502 .unwrap();
9503
9504 multi_buffer_editor.update(cx, |editor, cx| {
9505 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9506 s.select_ranges(Some(39..40))
9507 });
9508 editor.open_excerpts(&OpenExcerpts, cx);
9509 });
9510 cx.executor().run_until_parked();
9511 let second_item_id = workspace
9512 .update(cx, |workspace, cx| {
9513 let active_item = workspace
9514 .active_item(cx)
9515 .expect("should have an active item after navigating into the 2nd buffer");
9516 let second_item_id = active_item.item_id();
9517 assert_ne!(
9518 second_item_id, multibuffer_item_id,
9519 "Should navigate away from the multibuffer"
9520 );
9521 assert_ne!(
9522 second_item_id, first_item_id,
9523 "Should navigate into the 2nd buffer and activate it"
9524 );
9525 assert!(
9526 active_item.is_singleton(cx),
9527 "New active item should be a singleton buffer"
9528 );
9529 assert_eq!(
9530 active_item
9531 .act_as::<Editor>(cx)
9532 .expect("should have navigated into an editor")
9533 .read(cx)
9534 .text(cx),
9535 sample_text_2
9536 );
9537
9538 workspace
9539 .go_back(workspace.active_pane().downgrade(), cx)
9540 .detach_and_log_err(cx);
9541
9542 second_item_id
9543 })
9544 .unwrap();
9545 cx.executor().run_until_parked();
9546 workspace
9547 .update(cx, |workspace, cx| {
9548 let active_item = workspace
9549 .active_item(cx)
9550 .expect("should have an active item after navigating back from the 2nd buffer");
9551 assert_eq!(
9552 active_item.item_id(),
9553 multibuffer_item_id,
9554 "Should navigate back from the 2nd buffer to the multi buffer"
9555 );
9556 assert!(!active_item.is_singleton(cx));
9557 })
9558 .unwrap();
9559
9560 multi_buffer_editor.update(cx, |editor, cx| {
9561 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9562 s.select_ranges(Some(60..70))
9563 });
9564 editor.open_excerpts(&OpenExcerpts, cx);
9565 });
9566 cx.executor().run_until_parked();
9567 workspace
9568 .update(cx, |workspace, cx| {
9569 let active_item = workspace
9570 .active_item(cx)
9571 .expect("should have an active item after navigating into the 3rd buffer");
9572 let third_item_id = active_item.item_id();
9573 assert_ne!(
9574 third_item_id, multibuffer_item_id,
9575 "Should navigate into the 3rd buffer and activate it"
9576 );
9577 assert_ne!(third_item_id, first_item_id);
9578 assert_ne!(third_item_id, second_item_id);
9579 assert!(
9580 active_item.is_singleton(cx),
9581 "New active item should be a singleton buffer"
9582 );
9583 assert_eq!(
9584 active_item
9585 .act_as::<Editor>(cx)
9586 .expect("should have navigated into an editor")
9587 .read(cx)
9588 .text(cx),
9589 sample_text_3
9590 );
9591
9592 workspace
9593 .go_back(workspace.active_pane().downgrade(), cx)
9594 .detach_and_log_err(cx);
9595 })
9596 .unwrap();
9597 cx.executor().run_until_parked();
9598 workspace
9599 .update(cx, |workspace, cx| {
9600 let active_item = workspace
9601 .active_item(cx)
9602 .expect("should have an active item after navigating back from the 3rd buffer");
9603 assert_eq!(
9604 active_item.item_id(),
9605 multibuffer_item_id,
9606 "Should navigate back from the 3rd buffer to the multi buffer"
9607 );
9608 assert!(!active_item.is_singleton(cx));
9609 })
9610 .unwrap();
9611}
9612
9613#[gpui::test]
9614async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9615 init_test(cx, |_| {});
9616
9617 let mut cx = EditorTestContext::new(cx).await;
9618
9619 let diff_base = r#"
9620 use some::mod;
9621
9622 const A: u32 = 42;
9623
9624 fn main() {
9625 println!("hello");
9626
9627 println!("world");
9628 }
9629 "#
9630 .unindent();
9631
9632 cx.set_state(
9633 &r#"
9634 use some::modified;
9635
9636 ˇ
9637 fn main() {
9638 println!("hello there");
9639
9640 println!("around the");
9641 println!("world");
9642 }
9643 "#
9644 .unindent(),
9645 );
9646
9647 cx.set_diff_base(Some(&diff_base));
9648 executor.run_until_parked();
9649 let unexpanded_hunks = vec![
9650 (
9651 "use some::mod;\n".to_string(),
9652 DiffHunkStatus::Modified,
9653 DisplayRow(0)..DisplayRow(1),
9654 ),
9655 (
9656 "const A: u32 = 42;\n".to_string(),
9657 DiffHunkStatus::Removed,
9658 DisplayRow(2)..DisplayRow(2),
9659 ),
9660 (
9661 " println!(\"hello\");\n".to_string(),
9662 DiffHunkStatus::Modified,
9663 DisplayRow(4)..DisplayRow(5),
9664 ),
9665 (
9666 "".to_string(),
9667 DiffHunkStatus::Added,
9668 DisplayRow(6)..DisplayRow(7),
9669 ),
9670 ];
9671 cx.update_editor(|editor, cx| {
9672 let snapshot = editor.snapshot(cx);
9673 let all_hunks = editor_hunks(editor, &snapshot, cx);
9674 assert_eq!(all_hunks, unexpanded_hunks);
9675 });
9676
9677 cx.update_editor(|editor, cx| {
9678 for _ in 0..4 {
9679 editor.go_to_hunk(&GoToHunk, cx);
9680 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
9681 }
9682 });
9683 executor.run_until_parked();
9684 cx.assert_editor_state(
9685 &r#"
9686 use some::modified;
9687
9688 ˇ
9689 fn main() {
9690 println!("hello there");
9691
9692 println!("around the");
9693 println!("world");
9694 }
9695 "#
9696 .unindent(),
9697 );
9698 cx.update_editor(|editor, cx| {
9699 let snapshot = editor.snapshot(cx);
9700 let all_hunks = editor_hunks(editor, &snapshot, cx);
9701 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
9702 assert_eq!(
9703 expanded_hunks_background_highlights(editor, cx),
9704 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
9705 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
9706 );
9707 assert_eq!(
9708 all_hunks,
9709 vec![
9710 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
9711 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
9712 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
9713 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
9714 ],
9715 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
9716 (from modified and removed hunks)"
9717 );
9718 assert_eq!(
9719 all_hunks, all_expanded_hunks,
9720 "Editor hunks should not change and all be expanded"
9721 );
9722 });
9723
9724 cx.update_editor(|editor, cx| {
9725 editor.cancel(&Cancel, cx);
9726
9727 let snapshot = editor.snapshot(cx);
9728 let all_hunks = editor_hunks(editor, &snapshot, cx);
9729 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
9730 assert_eq!(
9731 expanded_hunks_background_highlights(editor, cx),
9732 Vec::new(),
9733 "After cancelling in editor, no git highlights should be left"
9734 );
9735 assert_eq!(
9736 all_expanded_hunks,
9737 Vec::new(),
9738 "After cancelling in editor, no hunks should be expanded"
9739 );
9740 assert_eq!(
9741 all_hunks, unexpanded_hunks,
9742 "After cancelling in editor, regular hunks' coordinates should get back to normal"
9743 );
9744 });
9745}
9746
9747#[gpui::test]
9748async fn test_toggled_diff_base_change(
9749 executor: BackgroundExecutor,
9750 cx: &mut gpui::TestAppContext,
9751) {
9752 init_test(cx, |_| {});
9753
9754 let mut cx = EditorTestContext::new(cx).await;
9755
9756 let diff_base = r#"
9757 use some::mod1;
9758 use some::mod2;
9759
9760 const A: u32 = 42;
9761 const B: u32 = 42;
9762 const C: u32 = 42;
9763
9764 fn main(ˇ) {
9765 println!("hello");
9766
9767 println!("world");
9768 }
9769 "#
9770 .unindent();
9771
9772 cx.set_state(
9773 &r#"
9774 use some::mod2;
9775
9776 const A: u32 = 42;
9777 const C: u32 = 42;
9778
9779 fn main(ˇ) {
9780 //println!("hello");
9781
9782 println!("world");
9783 //
9784 //
9785 }
9786 "#
9787 .unindent(),
9788 );
9789
9790 cx.set_diff_base(Some(&diff_base));
9791 executor.run_until_parked();
9792 cx.update_editor(|editor, cx| {
9793 let snapshot = editor.snapshot(cx);
9794 let all_hunks = editor_hunks(editor, &snapshot, cx);
9795 assert_eq!(
9796 all_hunks,
9797 vec![
9798 (
9799 "use some::mod1;\n".to_string(),
9800 DiffHunkStatus::Removed,
9801 DisplayRow(0)..DisplayRow(0)
9802 ),
9803 (
9804 "const B: u32 = 42;\n".to_string(),
9805 DiffHunkStatus::Removed,
9806 DisplayRow(3)..DisplayRow(3)
9807 ),
9808 (
9809 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
9810 DiffHunkStatus::Modified,
9811 DisplayRow(5)..DisplayRow(7)
9812 ),
9813 (
9814 "".to_string(),
9815 DiffHunkStatus::Added,
9816 DisplayRow(9)..DisplayRow(11)
9817 ),
9818 ]
9819 );
9820 });
9821
9822 cx.update_editor(|editor, cx| {
9823 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
9824 });
9825 executor.run_until_parked();
9826 cx.assert_editor_state(
9827 &r#"
9828 use some::mod2;
9829
9830 const A: u32 = 42;
9831 const C: u32 = 42;
9832
9833 fn main(ˇ) {
9834 //println!("hello");
9835
9836 println!("world");
9837 //
9838 //
9839 }
9840 "#
9841 .unindent(),
9842 );
9843 cx.update_editor(|editor, cx| {
9844 let snapshot = editor.snapshot(cx);
9845 let all_hunks = editor_hunks(editor, &snapshot, cx);
9846 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
9847 assert_eq!(
9848 expanded_hunks_background_highlights(editor, cx),
9849 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
9850 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
9851 );
9852 assert_eq!(
9853 all_hunks,
9854 vec![
9855 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
9856 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
9857 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
9858 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
9859 ],
9860 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
9861 (from modified and removed hunks)"
9862 );
9863 assert_eq!(
9864 all_hunks, all_expanded_hunks,
9865 "Editor hunks should not change and all be expanded"
9866 );
9867 });
9868
9869 cx.set_diff_base(Some("new diff base!"));
9870 executor.run_until_parked();
9871
9872 cx.update_editor(|editor, cx| {
9873 let snapshot = editor.snapshot(cx);
9874 let all_hunks = editor_hunks(editor, &snapshot, cx);
9875 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
9876 assert_eq!(
9877 expanded_hunks_background_highlights(editor, cx),
9878 Vec::new(),
9879 "After diff base is changed, old git highlights should be removed"
9880 );
9881 assert_eq!(
9882 all_expanded_hunks,
9883 Vec::new(),
9884 "After diff base is changed, old git hunk expansions should be removed"
9885 );
9886 assert_eq!(
9887 all_hunks,
9888 vec![(
9889 "new diff base!".to_string(),
9890 DiffHunkStatus::Modified,
9891 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
9892 )],
9893 "After diff base is changed, hunks should update"
9894 );
9895 });
9896}
9897
9898#[gpui::test]
9899async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9900 init_test(cx, |_| {});
9901
9902 let mut cx = EditorTestContext::new(cx).await;
9903
9904 let diff_base = r#"
9905 use some::mod1;
9906 use some::mod2;
9907
9908 const A: u32 = 42;
9909 const B: u32 = 42;
9910 const C: u32 = 42;
9911
9912 fn main(ˇ) {
9913 println!("hello");
9914
9915 println!("world");
9916 }
9917
9918 fn another() {
9919 println!("another");
9920 }
9921
9922 fn another2() {
9923 println!("another2");
9924 }
9925 "#
9926 .unindent();
9927
9928 cx.set_state(
9929 &r#"
9930 «use some::mod2;
9931
9932 const A: u32 = 42;
9933 const C: u32 = 42;
9934
9935 fn main() {
9936 //println!("hello");
9937
9938 println!("world");
9939 //
9940 //ˇ»
9941 }
9942
9943 fn another() {
9944 println!("another");
9945 println!("another");
9946 }
9947
9948 println!("another2");
9949 }
9950 "#
9951 .unindent(),
9952 );
9953
9954 cx.set_diff_base(Some(&diff_base));
9955 executor.run_until_parked();
9956 cx.update_editor(|editor, cx| {
9957 let snapshot = editor.snapshot(cx);
9958 let all_hunks = editor_hunks(editor, &snapshot, cx);
9959 assert_eq!(
9960 all_hunks,
9961 vec![
9962 (
9963 "use some::mod1;\n".to_string(),
9964 DiffHunkStatus::Removed,
9965 DisplayRow(0)..DisplayRow(0)
9966 ),
9967 (
9968 "const B: u32 = 42;\n".to_string(),
9969 DiffHunkStatus::Removed,
9970 DisplayRow(3)..DisplayRow(3)
9971 ),
9972 (
9973 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
9974 DiffHunkStatus::Modified,
9975 DisplayRow(5)..DisplayRow(7)
9976 ),
9977 (
9978 "".to_string(),
9979 DiffHunkStatus::Added,
9980 DisplayRow(9)..DisplayRow(11)
9981 ),
9982 (
9983 "".to_string(),
9984 DiffHunkStatus::Added,
9985 DisplayRow(15)..DisplayRow(16)
9986 ),
9987 (
9988 "fn another2() {\n".to_string(),
9989 DiffHunkStatus::Removed,
9990 DisplayRow(18)..DisplayRow(18)
9991 ),
9992 ]
9993 );
9994 });
9995
9996 cx.update_editor(|editor, cx| {
9997 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
9998 });
9999 executor.run_until_parked();
10000 cx.assert_editor_state(
10001 &r#"
10002 «use some::mod2;
10003
10004 const A: u32 = 42;
10005 const C: u32 = 42;
10006
10007 fn main() {
10008 //println!("hello");
10009
10010 println!("world");
10011 //
10012 //ˇ»
10013 }
10014
10015 fn another() {
10016 println!("another");
10017 println!("another");
10018 }
10019
10020 println!("another2");
10021 }
10022 "#
10023 .unindent(),
10024 );
10025 cx.update_editor(|editor, cx| {
10026 let snapshot = editor.snapshot(cx);
10027 let all_hunks = editor_hunks(editor, &snapshot, cx);
10028 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10029 assert_eq!(
10030 expanded_hunks_background_highlights(editor, cx),
10031 vec![
10032 DisplayRow(9)..=DisplayRow(10),
10033 DisplayRow(13)..=DisplayRow(14),
10034 DisplayRow(19)..=DisplayRow(19)
10035 ]
10036 );
10037 assert_eq!(
10038 all_hunks,
10039 vec![
10040 (
10041 "use some::mod1;\n".to_string(),
10042 DiffHunkStatus::Removed,
10043 DisplayRow(1)..DisplayRow(1)
10044 ),
10045 (
10046 "const B: u32 = 42;\n".to_string(),
10047 DiffHunkStatus::Removed,
10048 DisplayRow(5)..DisplayRow(5)
10049 ),
10050 (
10051 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10052 DiffHunkStatus::Modified,
10053 DisplayRow(9)..DisplayRow(11)
10054 ),
10055 (
10056 "".to_string(),
10057 DiffHunkStatus::Added,
10058 DisplayRow(13)..DisplayRow(15)
10059 ),
10060 (
10061 "".to_string(),
10062 DiffHunkStatus::Added,
10063 DisplayRow(19)..DisplayRow(20)
10064 ),
10065 (
10066 "fn another2() {\n".to_string(),
10067 DiffHunkStatus::Removed,
10068 DisplayRow(23)..DisplayRow(23)
10069 ),
10070 ],
10071 );
10072 assert_eq!(all_hunks, all_expanded_hunks);
10073 });
10074
10075 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10076 cx.executor().run_until_parked();
10077 cx.assert_editor_state(
10078 &r#"
10079 «use some::mod2;
10080
10081 const A: u32 = 42;
10082 const C: u32 = 42;
10083
10084 fn main() {
10085 //println!("hello");
10086
10087 println!("world");
10088 //
10089 //ˇ»
10090 }
10091
10092 fn another() {
10093 println!("another");
10094 println!("another");
10095 }
10096
10097 println!("another2");
10098 }
10099 "#
10100 .unindent(),
10101 );
10102 cx.update_editor(|editor, cx| {
10103 let snapshot = editor.snapshot(cx);
10104 let all_hunks = editor_hunks(editor, &snapshot, cx);
10105 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10106 assert_eq!(
10107 expanded_hunks_background_highlights(editor, cx),
10108 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10109 "Only one hunk is left not folded, its highlight should be visible"
10110 );
10111 assert_eq!(
10112 all_hunks,
10113 vec![
10114 (
10115 "use some::mod1;\n".to_string(),
10116 DiffHunkStatus::Removed,
10117 DisplayRow(0)..DisplayRow(0)
10118 ),
10119 (
10120 "const B: u32 = 42;\n".to_string(),
10121 DiffHunkStatus::Removed,
10122 DisplayRow(0)..DisplayRow(0)
10123 ),
10124 (
10125 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10126 DiffHunkStatus::Modified,
10127 DisplayRow(0)..DisplayRow(0)
10128 ),
10129 (
10130 "".to_string(),
10131 DiffHunkStatus::Added,
10132 DisplayRow(0)..DisplayRow(1)
10133 ),
10134 (
10135 "".to_string(),
10136 DiffHunkStatus::Added,
10137 DisplayRow(5)..DisplayRow(6)
10138 ),
10139 (
10140 "fn another2() {\n".to_string(),
10141 DiffHunkStatus::Removed,
10142 DisplayRow(9)..DisplayRow(9)
10143 ),
10144 ],
10145 "Hunk list should still return shifted folded hunks"
10146 );
10147 assert_eq!(
10148 all_expanded_hunks,
10149 vec![
10150 (
10151 "".to_string(),
10152 DiffHunkStatus::Added,
10153 DisplayRow(5)..DisplayRow(6)
10154 ),
10155 (
10156 "fn another2() {\n".to_string(),
10157 DiffHunkStatus::Removed,
10158 DisplayRow(9)..DisplayRow(9)
10159 ),
10160 ],
10161 "Only non-folded hunks should be left expanded"
10162 );
10163 });
10164
10165 cx.update_editor(|editor, cx| {
10166 editor.select_all(&SelectAll, cx);
10167 editor.unfold_lines(&UnfoldLines, cx);
10168 });
10169 cx.executor().run_until_parked();
10170 cx.assert_editor_state(
10171 &r#"
10172 «use some::mod2;
10173
10174 const A: u32 = 42;
10175 const C: u32 = 42;
10176
10177 fn main() {
10178 //println!("hello");
10179
10180 println!("world");
10181 //
10182 //
10183 }
10184
10185 fn another() {
10186 println!("another");
10187 println!("another");
10188 }
10189
10190 println!("another2");
10191 }
10192 ˇ»"#
10193 .unindent(),
10194 );
10195 cx.update_editor(|editor, cx| {
10196 let snapshot = editor.snapshot(cx);
10197 let all_hunks = editor_hunks(editor, &snapshot, cx);
10198 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10199 assert_eq!(
10200 expanded_hunks_background_highlights(editor, cx),
10201 vec![
10202 DisplayRow(9)..=DisplayRow(10),
10203 DisplayRow(13)..=DisplayRow(14),
10204 DisplayRow(19)..=DisplayRow(19)
10205 ],
10206 "After unfolding, all hunk diffs should be visible again"
10207 );
10208 assert_eq!(
10209 all_hunks,
10210 vec![
10211 (
10212 "use some::mod1;\n".to_string(),
10213 DiffHunkStatus::Removed,
10214 DisplayRow(1)..DisplayRow(1)
10215 ),
10216 (
10217 "const B: u32 = 42;\n".to_string(),
10218 DiffHunkStatus::Removed,
10219 DisplayRow(5)..DisplayRow(5)
10220 ),
10221 (
10222 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10223 DiffHunkStatus::Modified,
10224 DisplayRow(9)..DisplayRow(11)
10225 ),
10226 (
10227 "".to_string(),
10228 DiffHunkStatus::Added,
10229 DisplayRow(13)..DisplayRow(15)
10230 ),
10231 (
10232 "".to_string(),
10233 DiffHunkStatus::Added,
10234 DisplayRow(19)..DisplayRow(20)
10235 ),
10236 (
10237 "fn another2() {\n".to_string(),
10238 DiffHunkStatus::Removed,
10239 DisplayRow(23)..DisplayRow(23)
10240 ),
10241 ],
10242 );
10243 assert_eq!(all_hunks, all_expanded_hunks);
10244 });
10245}
10246
10247#[gpui::test]
10248async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10249 init_test(cx, |_| {});
10250
10251 let cols = 4;
10252 let rows = 10;
10253 let sample_text_1 = sample_text(rows, cols, 'a');
10254 assert_eq!(
10255 sample_text_1,
10256 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10257 );
10258 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10259 let sample_text_2 = sample_text(rows, cols, 'l');
10260 assert_eq!(
10261 sample_text_2,
10262 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10263 );
10264 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10265 let sample_text_3 = sample_text(rows, cols, 'v');
10266 assert_eq!(
10267 sample_text_3,
10268 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10269 );
10270 let modified_sample_text_3 =
10271 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10272 let buffer_1 = cx.new_model(|cx| {
10273 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10274 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10275 buffer
10276 });
10277 let buffer_2 = cx.new_model(|cx| {
10278 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10279 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10280 buffer
10281 });
10282 let buffer_3 = cx.new_model(|cx| {
10283 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10284 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10285 buffer
10286 });
10287
10288 let multi_buffer = cx.new_model(|cx| {
10289 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10290 multibuffer.push_excerpts(
10291 buffer_1.clone(),
10292 [
10293 ExcerptRange {
10294 context: Point::new(0, 0)..Point::new(3, 0),
10295 primary: None,
10296 },
10297 ExcerptRange {
10298 context: Point::new(5, 0)..Point::new(7, 0),
10299 primary: None,
10300 },
10301 ExcerptRange {
10302 context: Point::new(9, 0)..Point::new(10, 4),
10303 primary: None,
10304 },
10305 ],
10306 cx,
10307 );
10308 multibuffer.push_excerpts(
10309 buffer_2.clone(),
10310 [
10311 ExcerptRange {
10312 context: Point::new(0, 0)..Point::new(3, 0),
10313 primary: None,
10314 },
10315 ExcerptRange {
10316 context: Point::new(5, 0)..Point::new(7, 0),
10317 primary: None,
10318 },
10319 ExcerptRange {
10320 context: Point::new(9, 0)..Point::new(10, 4),
10321 primary: None,
10322 },
10323 ],
10324 cx,
10325 );
10326 multibuffer.push_excerpts(
10327 buffer_3.clone(),
10328 [
10329 ExcerptRange {
10330 context: Point::new(0, 0)..Point::new(3, 0),
10331 primary: None,
10332 },
10333 ExcerptRange {
10334 context: Point::new(5, 0)..Point::new(7, 0),
10335 primary: None,
10336 },
10337 ExcerptRange {
10338 context: Point::new(9, 0)..Point::new(10, 4),
10339 primary: None,
10340 },
10341 ],
10342 cx,
10343 );
10344 multibuffer
10345 });
10346
10347 let fs = FakeFs::new(cx.executor());
10348 fs.insert_tree(
10349 "/a",
10350 json!({
10351 "main.rs": modified_sample_text_1,
10352 "other.rs": modified_sample_text_2,
10353 "lib.rs": modified_sample_text_3,
10354 }),
10355 )
10356 .await;
10357
10358 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10359 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10360 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10361 let multi_buffer_editor =
10362 cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
10363 cx.executor().run_until_parked();
10364
10365 let expected_all_hunks = vec![
10366 (
10367 "bbbb\n".to_string(),
10368 DiffHunkStatus::Removed,
10369 DisplayRow(3)..DisplayRow(3),
10370 ),
10371 (
10372 "nnnn\n".to_string(),
10373 DiffHunkStatus::Modified,
10374 DisplayRow(16)..DisplayRow(17),
10375 ),
10376 (
10377 "".to_string(),
10378 DiffHunkStatus::Added,
10379 DisplayRow(31)..DisplayRow(32),
10380 ),
10381 ];
10382 let expected_all_hunks_shifted = vec![
10383 (
10384 "bbbb\n".to_string(),
10385 DiffHunkStatus::Removed,
10386 DisplayRow(4)..DisplayRow(4),
10387 ),
10388 (
10389 "nnnn\n".to_string(),
10390 DiffHunkStatus::Modified,
10391 DisplayRow(18)..DisplayRow(19),
10392 ),
10393 (
10394 "".to_string(),
10395 DiffHunkStatus::Added,
10396 DisplayRow(33)..DisplayRow(34),
10397 ),
10398 ];
10399
10400 multi_buffer_editor.update(cx, |editor, cx| {
10401 let snapshot = editor.snapshot(cx);
10402 let all_hunks = editor_hunks(editor, &snapshot, cx);
10403 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10404 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10405 assert_eq!(all_hunks, expected_all_hunks);
10406 assert_eq!(all_expanded_hunks, Vec::new());
10407 });
10408
10409 multi_buffer_editor.update(cx, |editor, cx| {
10410 editor.select_all(&SelectAll, cx);
10411 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10412 });
10413 cx.executor().run_until_parked();
10414 multi_buffer_editor.update(cx, |editor, cx| {
10415 let snapshot = editor.snapshot(cx);
10416 let all_hunks = editor_hunks(editor, &snapshot, cx);
10417 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10418 assert_eq!(
10419 expanded_hunks_background_highlights(editor, cx),
10420 vec![
10421 DisplayRow(18)..=DisplayRow(18),
10422 DisplayRow(33)..=DisplayRow(33)
10423 ],
10424 );
10425 assert_eq!(all_hunks, expected_all_hunks_shifted);
10426 assert_eq!(all_hunks, all_expanded_hunks);
10427 });
10428
10429 multi_buffer_editor.update(cx, |editor, cx| {
10430 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10431 });
10432 cx.executor().run_until_parked();
10433 multi_buffer_editor.update(cx, |editor, cx| {
10434 let snapshot = editor.snapshot(cx);
10435 let all_hunks = editor_hunks(editor, &snapshot, cx);
10436 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10437 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10438 assert_eq!(all_hunks, expected_all_hunks);
10439 assert_eq!(all_expanded_hunks, Vec::new());
10440 });
10441
10442 multi_buffer_editor.update(cx, |editor, cx| {
10443 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10444 });
10445 cx.executor().run_until_parked();
10446 multi_buffer_editor.update(cx, |editor, cx| {
10447 let snapshot = editor.snapshot(cx);
10448 let all_hunks = editor_hunks(editor, &snapshot, cx);
10449 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10450 assert_eq!(
10451 expanded_hunks_background_highlights(editor, cx),
10452 vec![
10453 DisplayRow(18)..=DisplayRow(18),
10454 DisplayRow(33)..=DisplayRow(33)
10455 ],
10456 );
10457 assert_eq!(all_hunks, expected_all_hunks_shifted);
10458 assert_eq!(all_hunks, all_expanded_hunks);
10459 });
10460
10461 multi_buffer_editor.update(cx, |editor, cx| {
10462 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10463 });
10464 cx.executor().run_until_parked();
10465 multi_buffer_editor.update(cx, |editor, cx| {
10466 let snapshot = editor.snapshot(cx);
10467 let all_hunks = editor_hunks(editor, &snapshot, cx);
10468 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10469 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10470 assert_eq!(all_hunks, expected_all_hunks);
10471 assert_eq!(all_expanded_hunks, Vec::new());
10472 });
10473}
10474
10475#[gpui::test]
10476async fn test_edits_around_toggled_additions(
10477 executor: BackgroundExecutor,
10478 cx: &mut gpui::TestAppContext,
10479) {
10480 init_test(cx, |_| {});
10481
10482 let mut cx = EditorTestContext::new(cx).await;
10483
10484 let diff_base = r#"
10485 use some::mod1;
10486 use some::mod2;
10487
10488 const A: u32 = 42;
10489
10490 fn main() {
10491 println!("hello");
10492
10493 println!("world");
10494 }
10495 "#
10496 .unindent();
10497 executor.run_until_parked();
10498 cx.set_state(
10499 &r#"
10500 use some::mod1;
10501 use some::mod2;
10502
10503 const A: u32 = 42;
10504 const B: u32 = 42;
10505 const C: u32 = 42;
10506 ˇ
10507
10508 fn main() {
10509 println!("hello");
10510
10511 println!("world");
10512 }
10513 "#
10514 .unindent(),
10515 );
10516
10517 cx.set_diff_base(Some(&diff_base));
10518 executor.run_until_parked();
10519 cx.update_editor(|editor, cx| {
10520 let snapshot = editor.snapshot(cx);
10521 let all_hunks = editor_hunks(editor, &snapshot, cx);
10522 assert_eq!(
10523 all_hunks,
10524 vec![(
10525 "".to_string(),
10526 DiffHunkStatus::Added,
10527 DisplayRow(4)..DisplayRow(7)
10528 )]
10529 );
10530 });
10531 cx.update_editor(|editor, cx| {
10532 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10533 });
10534 executor.run_until_parked();
10535 cx.assert_editor_state(
10536 &r#"
10537 use some::mod1;
10538 use some::mod2;
10539
10540 const A: u32 = 42;
10541 const B: u32 = 42;
10542 const C: u32 = 42;
10543 ˇ
10544
10545 fn main() {
10546 println!("hello");
10547
10548 println!("world");
10549 }
10550 "#
10551 .unindent(),
10552 );
10553 cx.update_editor(|editor, cx| {
10554 let snapshot = editor.snapshot(cx);
10555 let all_hunks = editor_hunks(editor, &snapshot, cx);
10556 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10557 assert_eq!(
10558 all_hunks,
10559 vec![(
10560 "".to_string(),
10561 DiffHunkStatus::Added,
10562 DisplayRow(4)..DisplayRow(7)
10563 )]
10564 );
10565 assert_eq!(
10566 expanded_hunks_background_highlights(editor, cx),
10567 vec![DisplayRow(4)..=DisplayRow(6)]
10568 );
10569 assert_eq!(all_hunks, all_expanded_hunks);
10570 });
10571
10572 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10573 executor.run_until_parked();
10574 cx.assert_editor_state(
10575 &r#"
10576 use some::mod1;
10577 use some::mod2;
10578
10579 const A: u32 = 42;
10580 const B: u32 = 42;
10581 const C: u32 = 42;
10582 const D: u32 = 42;
10583 ˇ
10584
10585 fn main() {
10586 println!("hello");
10587
10588 println!("world");
10589 }
10590 "#
10591 .unindent(),
10592 );
10593 cx.update_editor(|editor, cx| {
10594 let snapshot = editor.snapshot(cx);
10595 let all_hunks = editor_hunks(editor, &snapshot, cx);
10596 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10597 assert_eq!(
10598 all_hunks,
10599 vec![(
10600 "".to_string(),
10601 DiffHunkStatus::Added,
10602 DisplayRow(4)..DisplayRow(8)
10603 )]
10604 );
10605 assert_eq!(
10606 expanded_hunks_background_highlights(editor, cx),
10607 vec![DisplayRow(4)..=DisplayRow(6)],
10608 "Edited hunk should have one more line added"
10609 );
10610 assert_eq!(
10611 all_hunks, all_expanded_hunks,
10612 "Expanded hunk should also grow with the addition"
10613 );
10614 });
10615
10616 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10617 executor.run_until_parked();
10618 cx.assert_editor_state(
10619 &r#"
10620 use some::mod1;
10621 use some::mod2;
10622
10623 const A: u32 = 42;
10624 const B: u32 = 42;
10625 const C: u32 = 42;
10626 const D: u32 = 42;
10627 const E: u32 = 42;
10628 ˇ
10629
10630 fn main() {
10631 println!("hello");
10632
10633 println!("world");
10634 }
10635 "#
10636 .unindent(),
10637 );
10638 cx.update_editor(|editor, cx| {
10639 let snapshot = editor.snapshot(cx);
10640 let all_hunks = editor_hunks(editor, &snapshot, cx);
10641 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10642 assert_eq!(
10643 all_hunks,
10644 vec![(
10645 "".to_string(),
10646 DiffHunkStatus::Added,
10647 DisplayRow(4)..DisplayRow(9)
10648 )]
10649 );
10650 assert_eq!(
10651 expanded_hunks_background_highlights(editor, cx),
10652 vec![DisplayRow(4)..=DisplayRow(6)],
10653 "Edited hunk should have one more line added"
10654 );
10655 assert_eq!(all_hunks, all_expanded_hunks);
10656 });
10657
10658 cx.update_editor(|editor, cx| {
10659 editor.move_up(&MoveUp, cx);
10660 editor.delete_line(&DeleteLine, cx);
10661 });
10662 executor.run_until_parked();
10663 cx.assert_editor_state(
10664 &r#"
10665 use some::mod1;
10666 use some::mod2;
10667
10668 const A: u32 = 42;
10669 const B: u32 = 42;
10670 const C: u32 = 42;
10671 const D: u32 = 42;
10672 ˇ
10673
10674 fn main() {
10675 println!("hello");
10676
10677 println!("world");
10678 }
10679 "#
10680 .unindent(),
10681 );
10682 cx.update_editor(|editor, cx| {
10683 let snapshot = editor.snapshot(cx);
10684 let all_hunks = editor_hunks(editor, &snapshot, cx);
10685 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10686 assert_eq!(
10687 all_hunks,
10688 vec![(
10689 "".to_string(),
10690 DiffHunkStatus::Added,
10691 DisplayRow(4)..DisplayRow(8)
10692 )]
10693 );
10694 assert_eq!(
10695 expanded_hunks_background_highlights(editor, cx),
10696 vec![DisplayRow(4)..=DisplayRow(6)],
10697 "Deleting a line should shrint the hunk"
10698 );
10699 assert_eq!(
10700 all_hunks, all_expanded_hunks,
10701 "Expanded hunk should also shrink with the addition"
10702 );
10703 });
10704
10705 cx.update_editor(|editor, cx| {
10706 editor.move_up(&MoveUp, cx);
10707 editor.delete_line(&DeleteLine, cx);
10708 editor.move_up(&MoveUp, cx);
10709 editor.delete_line(&DeleteLine, cx);
10710 editor.move_up(&MoveUp, cx);
10711 editor.delete_line(&DeleteLine, cx);
10712 });
10713 executor.run_until_parked();
10714 cx.assert_editor_state(
10715 &r#"
10716 use some::mod1;
10717 use some::mod2;
10718
10719 const A: u32 = 42;
10720 ˇ
10721
10722 fn main() {
10723 println!("hello");
10724
10725 println!("world");
10726 }
10727 "#
10728 .unindent(),
10729 );
10730 cx.update_editor(|editor, cx| {
10731 let snapshot = editor.snapshot(cx);
10732 let all_hunks = editor_hunks(editor, &snapshot, cx);
10733 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10734 assert_eq!(
10735 all_hunks,
10736 vec![(
10737 "".to_string(),
10738 DiffHunkStatus::Added,
10739 DisplayRow(5)..DisplayRow(6)
10740 )]
10741 );
10742 assert_eq!(
10743 expanded_hunks_background_highlights(editor, cx),
10744 vec![DisplayRow(5)..=DisplayRow(5)]
10745 );
10746 assert_eq!(all_hunks, all_expanded_hunks);
10747 });
10748
10749 cx.update_editor(|editor, cx| {
10750 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
10751 editor.delete_line(&DeleteLine, cx);
10752 });
10753 executor.run_until_parked();
10754 cx.assert_editor_state(
10755 &r#"
10756 ˇ
10757
10758 fn main() {
10759 println!("hello");
10760
10761 println!("world");
10762 }
10763 "#
10764 .unindent(),
10765 );
10766 cx.update_editor(|editor, cx| {
10767 let snapshot = editor.snapshot(cx);
10768 let all_hunks = editor_hunks(editor, &snapshot, cx);
10769 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10770 assert_eq!(
10771 all_hunks,
10772 vec![
10773 (
10774 "use some::mod1;\nuse some::mod2;\n".to_string(),
10775 DiffHunkStatus::Removed,
10776 DisplayRow(0)..DisplayRow(0)
10777 ),
10778 (
10779 "const A: u32 = 42;\n".to_string(),
10780 DiffHunkStatus::Removed,
10781 DisplayRow(2)..DisplayRow(2)
10782 )
10783 ]
10784 );
10785 assert_eq!(
10786 expanded_hunks_background_highlights(editor, cx),
10787 Vec::new(),
10788 "Should close all stale expanded addition hunks"
10789 );
10790 assert_eq!(
10791 all_expanded_hunks,
10792 vec![(
10793 "const A: u32 = 42;\n".to_string(),
10794 DiffHunkStatus::Removed,
10795 DisplayRow(2)..DisplayRow(2)
10796 )],
10797 "Should open hunks that were adjacent to the stale addition one"
10798 );
10799 });
10800}
10801
10802#[gpui::test]
10803async fn test_edits_around_toggled_deletions(
10804 executor: BackgroundExecutor,
10805 cx: &mut gpui::TestAppContext,
10806) {
10807 init_test(cx, |_| {});
10808
10809 let mut cx = EditorTestContext::new(cx).await;
10810
10811 let diff_base = r#"
10812 use some::mod1;
10813 use some::mod2;
10814
10815 const A: u32 = 42;
10816 const B: u32 = 42;
10817 const C: u32 = 42;
10818
10819
10820 fn main() {
10821 println!("hello");
10822
10823 println!("world");
10824 }
10825 "#
10826 .unindent();
10827 executor.run_until_parked();
10828 cx.set_state(
10829 &r#"
10830 use some::mod1;
10831 use some::mod2;
10832
10833 ˇconst B: u32 = 42;
10834 const C: u32 = 42;
10835
10836
10837 fn main() {
10838 println!("hello");
10839
10840 println!("world");
10841 }
10842 "#
10843 .unindent(),
10844 );
10845
10846 cx.set_diff_base(Some(&diff_base));
10847 executor.run_until_parked();
10848 cx.update_editor(|editor, cx| {
10849 let snapshot = editor.snapshot(cx);
10850 let all_hunks = editor_hunks(editor, &snapshot, cx);
10851 assert_eq!(
10852 all_hunks,
10853 vec![(
10854 "const A: u32 = 42;\n".to_string(),
10855 DiffHunkStatus::Removed,
10856 DisplayRow(3)..DisplayRow(3)
10857 )]
10858 );
10859 });
10860 cx.update_editor(|editor, cx| {
10861 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10862 });
10863 executor.run_until_parked();
10864 cx.assert_editor_state(
10865 &r#"
10866 use some::mod1;
10867 use some::mod2;
10868
10869 ˇconst B: u32 = 42;
10870 const C: u32 = 42;
10871
10872
10873 fn main() {
10874 println!("hello");
10875
10876 println!("world");
10877 }
10878 "#
10879 .unindent(),
10880 );
10881 cx.update_editor(|editor, cx| {
10882 let snapshot = editor.snapshot(cx);
10883 let all_hunks = editor_hunks(editor, &snapshot, cx);
10884 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10885 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10886 assert_eq!(
10887 all_hunks,
10888 vec![(
10889 "const A: u32 = 42;\n".to_string(),
10890 DiffHunkStatus::Removed,
10891 DisplayRow(4)..DisplayRow(4)
10892 )]
10893 );
10894 assert_eq!(all_hunks, all_expanded_hunks);
10895 });
10896
10897 cx.update_editor(|editor, cx| {
10898 editor.delete_line(&DeleteLine, cx);
10899 });
10900 executor.run_until_parked();
10901 cx.assert_editor_state(
10902 &r#"
10903 use some::mod1;
10904 use some::mod2;
10905
10906 ˇconst C: u32 = 42;
10907
10908
10909 fn main() {
10910 println!("hello");
10911
10912 println!("world");
10913 }
10914 "#
10915 .unindent(),
10916 );
10917 cx.update_editor(|editor, cx| {
10918 let snapshot = editor.snapshot(cx);
10919 let all_hunks = editor_hunks(editor, &snapshot, cx);
10920 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10921 assert_eq!(
10922 expanded_hunks_background_highlights(editor, cx),
10923 Vec::new(),
10924 "Deleted hunks do not highlight current editor's background"
10925 );
10926 assert_eq!(
10927 all_hunks,
10928 vec![(
10929 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
10930 DiffHunkStatus::Removed,
10931 DisplayRow(5)..DisplayRow(5)
10932 )]
10933 );
10934 assert_eq!(all_hunks, all_expanded_hunks);
10935 });
10936
10937 cx.update_editor(|editor, cx| {
10938 editor.delete_line(&DeleteLine, cx);
10939 });
10940 executor.run_until_parked();
10941 cx.assert_editor_state(
10942 &r#"
10943 use some::mod1;
10944 use some::mod2;
10945
10946 ˇ
10947
10948 fn main() {
10949 println!("hello");
10950
10951 println!("world");
10952 }
10953 "#
10954 .unindent(),
10955 );
10956 cx.update_editor(|editor, cx| {
10957 let snapshot = editor.snapshot(cx);
10958 let all_hunks = editor_hunks(editor, &snapshot, cx);
10959 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10960 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10961 assert_eq!(
10962 all_hunks,
10963 vec![(
10964 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10965 DiffHunkStatus::Removed,
10966 DisplayRow(6)..DisplayRow(6)
10967 )]
10968 );
10969 assert_eq!(all_hunks, all_expanded_hunks);
10970 });
10971
10972 cx.update_editor(|editor, cx| {
10973 editor.handle_input("replacement", cx);
10974 });
10975 executor.run_until_parked();
10976 cx.assert_editor_state(
10977 &r#"
10978 use some::mod1;
10979 use some::mod2;
10980
10981 replacementˇ
10982
10983 fn main() {
10984 println!("hello");
10985
10986 println!("world");
10987 }
10988 "#
10989 .unindent(),
10990 );
10991 cx.update_editor(|editor, cx| {
10992 let snapshot = editor.snapshot(cx);
10993 let all_hunks = editor_hunks(editor, &snapshot, cx);
10994 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10995 assert_eq!(
10996 all_hunks,
10997 vec![(
10998 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
10999 DiffHunkStatus::Modified,
11000 DisplayRow(7)..DisplayRow(8)
11001 )]
11002 );
11003 assert_eq!(
11004 expanded_hunks_background_highlights(editor, cx),
11005 vec![DisplayRow(7)..=DisplayRow(7)],
11006 "Modified expanded hunks should display additions and highlight their background"
11007 );
11008 assert_eq!(all_hunks, all_expanded_hunks);
11009 });
11010}
11011
11012#[gpui::test]
11013async fn test_edits_around_toggled_modifications(
11014 executor: BackgroundExecutor,
11015 cx: &mut gpui::TestAppContext,
11016) {
11017 init_test(cx, |_| {});
11018
11019 let mut cx = EditorTestContext::new(cx).await;
11020
11021 let diff_base = r#"
11022 use some::mod1;
11023 use some::mod2;
11024
11025 const A: u32 = 42;
11026 const B: u32 = 42;
11027 const C: u32 = 42;
11028 const D: u32 = 42;
11029
11030
11031 fn main() {
11032 println!("hello");
11033
11034 println!("world");
11035 }"#
11036 .unindent();
11037 executor.run_until_parked();
11038 cx.set_state(
11039 &r#"
11040 use some::mod1;
11041 use some::mod2;
11042
11043 const A: u32 = 42;
11044 const B: u32 = 42;
11045 const C: u32 = 43ˇ
11046 const D: u32 = 42;
11047
11048
11049 fn main() {
11050 println!("hello");
11051
11052 println!("world");
11053 }"#
11054 .unindent(),
11055 );
11056
11057 cx.set_diff_base(Some(&diff_base));
11058 executor.run_until_parked();
11059 cx.update_editor(|editor, cx| {
11060 let snapshot = editor.snapshot(cx);
11061 let all_hunks = editor_hunks(editor, &snapshot, cx);
11062 assert_eq!(
11063 all_hunks,
11064 vec![(
11065 "const C: u32 = 42;\n".to_string(),
11066 DiffHunkStatus::Modified,
11067 DisplayRow(5)..DisplayRow(6)
11068 )]
11069 );
11070 });
11071 cx.update_editor(|editor, cx| {
11072 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11073 });
11074 executor.run_until_parked();
11075 cx.assert_editor_state(
11076 &r#"
11077 use some::mod1;
11078 use some::mod2;
11079
11080 const A: u32 = 42;
11081 const B: u32 = 42;
11082 const C: u32 = 43ˇ
11083 const D: u32 = 42;
11084
11085
11086 fn main() {
11087 println!("hello");
11088
11089 println!("world");
11090 }"#
11091 .unindent(),
11092 );
11093 cx.update_editor(|editor, cx| {
11094 let snapshot = editor.snapshot(cx);
11095 let all_hunks = editor_hunks(editor, &snapshot, cx);
11096 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11097 assert_eq!(
11098 expanded_hunks_background_highlights(editor, cx),
11099 vec![DisplayRow(6)..=DisplayRow(6)],
11100 );
11101 assert_eq!(
11102 all_hunks,
11103 vec![(
11104 "const C: u32 = 42;\n".to_string(),
11105 DiffHunkStatus::Modified,
11106 DisplayRow(6)..DisplayRow(7)
11107 )]
11108 );
11109 assert_eq!(all_hunks, all_expanded_hunks);
11110 });
11111
11112 cx.update_editor(|editor, cx| {
11113 editor.handle_input("\nnew_line\n", cx);
11114 });
11115 executor.run_until_parked();
11116 cx.assert_editor_state(
11117 &r#"
11118 use some::mod1;
11119 use some::mod2;
11120
11121 const A: u32 = 42;
11122 const B: u32 = 42;
11123 const C: u32 = 43
11124 new_line
11125 ˇ
11126 const D: u32 = 42;
11127
11128
11129 fn main() {
11130 println!("hello");
11131
11132 println!("world");
11133 }"#
11134 .unindent(),
11135 );
11136 cx.update_editor(|editor, cx| {
11137 let snapshot = editor.snapshot(cx);
11138 let all_hunks = editor_hunks(editor, &snapshot, cx);
11139 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11140 assert_eq!(
11141 expanded_hunks_background_highlights(editor, cx),
11142 vec![DisplayRow(6)..=DisplayRow(6)],
11143 "Modified hunk should grow highlighted lines on more text additions"
11144 );
11145 assert_eq!(
11146 all_hunks,
11147 vec![(
11148 "const C: u32 = 42;\n".to_string(),
11149 DiffHunkStatus::Modified,
11150 DisplayRow(6)..DisplayRow(9)
11151 )]
11152 );
11153 assert_eq!(all_hunks, all_expanded_hunks);
11154 });
11155
11156 cx.update_editor(|editor, cx| {
11157 editor.move_up(&MoveUp, cx);
11158 editor.move_up(&MoveUp, cx);
11159 editor.move_up(&MoveUp, cx);
11160 editor.delete_line(&DeleteLine, cx);
11161 });
11162 executor.run_until_parked();
11163 cx.assert_editor_state(
11164 &r#"
11165 use some::mod1;
11166 use some::mod2;
11167
11168 const A: u32 = 42;
11169 ˇconst C: u32 = 43
11170 new_line
11171
11172 const D: u32 = 42;
11173
11174
11175 fn main() {
11176 println!("hello");
11177
11178 println!("world");
11179 }"#
11180 .unindent(),
11181 );
11182 cx.update_editor(|editor, cx| {
11183 let snapshot = editor.snapshot(cx);
11184 let all_hunks = editor_hunks(editor, &snapshot, cx);
11185 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11186 assert_eq!(
11187 expanded_hunks_background_highlights(editor, cx),
11188 vec![DisplayRow(6)..=DisplayRow(8)],
11189 );
11190 assert_eq!(
11191 all_hunks,
11192 vec![(
11193 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11194 DiffHunkStatus::Modified,
11195 DisplayRow(6)..DisplayRow(9)
11196 )],
11197 "Modified hunk should grow deleted lines on text deletions above"
11198 );
11199 assert_eq!(all_hunks, all_expanded_hunks);
11200 });
11201
11202 cx.update_editor(|editor, cx| {
11203 editor.move_up(&MoveUp, cx);
11204 editor.handle_input("v", cx);
11205 });
11206 executor.run_until_parked();
11207 cx.assert_editor_state(
11208 &r#"
11209 use some::mod1;
11210 use some::mod2;
11211
11212 vˇconst A: u32 = 42;
11213 const C: u32 = 43
11214 new_line
11215
11216 const D: u32 = 42;
11217
11218
11219 fn main() {
11220 println!("hello");
11221
11222 println!("world");
11223 }"#
11224 .unindent(),
11225 );
11226 cx.update_editor(|editor, cx| {
11227 let snapshot = editor.snapshot(cx);
11228 let all_hunks = editor_hunks(editor, &snapshot, cx);
11229 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11230 assert_eq!(
11231 expanded_hunks_background_highlights(editor, cx),
11232 vec![DisplayRow(6)..=DisplayRow(9)],
11233 "Modified hunk should grow deleted lines on text modifications above"
11234 );
11235 assert_eq!(
11236 all_hunks,
11237 vec![(
11238 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11239 DiffHunkStatus::Modified,
11240 DisplayRow(6)..DisplayRow(10)
11241 )]
11242 );
11243 assert_eq!(all_hunks, all_expanded_hunks);
11244 });
11245
11246 cx.update_editor(|editor, cx| {
11247 editor.move_down(&MoveDown, cx);
11248 editor.move_down(&MoveDown, cx);
11249 editor.delete_line(&DeleteLine, cx)
11250 });
11251 executor.run_until_parked();
11252 cx.assert_editor_state(
11253 &r#"
11254 use some::mod1;
11255 use some::mod2;
11256
11257 vconst A: u32 = 42;
11258 const C: u32 = 43
11259 ˇ
11260 const D: u32 = 42;
11261
11262
11263 fn main() {
11264 println!("hello");
11265
11266 println!("world");
11267 }"#
11268 .unindent(),
11269 );
11270 cx.update_editor(|editor, cx| {
11271 let snapshot = editor.snapshot(cx);
11272 let all_hunks = editor_hunks(editor, &snapshot, cx);
11273 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11274 assert_eq!(
11275 expanded_hunks_background_highlights(editor, cx),
11276 vec![DisplayRow(6)..=DisplayRow(8)],
11277 "Modified hunk should grow shrink lines on modification lines removal"
11278 );
11279 assert_eq!(
11280 all_hunks,
11281 vec![(
11282 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11283 DiffHunkStatus::Modified,
11284 DisplayRow(6)..DisplayRow(9)
11285 )]
11286 );
11287 assert_eq!(all_hunks, all_expanded_hunks);
11288 });
11289
11290 cx.update_editor(|editor, cx| {
11291 editor.move_up(&MoveUp, cx);
11292 editor.move_up(&MoveUp, cx);
11293 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11294 editor.delete_line(&DeleteLine, cx)
11295 });
11296 executor.run_until_parked();
11297 cx.assert_editor_state(
11298 &r#"
11299 use some::mod1;
11300 use some::mod2;
11301
11302 ˇ
11303
11304 fn main() {
11305 println!("hello");
11306
11307 println!("world");
11308 }"#
11309 .unindent(),
11310 );
11311 cx.update_editor(|editor, cx| {
11312 let snapshot = editor.snapshot(cx);
11313 let all_hunks = editor_hunks(editor, &snapshot, cx);
11314 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11315 assert_eq!(
11316 expanded_hunks_background_highlights(editor, cx),
11317 Vec::new(),
11318 "Modified hunk should turn into a removed one on all modified lines removal"
11319 );
11320 assert_eq!(
11321 all_hunks,
11322 vec![(
11323 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11324 .to_string(),
11325 DiffHunkStatus::Removed,
11326 DisplayRow(7)..DisplayRow(7)
11327 )]
11328 );
11329 assert_eq!(all_hunks, all_expanded_hunks);
11330 });
11331}
11332
11333#[gpui::test]
11334async fn test_multiple_expanded_hunks_merge(
11335 executor: BackgroundExecutor,
11336 cx: &mut gpui::TestAppContext,
11337) {
11338 init_test(cx, |_| {});
11339
11340 let mut cx = EditorTestContext::new(cx).await;
11341
11342 let diff_base = r#"
11343 use some::mod1;
11344 use some::mod2;
11345
11346 const A: u32 = 42;
11347 const B: u32 = 42;
11348 const C: u32 = 42;
11349 const D: u32 = 42;
11350
11351
11352 fn main() {
11353 println!("hello");
11354
11355 println!("world");
11356 }"#
11357 .unindent();
11358 executor.run_until_parked();
11359 cx.set_state(
11360 &r#"
11361 use some::mod1;
11362 use some::mod2;
11363
11364 const A: u32 = 42;
11365 const B: u32 = 42;
11366 const C: u32 = 43ˇ
11367 const D: u32 = 42;
11368
11369
11370 fn main() {
11371 println!("hello");
11372
11373 println!("world");
11374 }"#
11375 .unindent(),
11376 );
11377
11378 cx.set_diff_base(Some(&diff_base));
11379 executor.run_until_parked();
11380 cx.update_editor(|editor, cx| {
11381 let snapshot = editor.snapshot(cx);
11382 let all_hunks = editor_hunks(editor, &snapshot, cx);
11383 assert_eq!(
11384 all_hunks,
11385 vec![(
11386 "const C: u32 = 42;\n".to_string(),
11387 DiffHunkStatus::Modified,
11388 DisplayRow(5)..DisplayRow(6)
11389 )]
11390 );
11391 });
11392 cx.update_editor(|editor, cx| {
11393 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11394 });
11395 executor.run_until_parked();
11396 cx.assert_editor_state(
11397 &r#"
11398 use some::mod1;
11399 use some::mod2;
11400
11401 const A: u32 = 42;
11402 const B: u32 = 42;
11403 const C: u32 = 43ˇ
11404 const D: u32 = 42;
11405
11406
11407 fn main() {
11408 println!("hello");
11409
11410 println!("world");
11411 }"#
11412 .unindent(),
11413 );
11414 cx.update_editor(|editor, cx| {
11415 let snapshot = editor.snapshot(cx);
11416 let all_hunks = editor_hunks(editor, &snapshot, cx);
11417 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11418 assert_eq!(
11419 expanded_hunks_background_highlights(editor, cx),
11420 vec![DisplayRow(6)..=DisplayRow(6)],
11421 );
11422 assert_eq!(
11423 all_hunks,
11424 vec![(
11425 "const C: u32 = 42;\n".to_string(),
11426 DiffHunkStatus::Modified,
11427 DisplayRow(6)..DisplayRow(7)
11428 )]
11429 );
11430 assert_eq!(all_hunks, all_expanded_hunks);
11431 });
11432
11433 cx.update_editor(|editor, cx| {
11434 editor.handle_input("\nnew_line\n", cx);
11435 });
11436 executor.run_until_parked();
11437 cx.assert_editor_state(
11438 &r#"
11439 use some::mod1;
11440 use some::mod2;
11441
11442 const A: u32 = 42;
11443 const B: u32 = 42;
11444 const C: u32 = 43
11445 new_line
11446 ˇ
11447 const D: u32 = 42;
11448
11449
11450 fn main() {
11451 println!("hello");
11452
11453 println!("world");
11454 }"#
11455 .unindent(),
11456 );
11457}
11458
11459async fn setup_indent_guides_editor(
11460 text: &str,
11461 cx: &mut gpui::TestAppContext,
11462) -> (BufferId, EditorTestContext) {
11463 init_test(cx, |_| {});
11464
11465 let mut cx = EditorTestContext::new(cx).await;
11466
11467 let buffer_id = cx.update_editor(|editor, cx| {
11468 editor.set_text(text, cx);
11469 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11470 let buffer_id = buffer_ids[0];
11471 buffer_id
11472 });
11473
11474 (buffer_id, cx)
11475}
11476
11477fn assert_indent_guides(
11478 range: Range<u32>,
11479 expected: Vec<IndentGuide>,
11480 active_indices: Option<Vec<usize>>,
11481 cx: &mut EditorTestContext,
11482) {
11483 let indent_guides = cx.update_editor(|editor, cx| {
11484 let snapshot = editor.snapshot(cx).display_snapshot;
11485 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11486 MultiBufferRow(range.start)..MultiBufferRow(range.end),
11487 &snapshot,
11488 cx,
11489 );
11490
11491 indent_guides.sort_by(|a, b| {
11492 a.depth.cmp(&b.depth).then(
11493 a.start_row
11494 .cmp(&b.start_row)
11495 .then(a.end_row.cmp(&b.end_row)),
11496 )
11497 });
11498 indent_guides
11499 });
11500
11501 if let Some(expected) = active_indices {
11502 let active_indices = cx.update_editor(|editor, cx| {
11503 let snapshot = editor.snapshot(cx).display_snapshot;
11504 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11505 });
11506
11507 assert_eq!(
11508 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11509 expected,
11510 "Active indent guide indices do not match"
11511 );
11512 }
11513
11514 let expected: Vec<_> = expected
11515 .into_iter()
11516 .map(|guide| MultiBufferIndentGuide {
11517 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11518 buffer: guide,
11519 })
11520 .collect();
11521
11522 assert_eq!(indent_guides, expected, "Indent guides do not match");
11523}
11524
11525#[gpui::test]
11526async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11527 let (buffer_id, mut cx) = setup_indent_guides_editor(
11528 &"
11529 fn main() {
11530 let a = 1;
11531 }"
11532 .unindent(),
11533 cx,
11534 )
11535 .await;
11536
11537 assert_indent_guides(
11538 0..3,
11539 vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)],
11540 None,
11541 &mut cx,
11542 );
11543}
11544
11545#[gpui::test]
11546async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11547 let (buffer_id, mut cx) = setup_indent_guides_editor(
11548 &"
11549 fn main() {
11550 let a = 1;
11551 let b = 2;
11552 }"
11553 .unindent(),
11554 cx,
11555 )
11556 .await;
11557
11558 assert_indent_guides(
11559 0..4,
11560 vec![IndentGuide::new(buffer_id, 1, 2, 0, 4)],
11561 None,
11562 &mut cx,
11563 );
11564}
11565
11566#[gpui::test]
11567async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11568 let (buffer_id, mut cx) = setup_indent_guides_editor(
11569 &"
11570 fn main() {
11571 let a = 1;
11572 if a == 3 {
11573 let b = 2;
11574 } else {
11575 let c = 3;
11576 }
11577 }"
11578 .unindent(),
11579 cx,
11580 )
11581 .await;
11582
11583 assert_indent_guides(
11584 0..8,
11585 vec![
11586 IndentGuide::new(buffer_id, 1, 6, 0, 4),
11587 IndentGuide::new(buffer_id, 3, 3, 1, 4),
11588 IndentGuide::new(buffer_id, 5, 5, 1, 4),
11589 ],
11590 None,
11591 &mut cx,
11592 );
11593}
11594
11595#[gpui::test]
11596async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11597 let (buffer_id, mut cx) = setup_indent_guides_editor(
11598 &"
11599 fn main() {
11600 let a = 1;
11601 let b = 2;
11602 let c = 3;
11603 }"
11604 .unindent(),
11605 cx,
11606 )
11607 .await;
11608
11609 assert_indent_guides(
11610 0..5,
11611 vec![
11612 IndentGuide::new(buffer_id, 1, 3, 0, 4),
11613 IndentGuide::new(buffer_id, 2, 2, 1, 4),
11614 ],
11615 None,
11616 &mut cx,
11617 );
11618}
11619
11620#[gpui::test]
11621async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
11622 let (buffer_id, mut cx) = setup_indent_guides_editor(
11623 &"
11624 fn main() {
11625 let a = 1;
11626
11627 let c = 3;
11628 }"
11629 .unindent(),
11630 cx,
11631 )
11632 .await;
11633
11634 assert_indent_guides(
11635 0..5,
11636 vec![IndentGuide::new(buffer_id, 1, 3, 0, 4)],
11637 None,
11638 &mut cx,
11639 );
11640}
11641
11642#[gpui::test]
11643async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
11644 let (buffer_id, mut cx) = setup_indent_guides_editor(
11645 &"
11646 fn main() {
11647 let a = 1;
11648
11649 let c = 3;
11650
11651 if a == 3 {
11652 let b = 2;
11653 } else {
11654 let c = 3;
11655 }
11656 }"
11657 .unindent(),
11658 cx,
11659 )
11660 .await;
11661
11662 assert_indent_guides(
11663 0..11,
11664 vec![
11665 IndentGuide::new(buffer_id, 1, 9, 0, 4),
11666 IndentGuide::new(buffer_id, 6, 6, 1, 4),
11667 IndentGuide::new(buffer_id, 8, 8, 1, 4),
11668 ],
11669 None,
11670 &mut cx,
11671 );
11672}
11673
11674#[gpui::test]
11675async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
11676 let (buffer_id, mut cx) = setup_indent_guides_editor(
11677 &"
11678 fn main() {
11679 let a = 1;
11680
11681 let c = 3;
11682
11683 if a == 3 {
11684 let b = 2;
11685 } else {
11686 let c = 3;
11687 }
11688 }"
11689 .unindent(),
11690 cx,
11691 )
11692 .await;
11693
11694 assert_indent_guides(
11695 1..11,
11696 vec![
11697 IndentGuide::new(buffer_id, 1, 9, 0, 4),
11698 IndentGuide::new(buffer_id, 6, 6, 1, 4),
11699 IndentGuide::new(buffer_id, 8, 8, 1, 4),
11700 ],
11701 None,
11702 &mut cx,
11703 );
11704}
11705
11706#[gpui::test]
11707async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
11708 let (buffer_id, mut cx) = setup_indent_guides_editor(
11709 &"
11710 fn main() {
11711 let a = 1;
11712
11713 let c = 3;
11714
11715 if a == 3 {
11716 let b = 2;
11717 } else {
11718 let c = 3;
11719 }
11720 }"
11721 .unindent(),
11722 cx,
11723 )
11724 .await;
11725
11726 assert_indent_guides(
11727 1..10,
11728 vec![
11729 IndentGuide::new(buffer_id, 1, 9, 0, 4),
11730 IndentGuide::new(buffer_id, 6, 6, 1, 4),
11731 IndentGuide::new(buffer_id, 8, 8, 1, 4),
11732 ],
11733 None,
11734 &mut cx,
11735 );
11736}
11737
11738#[gpui::test]
11739async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
11740 let (buffer_id, mut cx) = setup_indent_guides_editor(
11741 &"
11742 block1
11743 block2
11744 block3
11745 block4
11746 block2
11747 block1
11748 block1"
11749 .unindent(),
11750 cx,
11751 )
11752 .await;
11753
11754 assert_indent_guides(
11755 1..10,
11756 vec![
11757 IndentGuide::new(buffer_id, 1, 4, 0, 4),
11758 IndentGuide::new(buffer_id, 2, 3, 1, 4),
11759 IndentGuide::new(buffer_id, 3, 3, 2, 4),
11760 ],
11761 None,
11762 &mut cx,
11763 );
11764}
11765
11766#[gpui::test]
11767async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
11768 let (buffer_id, mut cx) = setup_indent_guides_editor(
11769 &"
11770 block1
11771 block2
11772 block3
11773
11774 block1
11775 block1"
11776 .unindent(),
11777 cx,
11778 )
11779 .await;
11780
11781 assert_indent_guides(
11782 0..6,
11783 vec![
11784 IndentGuide::new(buffer_id, 1, 2, 0, 4),
11785 IndentGuide::new(buffer_id, 2, 2, 1, 4),
11786 ],
11787 None,
11788 &mut cx,
11789 );
11790}
11791
11792#[gpui::test]
11793async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
11794 let (buffer_id, mut cx) = setup_indent_guides_editor(
11795 &"
11796 block1
11797
11798
11799
11800 block2
11801 "
11802 .unindent(),
11803 cx,
11804 )
11805 .await;
11806
11807 assert_indent_guides(
11808 0..1,
11809 vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)],
11810 None,
11811 &mut cx,
11812 );
11813}
11814
11815#[gpui::test]
11816async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
11817 let (buffer_id, mut cx) = setup_indent_guides_editor(
11818 &"
11819 def a:
11820 \tb = 3
11821 \tif True:
11822 \t\tc = 4
11823 \t\td = 5
11824 \tprint(b)
11825 "
11826 .unindent(),
11827 cx,
11828 )
11829 .await;
11830
11831 assert_indent_guides(
11832 0..6,
11833 vec![
11834 IndentGuide::new(buffer_id, 1, 6, 0, 4),
11835 IndentGuide::new(buffer_id, 3, 4, 1, 4),
11836 ],
11837 None,
11838 &mut cx,
11839 );
11840}
11841
11842#[gpui::test]
11843async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11844 let (buffer_id, mut cx) = setup_indent_guides_editor(
11845 &"
11846 fn main() {
11847 let a = 1;
11848 }"
11849 .unindent(),
11850 cx,
11851 )
11852 .await;
11853
11854 cx.update_editor(|editor, cx| {
11855 editor.change_selections(None, cx, |s| {
11856 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
11857 });
11858 });
11859
11860 assert_indent_guides(
11861 0..3,
11862 vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)],
11863 Some(vec![0]),
11864 &mut cx,
11865 );
11866}
11867
11868#[gpui::test]
11869async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
11870 let (buffer_id, mut cx) = setup_indent_guides_editor(
11871 &"
11872 fn main() {
11873 if 1 == 2 {
11874 let a = 1;
11875 }
11876 }"
11877 .unindent(),
11878 cx,
11879 )
11880 .await;
11881
11882 cx.update_editor(|editor, cx| {
11883 editor.change_selections(None, cx, |s| {
11884 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
11885 });
11886 });
11887
11888 assert_indent_guides(
11889 0..4,
11890 vec![
11891 IndentGuide::new(buffer_id, 1, 3, 0, 4),
11892 IndentGuide::new(buffer_id, 2, 2, 1, 4),
11893 ],
11894 Some(vec![1]),
11895 &mut cx,
11896 );
11897
11898 cx.update_editor(|editor, cx| {
11899 editor.change_selections(None, cx, |s| {
11900 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
11901 });
11902 });
11903
11904 assert_indent_guides(
11905 0..4,
11906 vec![
11907 IndentGuide::new(buffer_id, 1, 3, 0, 4),
11908 IndentGuide::new(buffer_id, 2, 2, 1, 4),
11909 ],
11910 Some(vec![1]),
11911 &mut cx,
11912 );
11913
11914 cx.update_editor(|editor, cx| {
11915 editor.change_selections(None, cx, |s| {
11916 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
11917 });
11918 });
11919
11920 assert_indent_guides(
11921 0..4,
11922 vec![
11923 IndentGuide::new(buffer_id, 1, 3, 0, 4),
11924 IndentGuide::new(buffer_id, 2, 2, 1, 4),
11925 ],
11926 Some(vec![0]),
11927 &mut cx,
11928 );
11929}
11930
11931#[gpui::test]
11932async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
11933 let (buffer_id, mut cx) = setup_indent_guides_editor(
11934 &"
11935 fn main() {
11936 let a = 1;
11937
11938 let b = 2;
11939 }"
11940 .unindent(),
11941 cx,
11942 )
11943 .await;
11944
11945 cx.update_editor(|editor, cx| {
11946 editor.change_selections(None, cx, |s| {
11947 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
11948 });
11949 });
11950
11951 assert_indent_guides(
11952 0..5,
11953 vec![IndentGuide::new(buffer_id, 1, 3, 0, 4)],
11954 Some(vec![0]),
11955 &mut cx,
11956 );
11957}
11958
11959#[gpui::test]
11960async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
11961 let (buffer_id, mut cx) = setup_indent_guides_editor(
11962 &"
11963 def m:
11964 a = 1
11965 pass"
11966 .unindent(),
11967 cx,
11968 )
11969 .await;
11970
11971 cx.update_editor(|editor, cx| {
11972 editor.change_selections(None, cx, |s| {
11973 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
11974 });
11975 });
11976
11977 assert_indent_guides(
11978 0..3,
11979 vec![IndentGuide::new(buffer_id, 1, 2, 0, 4)],
11980 Some(vec![0]),
11981 &mut cx,
11982 );
11983}
11984
11985#[gpui::test]
11986fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
11987 init_test(cx, |_| {});
11988
11989 let editor = cx.add_window(|cx| {
11990 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
11991 build_editor(buffer, cx)
11992 });
11993
11994 let render_args = Arc::new(Mutex::new(None));
11995 let snapshot = editor
11996 .update(cx, |editor, cx| {
11997 let snapshot = editor.buffer().read(cx).snapshot(cx);
11998 let range =
11999 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12000
12001 struct RenderArgs {
12002 row: MultiBufferRow,
12003 folded: bool,
12004 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12005 }
12006
12007 let flap = Flap::new(
12008 range,
12009 FoldPlaceholder::test(),
12010 {
12011 let toggle_callback = render_args.clone();
12012 move |row, folded, callback, _cx| {
12013 *toggle_callback.lock() = Some(RenderArgs {
12014 row,
12015 folded,
12016 callback,
12017 });
12018 div()
12019 }
12020 },
12021 |_row, _folded, _cx| div(),
12022 );
12023
12024 editor.insert_flaps(Some(flap), cx);
12025 let snapshot = editor.snapshot(cx);
12026 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12027 snapshot
12028 })
12029 .unwrap();
12030
12031 let render_args = render_args.lock().take().unwrap();
12032 assert_eq!(render_args.row, MultiBufferRow(1));
12033 assert_eq!(render_args.folded, false);
12034 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12035
12036 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12037 .unwrap();
12038 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12039 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12040
12041 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12042 .unwrap();
12043 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12044 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12045}
12046
12047fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12048 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12049 point..point
12050}
12051
12052fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12053 let (text, ranges) = marked_text_ranges(marked_text, true);
12054 assert_eq!(view.text(cx), text);
12055 assert_eq!(
12056 view.selections.ranges(cx),
12057 ranges,
12058 "Assert selections are {}",
12059 marked_text
12060 );
12061}
12062
12063/// Handle completion request passing a marked string specifying where the completion
12064/// should be triggered from using '|' character, what range should be replaced, and what completions
12065/// should be returned using '<' and '>' to delimit the range
12066pub fn handle_completion_request(
12067 cx: &mut EditorLspTestContext,
12068 marked_string: &str,
12069 completions: Vec<&'static str>,
12070 counter: Arc<AtomicUsize>,
12071) -> impl Future<Output = ()> {
12072 let complete_from_marker: TextRangeMarker = '|'.into();
12073 let replace_range_marker: TextRangeMarker = ('<', '>').into();
12074 let (_, mut marked_ranges) = marked_text_ranges_by(
12075 marked_string,
12076 vec![complete_from_marker.clone(), replace_range_marker.clone()],
12077 );
12078
12079 let complete_from_position =
12080 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12081 let replace_range =
12082 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12083
12084 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12085 let completions = completions.clone();
12086 counter.fetch_add(1, atomic::Ordering::Release);
12087 async move {
12088 assert_eq!(params.text_document_position.text_document.uri, url.clone());
12089 assert_eq!(
12090 params.text_document_position.position,
12091 complete_from_position
12092 );
12093 Ok(Some(lsp::CompletionResponse::Array(
12094 completions
12095 .iter()
12096 .map(|completion_text| lsp::CompletionItem {
12097 label: completion_text.to_string(),
12098 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12099 range: replace_range,
12100 new_text: completion_text.to_string(),
12101 })),
12102 ..Default::default()
12103 })
12104 .collect(),
12105 )))
12106 }
12107 });
12108
12109 async move {
12110 request.next().await;
12111 }
12112}
12113
12114fn handle_resolve_completion_request(
12115 cx: &mut EditorLspTestContext,
12116 edits: Option<Vec<(&'static str, &'static str)>>,
12117) -> impl Future<Output = ()> {
12118 let edits = edits.map(|edits| {
12119 edits
12120 .iter()
12121 .map(|(marked_string, new_text)| {
12122 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12123 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12124 lsp::TextEdit::new(replace_range, new_text.to_string())
12125 })
12126 .collect::<Vec<_>>()
12127 });
12128
12129 let mut request =
12130 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12131 let edits = edits.clone();
12132 async move {
12133 Ok(lsp::CompletionItem {
12134 additional_text_edits: edits,
12135 ..Default::default()
12136 })
12137 }
12138 });
12139
12140 async move {
12141 request.next().await;
12142 }
12143}
12144
12145pub(crate) fn update_test_language_settings(
12146 cx: &mut TestAppContext,
12147 f: impl Fn(&mut AllLanguageSettingsContent),
12148) {
12149 _ = cx.update(|cx| {
12150 SettingsStore::update_global(cx, |store, cx| {
12151 store.update_user_settings::<AllLanguageSettings>(cx, f);
12152 });
12153 });
12154}
12155
12156pub(crate) fn update_test_project_settings(
12157 cx: &mut TestAppContext,
12158 f: impl Fn(&mut ProjectSettings),
12159) {
12160 _ = cx.update(|cx| {
12161 SettingsStore::update_global(cx, |store, cx| {
12162 store.update_user_settings::<ProjectSettings>(cx, f);
12163 });
12164 });
12165}
12166
12167pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12168 _ = cx.update(|cx| {
12169 let store = SettingsStore::test(cx);
12170 cx.set_global(store);
12171 theme::init(theme::LoadThemes::JustBase, cx);
12172 release_channel::init("0.0.0", cx);
12173 client::init_settings(cx);
12174 language::init(cx);
12175 Project::init_settings(cx);
12176 workspace::init_settings(cx);
12177 crate::init(cx);
12178 });
12179
12180 update_test_language_settings(cx, f);
12181}
12182
12183pub(crate) fn rust_lang() -> Arc<Language> {
12184 Arc::new(Language::new(
12185 LanguageConfig {
12186 name: "Rust".into(),
12187 matcher: LanguageMatcher {
12188 path_suffixes: vec!["rs".to_string()],
12189 ..Default::default()
12190 },
12191 ..Default::default()
12192 },
12193 Some(tree_sitter_rust::language()),
12194 ))
12195}
12196
12197#[track_caller]
12198fn assert_hunk_revert(
12199 not_reverted_text_with_selections: &str,
12200 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12201 expected_reverted_text_with_selections: &str,
12202 base_text: &str,
12203 cx: &mut EditorLspTestContext,
12204) {
12205 cx.set_state(not_reverted_text_with_selections);
12206 cx.update_editor(|editor, cx| {
12207 editor
12208 .buffer()
12209 .read(cx)
12210 .as_singleton()
12211 .unwrap()
12212 .update(cx, |buffer, cx| {
12213 buffer.set_diff_base(Some(base_text.into()), cx);
12214 });
12215 });
12216 cx.executor().run_until_parked();
12217
12218 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12219 let snapshot = editor.buffer().read(cx).snapshot(cx);
12220 let reverted_hunk_statuses = snapshot
12221 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12222 .map(|hunk| hunk_status(&hunk))
12223 .collect::<Vec<_>>();
12224
12225 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12226 reverted_hunk_statuses
12227 });
12228 cx.executor().run_until_parked();
12229 cx.assert_editor_state(expected_reverted_text_with_selections);
12230 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12231}