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