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