1use super::*;
2use crate::{
3 test::{
4 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
5 editor_test_context::EditorTestContext, select_ranges,
6 },
7 JoinLines,
8};
9use drag_and_drop::DragAndDrop;
10use futures::StreamExt;
11use gpui::{
12 executor::Deterministic,
13 geometry::{rect::RectF, vector::vec2f},
14 platform::{WindowBounds, WindowOptions},
15 serde_json::{self, json},
16 TestAppContext,
17};
18use indoc::indoc;
19use language::{
20 language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
21 BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
22};
23use parking_lot::Mutex;
24use project::FakeFs;
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::{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.add_model(|cx| {
41 let mut buffer = language::Buffer::new(0, "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 cx.subscribe(&cx.handle(), move |_, _, event, _| {
51 if matches!(
52 event,
53 Event::Edited | Event::BufferEdited | Event::DirtyChanged
54 ) {
55 events.borrow_mut().push(("editor1", event.clone()));
56 }
57 })
58 .detach();
59 Editor::for_buffer(buffer.clone(), None, cx)
60 }
61 });
62 let (_, editor2) = cx.add_window({
63 let events = events.clone();
64 |cx| {
65 cx.subscribe(&cx.handle(), move |_, _, event, _| {
66 if matches!(
67 event,
68 Event::Edited | Event::BufferEdited | Event::DirtyChanged
69 ) {
70 events.borrow_mut().push(("editor2", event.clone()));
71 }
72 })
73 .detach();
74 Editor::for_buffer(buffer.clone(), None, cx)
75 }
76 });
77 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
78
79 // Mutating editor 1 will emit an `Edited` event only for that editor.
80 editor1.update(cx, |editor, cx| editor.insert("X", cx));
81 assert_eq!(
82 mem::take(&mut *events.borrow_mut()),
83 [
84 ("editor1", Event::Edited),
85 ("editor1", Event::BufferEdited),
86 ("editor2", Event::BufferEdited),
87 ("editor1", Event::DirtyChanged),
88 ("editor2", Event::DirtyChanged)
89 ]
90 );
91
92 // Mutating editor 2 will emit an `Edited` event only for that editor.
93 editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor2", Event::Edited),
98 ("editor1", Event::BufferEdited),
99 ("editor2", Event::BufferEdited),
100 ]
101 );
102
103 // Undoing on editor 1 will emit an `Edited` event only for that editor.
104 editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor1", Event::Edited),
109 ("editor1", Event::BufferEdited),
110 ("editor2", Event::BufferEdited),
111 ("editor1", Event::DirtyChanged),
112 ("editor2", Event::DirtyChanged),
113 ]
114 );
115
116 // Redoing on editor 1 will emit an `Edited` event only for that editor.
117 editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
118 assert_eq!(
119 mem::take(&mut *events.borrow_mut()),
120 [
121 ("editor1", Event::Edited),
122 ("editor1", Event::BufferEdited),
123 ("editor2", Event::BufferEdited),
124 ("editor1", Event::DirtyChanged),
125 ("editor2", Event::DirtyChanged),
126 ]
127 );
128
129 // Undoing on editor 2 will emit an `Edited` event only for that editor.
130 editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
131 assert_eq!(
132 mem::take(&mut *events.borrow_mut()),
133 [
134 ("editor2", Event::Edited),
135 ("editor1", Event::BufferEdited),
136 ("editor2", Event::BufferEdited),
137 ("editor1", Event::DirtyChanged),
138 ("editor2", Event::DirtyChanged),
139 ]
140 );
141
142 // Redoing on editor 2 will emit an `Edited` event only for that editor.
143 editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
144 assert_eq!(
145 mem::take(&mut *events.borrow_mut()),
146 [
147 ("editor2", Event::Edited),
148 ("editor1", Event::BufferEdited),
149 ("editor2", Event::BufferEdited),
150 ("editor1", Event::DirtyChanged),
151 ("editor2", Event::DirtyChanged),
152 ]
153 );
154
155 // No event is emitted when the mutation is a no-op.
156 editor2.update(cx, |editor, cx| {
157 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
158
159 editor.backspace(&Backspace, cx);
160 });
161 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
162}
163
164#[gpui::test]
165fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
166 init_test(cx, |_| {});
167
168 let mut now = Instant::now();
169 let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
170 let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
171 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
172 let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
173
174 editor.update(cx, |editor, cx| {
175 editor.start_transaction_at(now, cx);
176 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
177
178 editor.insert("cd", cx);
179 editor.end_transaction_at(now, cx);
180 assert_eq!(editor.text(cx), "12cd56");
181 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
182
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
185 editor.insert("e", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cde6");
188 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
189
190 now += group_interval + Duration::from_millis(1);
191 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
192
193 // Simulate an edit in another editor
194 buffer.update(cx, |buffer, cx| {
195 buffer.start_transaction_at(now, cx);
196 buffer.edit([(0..1, "a")], None, cx);
197 buffer.edit([(1..1, "b")], None, cx);
198 buffer.end_transaction_at(now, cx);
199 });
200
201 assert_eq!(editor.text(cx), "ab2cde6");
202 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
203
204 // Last transaction happened past the group interval in a different editor.
205 // Undo it individually and don't restore selections.
206 editor.undo(&Undo, cx);
207 assert_eq!(editor.text(cx), "12cde6");
208 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
209
210 // First two transactions happened within the group interval in this editor.
211 // Undo them together and restore selections.
212 editor.undo(&Undo, cx);
213 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
214 assert_eq!(editor.text(cx), "123456");
215 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
216
217 // Redo the first two transactions together.
218 editor.redo(&Redo, cx);
219 assert_eq!(editor.text(cx), "12cde6");
220 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
221
222 // Redo the last transaction on its own.
223 editor.redo(&Redo, cx);
224 assert_eq!(editor.text(cx), "ab2cde6");
225 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
226
227 // Test empty transactions.
228 editor.start_transaction_at(now, cx);
229 editor.end_transaction_at(now, cx);
230 editor.undo(&Undo, cx);
231 assert_eq!(editor.text(cx), "12cde6");
232 });
233}
234
235#[gpui::test]
236fn test_ime_composition(cx: &mut TestAppContext) {
237 init_test(cx, |_| {});
238
239 let buffer = cx.add_model(|cx| {
240 let mut buffer = language::Buffer::new(0, "abcde", cx);
241 // Ensure automatic grouping doesn't occur.
242 buffer.set_group_interval(Duration::ZERO);
243 buffer
244 });
245
246 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
247 cx.add_window(|cx| {
248 let mut editor = build_editor(buffer.clone(), cx);
249
250 // Start a new IME composition.
251 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
252 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
253 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
254 assert_eq!(editor.text(cx), "äbcde");
255 assert_eq!(
256 editor.marked_text_ranges(cx),
257 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
258 );
259
260 // Finalize IME composition.
261 editor.replace_text_in_range(None, "ā", cx);
262 assert_eq!(editor.text(cx), "ābcde");
263 assert_eq!(editor.marked_text_ranges(cx), None);
264
265 // IME composition edits are grouped and are undone/redone at once.
266 editor.undo(&Default::default(), cx);
267 assert_eq!(editor.text(cx), "abcde");
268 assert_eq!(editor.marked_text_ranges(cx), None);
269 editor.redo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // Start a new IME composition.
274 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
275 assert_eq!(
276 editor.marked_text_ranges(cx),
277 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
278 );
279
280 // Undoing during an IME composition cancels it.
281 editor.undo(&Default::default(), cx);
282 assert_eq!(editor.text(cx), "ābcde");
283 assert_eq!(editor.marked_text_ranges(cx), None);
284
285 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
286 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
287 assert_eq!(editor.text(cx), "ābcdè");
288 assert_eq!(
289 editor.marked_text_ranges(cx),
290 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
291 );
292
293 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
294 editor.replace_text_in_range(Some(4..999), "ę", cx);
295 assert_eq!(editor.text(cx), "ābcdę");
296 assert_eq!(editor.marked_text_ranges(cx), None);
297
298 // Start a new IME composition with multiple cursors.
299 editor.change_selections(None, cx, |s| {
300 s.select_ranges([
301 OffsetUtf16(1)..OffsetUtf16(1),
302 OffsetUtf16(3)..OffsetUtf16(3),
303 OffsetUtf16(5)..OffsetUtf16(5),
304 ])
305 });
306 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
307 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
308 assert_eq!(
309 editor.marked_text_ranges(cx),
310 Some(vec![
311 OffsetUtf16(0)..OffsetUtf16(3),
312 OffsetUtf16(4)..OffsetUtf16(7),
313 OffsetUtf16(8)..OffsetUtf16(11)
314 ])
315 );
316
317 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
318 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
319 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
320 assert_eq!(
321 editor.marked_text_ranges(cx),
322 Some(vec![
323 OffsetUtf16(1)..OffsetUtf16(2),
324 OffsetUtf16(5)..OffsetUtf16(6),
325 OffsetUtf16(9)..OffsetUtf16(10)
326 ])
327 );
328
329 // Finalize IME composition with multiple cursors.
330 editor.replace_text_in_range(Some(9..10), "2", cx);
331 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
332 assert_eq!(editor.marked_text_ranges(cx), None);
333
334 editor
335 });
336}
337
338#[gpui::test]
339fn test_selection_with_mouse(cx: &mut TestAppContext) {
340 init_test(cx, |_| {});
341
342 let (_, editor) = cx.add_window(|cx| {
343 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
344 build_editor(buffer, cx)
345 });
346 editor.update(cx, |view, cx| {
347 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
348 });
349 assert_eq!(
350 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
351 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
352 );
353
354 editor.update(cx, |view, cx| {
355 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
356 });
357
358 assert_eq!(
359 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
360 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
361 );
362
363 editor.update(cx, |view, cx| {
364 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
365 });
366
367 assert_eq!(
368 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
369 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
370 );
371
372 editor.update(cx, |view, cx| {
373 view.end_selection(cx);
374 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
375 });
376
377 assert_eq!(
378 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
379 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
380 );
381
382 editor.update(cx, |view, cx| {
383 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
384 view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
385 });
386
387 assert_eq!(
388 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
389 [
390 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
391 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
392 ]
393 );
394
395 editor.update(cx, |view, cx| {
396 view.end_selection(cx);
397 });
398
399 assert_eq!(
400 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
401 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
402 );
403}
404
405#[gpui::test]
406fn test_canceling_pending_selection(cx: &mut TestAppContext) {
407 init_test(cx, |_| {});
408
409 let (_, view) = cx.add_window(|cx| {
410 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
411 build_editor(buffer, cx)
412 });
413
414 view.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
416 assert_eq!(
417 view.selections.display_ranges(cx),
418 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
419 );
420 });
421
422 view.update(cx, |view, cx| {
423 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
424 assert_eq!(
425 view.selections.display_ranges(cx),
426 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
427 );
428 });
429
430 view.update(cx, |view, cx| {
431 view.cancel(&Cancel, cx);
432 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
433 assert_eq!(
434 view.selections.display_ranges(cx),
435 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
436 );
437 });
438}
439
440#[gpui::test]
441fn test_clone(cx: &mut TestAppContext) {
442 init_test(cx, |_| {});
443
444 let (text, selection_ranges) = marked_text_ranges(
445 indoc! {"
446 one
447 two
448 threeˇ
449 four
450 fiveˇ
451 "},
452 true,
453 );
454
455 let (_, editor) = cx.add_window(|cx| {
456 let buffer = MultiBuffer::build_simple(&text, cx);
457 build_editor(buffer, cx)
458 });
459
460 editor.update(cx, |editor, cx| {
461 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
462 editor.fold_ranges(
463 [
464 Point::new(1, 0)..Point::new(2, 0),
465 Point::new(3, 0)..Point::new(4, 0),
466 ],
467 true,
468 cx,
469 );
470 });
471
472 let (_, cloned_editor) = editor.update(cx, |editor, cx| {
473 cx.add_window(Default::default(), |cx| editor.clone(cx))
474 });
475
476 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
477 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
478
479 assert_eq!(
480 cloned_editor.update(cx, |e, cx| e.display_text(cx)),
481 editor.update(cx, |e, cx| e.display_text(cx))
482 );
483 assert_eq!(
484 cloned_snapshot
485 .folds_in_range(0..text.len())
486 .collect::<Vec<_>>(),
487 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
488 );
489 assert_set_eq!(
490 cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
491 editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
492 );
493 assert_set_eq!(
494 cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
495 editor.update(cx, |e, cx| e.selections.display_ranges(cx))
496 );
497}
498
499#[gpui::test]
500async fn test_navigation_history(cx: &mut TestAppContext) {
501 init_test(cx, |_| {});
502
503 cx.set_global(DragAndDrop::<Workspace>::default());
504 use workspace::item::Item;
505
506 let fs = FakeFs::new(cx.background());
507 let project = Project::test(fs, [], cx).await;
508 let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
509 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
510 cx.add_view(window_id, |cx| {
511 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
512 let mut editor = build_editor(buffer.clone(), cx);
513 let handle = cx.handle();
514 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
515
516 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
517 editor.nav_history.as_mut().unwrap().pop_backward(cx)
518 }
519
520 // Move the cursor a small distance.
521 // Nothing is added to the navigation history.
522 editor.change_selections(None, cx, |s| {
523 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
524 });
525 editor.change_selections(None, cx, |s| {
526 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
527 });
528 assert!(pop_history(&mut editor, cx).is_none());
529
530 // Move the cursor a large distance.
531 // The history can jump back to the previous position.
532 editor.change_selections(None, cx, |s| {
533 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
534 });
535 let nav_entry = pop_history(&mut editor, cx).unwrap();
536 editor.navigate(nav_entry.data.unwrap(), cx);
537 assert_eq!(nav_entry.item.id(), cx.view_id());
538 assert_eq!(
539 editor.selections.display_ranges(cx),
540 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
541 );
542 assert!(pop_history(&mut editor, cx).is_none());
543
544 // Move the cursor a small distance via the mouse.
545 // Nothing is added to the navigation history.
546 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
547 editor.end_selection(cx);
548 assert_eq!(
549 editor.selections.display_ranges(cx),
550 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
551 );
552 assert!(pop_history(&mut editor, cx).is_none());
553
554 // Move the cursor a large distance via the mouse.
555 // The history can jump back to the previous position.
556 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
557 editor.end_selection(cx);
558 assert_eq!(
559 editor.selections.display_ranges(cx),
560 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
561 );
562 let nav_entry = pop_history(&mut editor, cx).unwrap();
563 editor.navigate(nav_entry.data.unwrap(), cx);
564 assert_eq!(nav_entry.item.id(), cx.view_id());
565 assert_eq!(
566 editor.selections.display_ranges(cx),
567 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
568 );
569 assert!(pop_history(&mut editor, cx).is_none());
570
571 // Set scroll position to check later
572 editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
573 let original_scroll_position = editor.scroll_manager.anchor();
574
575 // Jump to the end of the document and adjust scroll
576 editor.move_to_end(&MoveToEnd, cx);
577 editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
578 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
579
580 let nav_entry = pop_history(&mut editor, cx).unwrap();
581 editor.navigate(nav_entry.data.unwrap(), cx);
582 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
583
584 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
585 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
586 invalid_anchor.text_anchor.buffer_id = Some(999);
587 let invalid_point = Point::new(9999, 0);
588 editor.navigate(
589 Box::new(NavigationData {
590 cursor_anchor: invalid_anchor,
591 cursor_position: invalid_point,
592 scroll_anchor: ScrollAnchor {
593 anchor: invalid_anchor,
594 offset: Default::default(),
595 },
596 scroll_top_row: invalid_point.row,
597 }),
598 cx,
599 );
600 assert_eq!(
601 editor.selections.display_ranges(cx),
602 &[editor.max_point(cx)..editor.max_point(cx)]
603 );
604 assert_eq!(
605 editor.scroll_position(cx),
606 vec2f(0., editor.max_point(cx).row() as f32)
607 );
608
609 editor
610 });
611}
612
613#[gpui::test]
614fn test_cancel(cx: &mut TestAppContext) {
615 init_test(cx, |_| {});
616
617 let (_, view) = cx.add_window(|cx| {
618 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
619 build_editor(buffer, cx)
620 });
621
622 view.update(cx, |view, cx| {
623 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
624 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
625 view.end_selection(cx);
626
627 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
628 view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
629 view.end_selection(cx);
630 assert_eq!(
631 view.selections.display_ranges(cx),
632 [
633 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
634 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
635 ]
636 );
637 });
638
639 view.update(cx, |view, cx| {
640 view.cancel(&Cancel, cx);
641 assert_eq!(
642 view.selections.display_ranges(cx),
643 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
644 );
645 });
646
647 view.update(cx, |view, cx| {
648 view.cancel(&Cancel, cx);
649 assert_eq!(
650 view.selections.display_ranges(cx),
651 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
652 );
653 });
654}
655
656#[gpui::test]
657fn test_fold_action(cx: &mut TestAppContext) {
658 init_test(cx, |_| {});
659
660 let (_, view) = cx.add_window(|cx| {
661 let buffer = MultiBuffer::build_simple(
662 &"
663 impl Foo {
664 // Hello!
665
666 fn a() {
667 1
668 }
669
670 fn b() {
671 2
672 }
673
674 fn c() {
675 3
676 }
677 }
678 "
679 .unindent(),
680 cx,
681 );
682 build_editor(buffer.clone(), cx)
683 });
684
685 view.update(cx, |view, cx| {
686 view.change_selections(None, cx, |s| {
687 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
688 });
689 view.fold(&Fold, cx);
690 assert_eq!(
691 view.display_text(cx),
692 "
693 impl Foo {
694 // Hello!
695
696 fn a() {
697 1
698 }
699
700 fn b() {⋯
701 }
702
703 fn c() {⋯
704 }
705 }
706 "
707 .unindent(),
708 );
709
710 view.fold(&Fold, cx);
711 assert_eq!(
712 view.display_text(cx),
713 "
714 impl Foo {⋯
715 }
716 "
717 .unindent(),
718 );
719
720 view.unfold_lines(&UnfoldLines, cx);
721 assert_eq!(
722 view.display_text(cx),
723 "
724 impl Foo {
725 // Hello!
726
727 fn a() {
728 1
729 }
730
731 fn b() {⋯
732 }
733
734 fn c() {⋯
735 }
736 }
737 "
738 .unindent(),
739 );
740
741 view.unfold_lines(&UnfoldLines, cx);
742 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
743 });
744}
745
746#[gpui::test]
747fn test_move_cursor(cx: &mut TestAppContext) {
748 init_test(cx, |_| {});
749
750 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
751 let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
752
753 buffer.update(cx, |buffer, cx| {
754 buffer.edit(
755 vec![
756 (Point::new(1, 0)..Point::new(1, 0), "\t"),
757 (Point::new(1, 1)..Point::new(1, 1), "\t"),
758 ],
759 None,
760 cx,
761 );
762 });
763 view.update(cx, |view, cx| {
764 assert_eq!(
765 view.selections.display_ranges(cx),
766 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
767 );
768
769 view.move_down(&MoveDown, cx);
770 assert_eq!(
771 view.selections.display_ranges(cx),
772 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
773 );
774
775 view.move_right(&MoveRight, cx);
776 assert_eq!(
777 view.selections.display_ranges(cx),
778 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
779 );
780
781 view.move_left(&MoveLeft, cx);
782 assert_eq!(
783 view.selections.display_ranges(cx),
784 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
785 );
786
787 view.move_up(&MoveUp, cx);
788 assert_eq!(
789 view.selections.display_ranges(cx),
790 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
791 );
792
793 view.move_to_end(&MoveToEnd, cx);
794 assert_eq!(
795 view.selections.display_ranges(cx),
796 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
797 );
798
799 view.move_to_beginning(&MoveToBeginning, cx);
800 assert_eq!(
801 view.selections.display_ranges(cx),
802 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
803 );
804
805 view.change_selections(None, cx, |s| {
806 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
807 });
808 view.select_to_beginning(&SelectToBeginning, cx);
809 assert_eq!(
810 view.selections.display_ranges(cx),
811 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
812 );
813
814 view.select_to_end(&SelectToEnd, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let (_, view) = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
828 build_editor(buffer.clone(), cx)
829 });
830
831 assert_eq!('ⓐ'.len_utf8(), 3);
832 assert_eq!('α'.len_utf8(), 2);
833
834 view.update(cx, |view, cx| {
835 view.fold_ranges(
836 vec![
837 Point::new(0, 6)..Point::new(0, 12),
838 Point::new(1, 2)..Point::new(1, 4),
839 Point::new(2, 4)..Point::new(2, 8),
840 ],
841 true,
842 cx,
843 );
844 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε\n");
845
846 view.move_right(&MoveRight, cx);
847 assert_eq!(
848 view.selections.display_ranges(cx),
849 &[empty_range(0, "ⓐ".len())]
850 );
851 view.move_right(&MoveRight, cx);
852 assert_eq!(
853 view.selections.display_ranges(cx),
854 &[empty_range(0, "ⓐⓑ".len())]
855 );
856 view.move_right(&MoveRight, cx);
857 assert_eq!(
858 view.selections.display_ranges(cx),
859 &[empty_range(0, "ⓐⓑ⋯".len())]
860 );
861
862 view.move_down(&MoveDown, cx);
863 assert_eq!(
864 view.selections.display_ranges(cx),
865 &[empty_range(1, "ab⋯".len())]
866 );
867 view.move_left(&MoveLeft, cx);
868 assert_eq!(
869 view.selections.display_ranges(cx),
870 &[empty_range(1, "ab".len())]
871 );
872 view.move_left(&MoveLeft, cx);
873 assert_eq!(
874 view.selections.display_ranges(cx),
875 &[empty_range(1, "a".len())]
876 );
877
878 view.move_down(&MoveDown, cx);
879 assert_eq!(
880 view.selections.display_ranges(cx),
881 &[empty_range(2, "α".len())]
882 );
883 view.move_right(&MoveRight, cx);
884 assert_eq!(
885 view.selections.display_ranges(cx),
886 &[empty_range(2, "αβ".len())]
887 );
888 view.move_right(&MoveRight, cx);
889 assert_eq!(
890 view.selections.display_ranges(cx),
891 &[empty_range(2, "αβ⋯".len())]
892 );
893 view.move_right(&MoveRight, cx);
894 assert_eq!(
895 view.selections.display_ranges(cx),
896 &[empty_range(2, "αβ⋯ε".len())]
897 );
898
899 view.move_up(&MoveUp, cx);
900 assert_eq!(
901 view.selections.display_ranges(cx),
902 &[empty_range(1, "ab⋯e".len())]
903 );
904 view.move_up(&MoveUp, cx);
905 assert_eq!(
906 view.selections.display_ranges(cx),
907 &[empty_range(0, "ⓐⓑ⋯ⓔ".len())]
908 );
909 view.move_left(&MoveLeft, cx);
910 assert_eq!(
911 view.selections.display_ranges(cx),
912 &[empty_range(0, "ⓐⓑ⋯".len())]
913 );
914 view.move_left(&MoveLeft, cx);
915 assert_eq!(
916 view.selections.display_ranges(cx),
917 &[empty_range(0, "ⓐⓑ".len())]
918 );
919 view.move_left(&MoveLeft, cx);
920 assert_eq!(
921 view.selections.display_ranges(cx),
922 &[empty_range(0, "ⓐ".len())]
923 );
924 });
925}
926
927#[gpui::test]
928fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
929 init_test(cx, |_| {});
930
931 let (_, view) = cx.add_window(|cx| {
932 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
933 build_editor(buffer.clone(), cx)
934 });
935 view.update(cx, |view, cx| {
936 view.change_selections(None, cx, |s| {
937 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
938 });
939 view.move_down(&MoveDown, cx);
940 assert_eq!(
941 view.selections.display_ranges(cx),
942 &[empty_range(1, "abcd".len())]
943 );
944
945 view.move_down(&MoveDown, cx);
946 assert_eq!(
947 view.selections.display_ranges(cx),
948 &[empty_range(2, "αβγ".len())]
949 );
950
951 view.move_down(&MoveDown, cx);
952 assert_eq!(
953 view.selections.display_ranges(cx),
954 &[empty_range(3, "abcd".len())]
955 );
956
957 view.move_down(&MoveDown, cx);
958 assert_eq!(
959 view.selections.display_ranges(cx),
960 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
961 );
962
963 view.move_up(&MoveUp, cx);
964 assert_eq!(
965 view.selections.display_ranges(cx),
966 &[empty_range(3, "abcd".len())]
967 );
968
969 view.move_up(&MoveUp, cx);
970 assert_eq!(
971 view.selections.display_ranges(cx),
972 &[empty_range(2, "αβγ".len())]
973 );
974 });
975}
976
977#[gpui::test]
978fn test_beginning_end_of_line(cx: &mut TestAppContext) {
979 init_test(cx, |_| {});
980
981 let (_, view) = cx.add_window(|cx| {
982 let buffer = MultiBuffer::build_simple("abc\n def", cx);
983 build_editor(buffer, cx)
984 });
985 view.update(cx, |view, cx| {
986 view.change_selections(None, cx, |s| {
987 s.select_display_ranges([
988 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
989 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
990 ]);
991 });
992 });
993
994 view.update(cx, |view, cx| {
995 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
996 assert_eq!(
997 view.selections.display_ranges(cx),
998 &[
999 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1000 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1001 ]
1002 );
1003 });
1004
1005 view.update(cx, |view, cx| {
1006 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1007 assert_eq!(
1008 view.selections.display_ranges(cx),
1009 &[
1010 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1011 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1012 ]
1013 );
1014 });
1015
1016 view.update(cx, |view, cx| {
1017 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1018 assert_eq!(
1019 view.selections.display_ranges(cx),
1020 &[
1021 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1022 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1023 ]
1024 );
1025 });
1026
1027 view.update(cx, |view, cx| {
1028 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1029 assert_eq!(
1030 view.selections.display_ranges(cx),
1031 &[
1032 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1033 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1034 ]
1035 );
1036 });
1037
1038 // Moving to the end of line again is a no-op.
1039 view.update(cx, |view, cx| {
1040 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1041 assert_eq!(
1042 view.selections.display_ranges(cx),
1043 &[
1044 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1045 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1046 ]
1047 );
1048 });
1049
1050 view.update(cx, |view, cx| {
1051 view.move_left(&MoveLeft, cx);
1052 view.select_to_beginning_of_line(
1053 &SelectToBeginningOfLine {
1054 stop_at_soft_wraps: true,
1055 },
1056 cx,
1057 );
1058 assert_eq!(
1059 view.selections.display_ranges(cx),
1060 &[
1061 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1062 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1063 ]
1064 );
1065 });
1066
1067 view.update(cx, |view, cx| {
1068 view.select_to_beginning_of_line(
1069 &SelectToBeginningOfLine {
1070 stop_at_soft_wraps: true,
1071 },
1072 cx,
1073 );
1074 assert_eq!(
1075 view.selections.display_ranges(cx),
1076 &[
1077 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1078 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1079 ]
1080 );
1081 });
1082
1083 view.update(cx, |view, cx| {
1084 view.select_to_beginning_of_line(
1085 &SelectToBeginningOfLine {
1086 stop_at_soft_wraps: true,
1087 },
1088 cx,
1089 );
1090 assert_eq!(
1091 view.selections.display_ranges(cx),
1092 &[
1093 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1094 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1095 ]
1096 );
1097 });
1098
1099 view.update(cx, |view, cx| {
1100 view.select_to_end_of_line(
1101 &SelectToEndOfLine {
1102 stop_at_soft_wraps: true,
1103 },
1104 cx,
1105 );
1106 assert_eq!(
1107 view.selections.display_ranges(cx),
1108 &[
1109 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1110 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1111 ]
1112 );
1113 });
1114
1115 view.update(cx, |view, cx| {
1116 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1117 assert_eq!(view.display_text(cx), "ab\n de");
1118 assert_eq!(
1119 view.selections.display_ranges(cx),
1120 &[
1121 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1122 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1123 ]
1124 );
1125 });
1126
1127 view.update(cx, |view, cx| {
1128 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1129 assert_eq!(view.display_text(cx), "\n");
1130 assert_eq!(
1131 view.selections.display_ranges(cx),
1132 &[
1133 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1134 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1135 ]
1136 );
1137 });
1138}
1139
1140#[gpui::test]
1141fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1142 init_test(cx, |_| {});
1143
1144 let (_, view) = cx.add_window(|cx| {
1145 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1146 build_editor(buffer, cx)
1147 });
1148 view.update(cx, |view, cx| {
1149 view.change_selections(None, cx, |s| {
1150 s.select_display_ranges([
1151 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1152 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1153 ])
1154 });
1155
1156 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1157 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1158
1159 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1160 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1161
1162 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1163 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1164
1165 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1166 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1167
1168 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1169 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1170
1171 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1172 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1173
1174 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1175 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1176
1177 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1178 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1179
1180 view.move_right(&MoveRight, cx);
1181 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1182 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1183
1184 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1185 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1186
1187 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1188 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1189 });
1190}
1191
1192#[gpui::test]
1193fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1194 init_test(cx, |_| {});
1195
1196 let (_, view) = cx.add_window(|cx| {
1197 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1198 build_editor(buffer, cx)
1199 });
1200
1201 view.update(cx, |view, cx| {
1202 view.set_wrap_width(Some(140.), cx);
1203 assert_eq!(
1204 view.display_text(cx),
1205 "use one::{\n two::three::\n four::five\n};"
1206 );
1207
1208 view.change_selections(None, cx, |s| {
1209 s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1210 });
1211
1212 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1213 assert_eq!(
1214 view.selections.display_ranges(cx),
1215 &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1216 );
1217
1218 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1222 );
1223
1224 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1228 );
1229
1230 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1234 );
1235
1236 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1237 assert_eq!(
1238 view.selections.display_ranges(cx),
1239 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1240 );
1241
1242 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1243 assert_eq!(
1244 view.selections.display_ranges(cx),
1245 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1246 );
1247 });
1248}
1249
1250#[gpui::test]
1251async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1252 init_test(cx, |_| {});
1253 let mut cx = EditorTestContext::new(cx).await;
1254
1255 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1256 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1257
1258 cx.set_state(
1259 &r#"ˇone
1260 two
1261
1262 three
1263 fourˇ
1264 five
1265
1266 six"#
1267 .unindent(),
1268 );
1269
1270 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1271 cx.assert_editor_state(
1272 &r#"one
1273 two
1274 ˇ
1275 three
1276 four
1277 five
1278 ˇ
1279 six"#
1280 .unindent(),
1281 );
1282
1283 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1284 cx.assert_editor_state(
1285 &r#"one
1286 two
1287
1288 three
1289 four
1290 five
1291 ˇ
1292 sixˇ"#
1293 .unindent(),
1294 );
1295
1296 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1297 cx.assert_editor_state(
1298 &r#"ˇone
1299 two
1300
1301 three
1302 four
1303 five
1304
1305 sixˇ"#
1306 .unindent(),
1307 );
1308
1309 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1310 cx.assert_editor_state(
1311 &r#"ˇone
1312 two
1313 ˇ
1314 three
1315 four
1316 five
1317
1318 six"#
1319 .unindent(),
1320 );
1321
1322 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1323 cx.assert_editor_state(
1324 &r#"ˇone
1325 two
1326
1327 three
1328 four
1329 five
1330
1331 sixˇ"#
1332 .unindent(),
1333 );
1334
1335 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1336 cx.assert_editor_state(
1337 &r#"one
1338 two
1339
1340 three
1341 four
1342 five
1343 ˇ
1344 sixˇ"#
1345 .unindent(),
1346 );
1347
1348 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1349 cx.assert_editor_state(
1350 &r#"one
1351 two
1352 ˇ
1353 three
1354 four
1355 five
1356 ˇ
1357 six"#
1358 .unindent(),
1359 );
1360}
1361
1362#[gpui::test]
1363async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1364 init_test(cx, |_| {});
1365 let mut cx = EditorTestContext::new(cx).await;
1366
1367 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1368 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1369
1370 cx.set_state(
1371 &r#"
1372 ˇone
1373 two
1374 threeˇ
1375 four
1376 five
1377 six
1378 seven
1379 eight
1380 nine
1381 ten
1382 "#
1383 .unindent(),
1384 );
1385
1386 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1387 cx.assert_editor_state(
1388 &r#"
1389 one
1390 two
1391 three
1392 ˇfour
1393 five
1394 sixˇ
1395 seven
1396 eight
1397 nine
1398 ten
1399 "#
1400 .unindent(),
1401 );
1402
1403 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1404 cx.assert_editor_state(
1405 &r#"
1406 one
1407 two
1408 three
1409 four
1410 five
1411 six
1412 ˇseven
1413 eight
1414 nineˇ
1415 ten
1416 "#
1417 .unindent(),
1418 );
1419
1420 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1421 cx.assert_editor_state(
1422 &r#"
1423 one
1424 two
1425 three
1426 ˇfour
1427 five
1428 sixˇ
1429 seven
1430 eight
1431 nine
1432 ten
1433 "#
1434 .unindent(),
1435 );
1436
1437 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1438 cx.assert_editor_state(
1439 &r#"
1440 ˇone
1441 two
1442 threeˇ
1443 four
1444 five
1445 six
1446 seven
1447 eight
1448 nine
1449 ten
1450 "#
1451 .unindent(),
1452 );
1453
1454 // Test select collapsing
1455 cx.update_editor(|editor, cx| {
1456 editor.move_page_down(&MovePageDown::default(), cx);
1457 editor.move_page_down(&MovePageDown::default(), cx);
1458 editor.move_page_down(&MovePageDown::default(), cx);
1459 });
1460 cx.assert_editor_state(
1461 &r#"
1462 one
1463 two
1464 three
1465 four
1466 five
1467 six
1468 seven
1469 eight
1470 nine
1471 ˇten
1472 ˇ"#
1473 .unindent(),
1474 );
1475}
1476
1477#[gpui::test]
1478async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1479 init_test(cx, |_| {});
1480 let mut cx = EditorTestContext::new(cx).await;
1481 cx.set_state("one «two threeˇ» four");
1482 cx.update_editor(|editor, cx| {
1483 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1484 assert_eq!(editor.text(cx), " four");
1485 });
1486}
1487
1488#[gpui::test]
1489fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1490 init_test(cx, |_| {});
1491
1492 let (_, view) = cx.add_window(|cx| {
1493 let buffer = MultiBuffer::build_simple("one two three four", cx);
1494 build_editor(buffer.clone(), cx)
1495 });
1496
1497 view.update(cx, |view, cx| {
1498 view.change_selections(None, cx, |s| {
1499 s.select_display_ranges([
1500 // an empty selection - the preceding word fragment is deleted
1501 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1502 // characters selected - they are deleted
1503 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1504 ])
1505 });
1506 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1507 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1508 });
1509
1510 view.update(cx, |view, cx| {
1511 view.change_selections(None, cx, |s| {
1512 s.select_display_ranges([
1513 // an empty selection - the following word fragment is deleted
1514 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1515 // characters selected - they are deleted
1516 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1517 ])
1518 });
1519 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1520 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1521 });
1522}
1523
1524#[gpui::test]
1525fn test_newline(cx: &mut TestAppContext) {
1526 init_test(cx, |_| {});
1527
1528 let (_, view) = cx.add_window(|cx| {
1529 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1530 build_editor(buffer.clone(), cx)
1531 });
1532
1533 view.update(cx, |view, cx| {
1534 view.change_selections(None, cx, |s| {
1535 s.select_display_ranges([
1536 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1537 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1538 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1539 ])
1540 });
1541
1542 view.newline(&Newline, cx);
1543 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1544 });
1545}
1546
1547#[gpui::test]
1548fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1549 init_test(cx, |_| {});
1550
1551 let (_, editor) = cx.add_window(|cx| {
1552 let buffer = MultiBuffer::build_simple(
1553 "
1554 a
1555 b(
1556 X
1557 )
1558 c(
1559 X
1560 )
1561 "
1562 .unindent()
1563 .as_str(),
1564 cx,
1565 );
1566 let mut editor = build_editor(buffer.clone(), cx);
1567 editor.change_selections(None, cx, |s| {
1568 s.select_ranges([
1569 Point::new(2, 4)..Point::new(2, 5),
1570 Point::new(5, 4)..Point::new(5, 5),
1571 ])
1572 });
1573 editor
1574 });
1575
1576 editor.update(cx, |editor, cx| {
1577 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1578 editor.buffer.update(cx, |buffer, cx| {
1579 buffer.edit(
1580 [
1581 (Point::new(1, 2)..Point::new(3, 0), ""),
1582 (Point::new(4, 2)..Point::new(6, 0), ""),
1583 ],
1584 None,
1585 cx,
1586 );
1587 assert_eq!(
1588 buffer.read(cx).text(),
1589 "
1590 a
1591 b()
1592 c()
1593 "
1594 .unindent()
1595 );
1596 });
1597 assert_eq!(
1598 editor.selections.ranges(cx),
1599 &[
1600 Point::new(1, 2)..Point::new(1, 2),
1601 Point::new(2, 2)..Point::new(2, 2),
1602 ],
1603 );
1604
1605 editor.newline(&Newline, cx);
1606 assert_eq!(
1607 editor.text(cx),
1608 "
1609 a
1610 b(
1611 )
1612 c(
1613 )
1614 "
1615 .unindent()
1616 );
1617
1618 // The selections are moved after the inserted newlines
1619 assert_eq!(
1620 editor.selections.ranges(cx),
1621 &[
1622 Point::new(2, 0)..Point::new(2, 0),
1623 Point::new(4, 0)..Point::new(4, 0),
1624 ],
1625 );
1626 });
1627}
1628
1629#[gpui::test]
1630async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1631 init_test(cx, |settings| {
1632 settings.defaults.tab_size = NonZeroU32::new(4)
1633 });
1634
1635 let language = Arc::new(
1636 Language::new(
1637 LanguageConfig::default(),
1638 Some(tree_sitter_rust::language()),
1639 )
1640 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1641 .unwrap(),
1642 );
1643
1644 let mut cx = EditorTestContext::new(cx).await;
1645 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1646 cx.set_state(indoc! {"
1647 const a: ˇA = (
1648 (ˇ
1649 «const_functionˇ»(ˇ),
1650 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1651 )ˇ
1652 ˇ);ˇ
1653 "});
1654
1655 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1656 cx.assert_editor_state(indoc! {"
1657 ˇ
1658 const a: A = (
1659 ˇ
1660 (
1661 ˇ
1662 ˇ
1663 const_function(),
1664 ˇ
1665 ˇ
1666 ˇ
1667 ˇ
1668 something_else,
1669 ˇ
1670 )
1671 ˇ
1672 ˇ
1673 );
1674 "});
1675}
1676
1677#[gpui::test]
1678async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1679 init_test(cx, |settings| {
1680 settings.defaults.tab_size = NonZeroU32::new(4)
1681 });
1682
1683 let language = Arc::new(
1684 Language::new(
1685 LanguageConfig::default(),
1686 Some(tree_sitter_rust::language()),
1687 )
1688 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1689 .unwrap(),
1690 );
1691
1692 let mut cx = EditorTestContext::new(cx).await;
1693 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1694 cx.set_state(indoc! {"
1695 const a: ˇA = (
1696 (ˇ
1697 «const_functionˇ»(ˇ),
1698 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1699 )ˇ
1700 ˇ);ˇ
1701 "});
1702
1703 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1704 cx.assert_editor_state(indoc! {"
1705 const a: A = (
1706 ˇ
1707 (
1708 ˇ
1709 const_function(),
1710 ˇ
1711 ˇ
1712 something_else,
1713 ˇ
1714 ˇ
1715 ˇ
1716 ˇ
1717 )
1718 ˇ
1719 );
1720 ˇ
1721 ˇ
1722 "});
1723}
1724
1725#[gpui::test]
1726async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1727 init_test(cx, |settings| {
1728 settings.defaults.tab_size = NonZeroU32::new(4)
1729 });
1730
1731 let language = Arc::new(Language::new(
1732 LanguageConfig {
1733 line_comment: Some("//".into()),
1734 ..LanguageConfig::default()
1735 },
1736 None,
1737 ));
1738 {
1739 let mut cx = EditorTestContext::new(cx).await;
1740 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1741 cx.set_state(indoc! {"
1742 // Fooˇ
1743 "});
1744
1745 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1746 cx.assert_editor_state(indoc! {"
1747 // Foo
1748 //ˇ
1749 "});
1750 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1751 cx.set_state(indoc! {"
1752 ˇ// Foo
1753 "});
1754 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1755 cx.assert_editor_state(indoc! {"
1756
1757 ˇ// Foo
1758 "});
1759 }
1760 // Ensure that comment continuations can be disabled.
1761 update_test_settings(cx, |settings| {
1762 settings.defaults.extend_comment_on_newline = Some(false);
1763 });
1764 let mut cx = EditorTestContext::new(cx).await;
1765 cx.set_state(indoc! {"
1766 // Fooˇ
1767 "});
1768 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1769 cx.assert_editor_state(indoc! {"
1770 // Foo
1771 ˇ
1772 "});
1773}
1774
1775#[gpui::test]
1776fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1777 init_test(cx, |_| {});
1778
1779 let (_, editor) = cx.add_window(|cx| {
1780 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1781 let mut editor = build_editor(buffer.clone(), cx);
1782 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1783 editor
1784 });
1785
1786 editor.update(cx, |editor, cx| {
1787 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1788 editor.buffer.update(cx, |buffer, cx| {
1789 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1790 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1791 });
1792 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1793
1794 editor.insert("Z", cx);
1795 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1796
1797 // The selections are moved after the inserted characters
1798 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1799 });
1800}
1801
1802#[gpui::test]
1803async fn test_tab(cx: &mut gpui::TestAppContext) {
1804 init_test(cx, |settings| {
1805 settings.defaults.tab_size = NonZeroU32::new(3)
1806 });
1807
1808 let mut cx = EditorTestContext::new(cx).await;
1809 cx.set_state(indoc! {"
1810 ˇabˇc
1811 ˇ🏀ˇ🏀ˇefg
1812 dˇ
1813 "});
1814 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1815 cx.assert_editor_state(indoc! {"
1816 ˇab ˇc
1817 ˇ🏀 ˇ🏀 ˇefg
1818 d ˇ
1819 "});
1820
1821 cx.set_state(indoc! {"
1822 a
1823 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1824 "});
1825 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1826 cx.assert_editor_state(indoc! {"
1827 a
1828 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1829 "});
1830}
1831
1832#[gpui::test]
1833async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1834 init_test(cx, |_| {});
1835
1836 let mut cx = EditorTestContext::new(cx).await;
1837 let language = Arc::new(
1838 Language::new(
1839 LanguageConfig::default(),
1840 Some(tree_sitter_rust::language()),
1841 )
1842 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1843 .unwrap(),
1844 );
1845 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1846
1847 // cursors that are already at the suggested indent level insert
1848 // a soft tab. cursors that are to the left of the suggested indent
1849 // auto-indent their line.
1850 cx.set_state(indoc! {"
1851 ˇ
1852 const a: B = (
1853 c(
1854 d(
1855 ˇ
1856 )
1857 ˇ
1858 ˇ )
1859 );
1860 "});
1861 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1862 cx.assert_editor_state(indoc! {"
1863 ˇ
1864 const a: B = (
1865 c(
1866 d(
1867 ˇ
1868 )
1869 ˇ
1870 ˇ)
1871 );
1872 "});
1873
1874 // handle auto-indent when there are multiple cursors on the same line
1875 cx.set_state(indoc! {"
1876 const a: B = (
1877 c(
1878 ˇ ˇ
1879 ˇ )
1880 );
1881 "});
1882 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1883 cx.assert_editor_state(indoc! {"
1884 const a: B = (
1885 c(
1886 ˇ
1887 ˇ)
1888 );
1889 "});
1890}
1891
1892#[gpui::test]
1893async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1894 init_test(cx, |settings| {
1895 settings.defaults.tab_size = NonZeroU32::new(4)
1896 });
1897
1898 let language = Arc::new(
1899 Language::new(
1900 LanguageConfig::default(),
1901 Some(tree_sitter_rust::language()),
1902 )
1903 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1904 .unwrap(),
1905 );
1906
1907 let mut cx = EditorTestContext::new(cx).await;
1908 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1909 cx.set_state(indoc! {"
1910 fn a() {
1911 if b {
1912 \t ˇc
1913 }
1914 }
1915 "});
1916
1917 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1918 cx.assert_editor_state(indoc! {"
1919 fn a() {
1920 if b {
1921 ˇc
1922 }
1923 }
1924 "});
1925}
1926
1927#[gpui::test]
1928async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1929 init_test(cx, |settings| {
1930 settings.defaults.tab_size = NonZeroU32::new(4);
1931 });
1932
1933 let mut cx = EditorTestContext::new(cx).await;
1934
1935 cx.set_state(indoc! {"
1936 «oneˇ» «twoˇ»
1937 three
1938 four
1939 "});
1940 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1941 cx.assert_editor_state(indoc! {"
1942 «oneˇ» «twoˇ»
1943 three
1944 four
1945 "});
1946
1947 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1948 cx.assert_editor_state(indoc! {"
1949 «oneˇ» «twoˇ»
1950 three
1951 four
1952 "});
1953
1954 // select across line ending
1955 cx.set_state(indoc! {"
1956 one two
1957 t«hree
1958 ˇ» four
1959 "});
1960 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1961 cx.assert_editor_state(indoc! {"
1962 one two
1963 t«hree
1964 ˇ» four
1965 "});
1966
1967 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1968 cx.assert_editor_state(indoc! {"
1969 one two
1970 t«hree
1971 ˇ» four
1972 "});
1973
1974 // Ensure that indenting/outdenting works when the cursor is at column 0.
1975 cx.set_state(indoc! {"
1976 one two
1977 ˇthree
1978 four
1979 "});
1980 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1981 cx.assert_editor_state(indoc! {"
1982 one two
1983 ˇthree
1984 four
1985 "});
1986
1987 cx.set_state(indoc! {"
1988 one two
1989 ˇ three
1990 four
1991 "});
1992 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1993 cx.assert_editor_state(indoc! {"
1994 one two
1995 ˇthree
1996 four
1997 "});
1998}
1999
2000#[gpui::test]
2001async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2002 init_test(cx, |settings| {
2003 settings.defaults.hard_tabs = Some(true);
2004 });
2005
2006 let mut cx = EditorTestContext::new(cx).await;
2007
2008 // select two ranges on one line
2009 cx.set_state(indoc! {"
2010 «oneˇ» «twoˇ»
2011 three
2012 four
2013 "});
2014 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2015 cx.assert_editor_state(indoc! {"
2016 \t«oneˇ» «twoˇ»
2017 three
2018 four
2019 "});
2020 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2021 cx.assert_editor_state(indoc! {"
2022 \t\t«oneˇ» «twoˇ»
2023 three
2024 four
2025 "});
2026 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2027 cx.assert_editor_state(indoc! {"
2028 \t«oneˇ» «twoˇ»
2029 three
2030 four
2031 "});
2032 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2033 cx.assert_editor_state(indoc! {"
2034 «oneˇ» «twoˇ»
2035 three
2036 four
2037 "});
2038
2039 // select across a line ending
2040 cx.set_state(indoc! {"
2041 one two
2042 t«hree
2043 ˇ»four
2044 "});
2045 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2046 cx.assert_editor_state(indoc! {"
2047 one two
2048 \tt«hree
2049 ˇ»four
2050 "});
2051 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2052 cx.assert_editor_state(indoc! {"
2053 one two
2054 \t\tt«hree
2055 ˇ»four
2056 "});
2057 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2058 cx.assert_editor_state(indoc! {"
2059 one two
2060 \tt«hree
2061 ˇ»four
2062 "});
2063 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2064 cx.assert_editor_state(indoc! {"
2065 one two
2066 t«hree
2067 ˇ»four
2068 "});
2069
2070 // Ensure that indenting/outdenting works when the cursor is at column 0.
2071 cx.set_state(indoc! {"
2072 one two
2073 ˇthree
2074 four
2075 "});
2076 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2077 cx.assert_editor_state(indoc! {"
2078 one two
2079 ˇthree
2080 four
2081 "});
2082 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2083 cx.assert_editor_state(indoc! {"
2084 one two
2085 \tˇthree
2086 four
2087 "});
2088 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2089 cx.assert_editor_state(indoc! {"
2090 one two
2091 ˇthree
2092 four
2093 "});
2094}
2095
2096#[gpui::test]
2097fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2098 init_test(cx, |settings| {
2099 settings.languages.extend([
2100 (
2101 "TOML".into(),
2102 LanguageSettingsContent {
2103 tab_size: NonZeroU32::new(2),
2104 ..Default::default()
2105 },
2106 ),
2107 (
2108 "Rust".into(),
2109 LanguageSettingsContent {
2110 tab_size: NonZeroU32::new(4),
2111 ..Default::default()
2112 },
2113 ),
2114 ]);
2115 });
2116
2117 let toml_language = Arc::new(Language::new(
2118 LanguageConfig {
2119 name: "TOML".into(),
2120 ..Default::default()
2121 },
2122 None,
2123 ));
2124 let rust_language = Arc::new(Language::new(
2125 LanguageConfig {
2126 name: "Rust".into(),
2127 ..Default::default()
2128 },
2129 None,
2130 ));
2131
2132 let toml_buffer =
2133 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2134 let rust_buffer = cx.add_model(|cx| {
2135 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
2136 });
2137 let multibuffer = cx.add_model(|cx| {
2138 let mut multibuffer = MultiBuffer::new(0);
2139 multibuffer.push_excerpts(
2140 toml_buffer.clone(),
2141 [ExcerptRange {
2142 context: Point::new(0, 0)..Point::new(2, 0),
2143 primary: None,
2144 }],
2145 cx,
2146 );
2147 multibuffer.push_excerpts(
2148 rust_buffer.clone(),
2149 [ExcerptRange {
2150 context: Point::new(0, 0)..Point::new(1, 0),
2151 primary: None,
2152 }],
2153 cx,
2154 );
2155 multibuffer
2156 });
2157
2158 cx.add_window(|cx| {
2159 let mut editor = build_editor(multibuffer, cx);
2160
2161 assert_eq!(
2162 editor.text(cx),
2163 indoc! {"
2164 a = 1
2165 b = 2
2166
2167 const c: usize = 3;
2168 "}
2169 );
2170
2171 select_ranges(
2172 &mut editor,
2173 indoc! {"
2174 «aˇ» = 1
2175 b = 2
2176
2177 «const c:ˇ» usize = 3;
2178 "},
2179 cx,
2180 );
2181
2182 editor.tab(&Tab, cx);
2183 assert_text_with_selections(
2184 &mut editor,
2185 indoc! {"
2186 «aˇ» = 1
2187 b = 2
2188
2189 «const c:ˇ» usize = 3;
2190 "},
2191 cx,
2192 );
2193 editor.tab_prev(&TabPrev, cx);
2194 assert_text_with_selections(
2195 &mut editor,
2196 indoc! {"
2197 «aˇ» = 1
2198 b = 2
2199
2200 «const c:ˇ» usize = 3;
2201 "},
2202 cx,
2203 );
2204
2205 editor
2206 });
2207}
2208
2209#[gpui::test]
2210async fn test_backspace(cx: &mut gpui::TestAppContext) {
2211 init_test(cx, |_| {});
2212
2213 let mut cx = EditorTestContext::new(cx).await;
2214
2215 // Basic backspace
2216 cx.set_state(indoc! {"
2217 onˇe two three
2218 fou«rˇ» five six
2219 seven «ˇeight nine
2220 »ten
2221 "});
2222 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2223 cx.assert_editor_state(indoc! {"
2224 oˇe two three
2225 fouˇ five six
2226 seven ˇten
2227 "});
2228
2229 // Test backspace inside and around indents
2230 cx.set_state(indoc! {"
2231 zero
2232 ˇone
2233 ˇtwo
2234 ˇ ˇ ˇ three
2235 ˇ ˇ four
2236 "});
2237 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2238 cx.assert_editor_state(indoc! {"
2239 zero
2240 ˇone
2241 ˇtwo
2242 ˇ threeˇ four
2243 "});
2244
2245 // Test backspace with line_mode set to true
2246 cx.update_editor(|e, _| e.selections.line_mode = true);
2247 cx.set_state(indoc! {"
2248 The ˇquick ˇbrown
2249 fox jumps over
2250 the lazy dog
2251 ˇThe qu«ick bˇ»rown"});
2252 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2253 cx.assert_editor_state(indoc! {"
2254 ˇfox jumps over
2255 the lazy dogˇ"});
2256}
2257
2258#[gpui::test]
2259async fn test_delete(cx: &mut gpui::TestAppContext) {
2260 init_test(cx, |_| {});
2261
2262 let mut cx = EditorTestContext::new(cx).await;
2263 cx.set_state(indoc! {"
2264 onˇe two three
2265 fou«rˇ» five six
2266 seven «ˇeight nine
2267 »ten
2268 "});
2269 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2270 cx.assert_editor_state(indoc! {"
2271 onˇ two three
2272 fouˇ five six
2273 seven ˇten
2274 "});
2275
2276 // Test backspace with line_mode set to true
2277 cx.update_editor(|e, _| e.selections.line_mode = true);
2278 cx.set_state(indoc! {"
2279 The ˇquick ˇbrown
2280 fox «ˇjum»ps over
2281 the lazy dog
2282 ˇThe qu«ick bˇ»rown"});
2283 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2284 cx.assert_editor_state("ˇthe lazy dogˇ");
2285}
2286
2287#[gpui::test]
2288fn test_delete_line(cx: &mut TestAppContext) {
2289 init_test(cx, |_| {});
2290
2291 let (_, view) = cx.add_window(|cx| {
2292 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2293 build_editor(buffer, cx)
2294 });
2295 view.update(cx, |view, cx| {
2296 view.change_selections(None, cx, |s| {
2297 s.select_display_ranges([
2298 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2299 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2300 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2301 ])
2302 });
2303 view.delete_line(&DeleteLine, cx);
2304 assert_eq!(view.display_text(cx), "ghi");
2305 assert_eq!(
2306 view.selections.display_ranges(cx),
2307 vec![
2308 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2309 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2310 ]
2311 );
2312 });
2313
2314 let (_, view) = cx.add_window(|cx| {
2315 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2316 build_editor(buffer, cx)
2317 });
2318 view.update(cx, |view, cx| {
2319 view.change_selections(None, cx, |s| {
2320 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2321 });
2322 view.delete_line(&DeleteLine, cx);
2323 assert_eq!(view.display_text(cx), "ghi\n");
2324 assert_eq!(
2325 view.selections.display_ranges(cx),
2326 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2327 );
2328 });
2329}
2330
2331#[gpui::test]
2332fn test_join_lines(cx: &mut TestAppContext) {
2333 init_test(cx, |_| {});
2334
2335 let (_, editor) = cx.add_window(|cx| {
2336 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2337 let mut editor = build_editor(buffer.clone(), cx);
2338 let buffer = buffer.read(cx).as_singleton().unwrap();
2339
2340 assert_eq!(
2341 editor.selections.ranges::<Point>(cx),
2342 &[Point::new(0, 0)..Point::new(0, 0)]
2343 );
2344
2345 editor.join_lines(&JoinLines, cx);
2346
2347 // assert_eq!(
2348 // editor.selections.ranges::<Point>(cx),
2349 // &[Point::new(0, 0), Point::new(0, 0)]
2350 // );
2351
2352 assert_eq!(buffer.read(cx).text(), "abc def\nghi\n");
2353
2354 editor
2355 });
2356}
2357
2358#[gpui::test]
2359fn test_duplicate_line(cx: &mut TestAppContext) {
2360 init_test(cx, |_| {});
2361
2362 let (_, view) = cx.add_window(|cx| {
2363 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2364 build_editor(buffer, cx)
2365 });
2366 view.update(cx, |view, cx| {
2367 view.change_selections(None, cx, |s| {
2368 s.select_display_ranges([
2369 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2370 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2371 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2372 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2373 ])
2374 });
2375 view.duplicate_line(&DuplicateLine, cx);
2376 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2377 assert_eq!(
2378 view.selections.display_ranges(cx),
2379 vec![
2380 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2381 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2382 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2383 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2384 ]
2385 );
2386 });
2387
2388 let (_, view) = cx.add_window(|cx| {
2389 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2390 build_editor(buffer, cx)
2391 });
2392 view.update(cx, |view, cx| {
2393 view.change_selections(None, cx, |s| {
2394 s.select_display_ranges([
2395 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2396 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2397 ])
2398 });
2399 view.duplicate_line(&DuplicateLine, cx);
2400 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2401 assert_eq!(
2402 view.selections.display_ranges(cx),
2403 vec![
2404 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2405 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2406 ]
2407 );
2408 });
2409}
2410
2411#[gpui::test]
2412fn test_move_line_up_down(cx: &mut TestAppContext) {
2413 init_test(cx, |_| {});
2414
2415 let (_, view) = cx.add_window(|cx| {
2416 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2417 build_editor(buffer, cx)
2418 });
2419 view.update(cx, |view, cx| {
2420 view.fold_ranges(
2421 vec![
2422 Point::new(0, 2)..Point::new(1, 2),
2423 Point::new(2, 3)..Point::new(4, 1),
2424 Point::new(7, 0)..Point::new(8, 4),
2425 ],
2426 true,
2427 cx,
2428 );
2429 view.change_selections(None, cx, |s| {
2430 s.select_display_ranges([
2431 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2432 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2433 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2434 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2435 ])
2436 });
2437 assert_eq!(
2438 view.display_text(cx),
2439 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2440 );
2441
2442 view.move_line_up(&MoveLineUp, cx);
2443 assert_eq!(
2444 view.display_text(cx),
2445 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2446 );
2447 assert_eq!(
2448 view.selections.display_ranges(cx),
2449 vec![
2450 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2451 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2452 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2453 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2454 ]
2455 );
2456 });
2457
2458 view.update(cx, |view, cx| {
2459 view.move_line_down(&MoveLineDown, cx);
2460 assert_eq!(
2461 view.display_text(cx),
2462 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2463 );
2464 assert_eq!(
2465 view.selections.display_ranges(cx),
2466 vec![
2467 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2468 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2469 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2470 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2471 ]
2472 );
2473 });
2474
2475 view.update(cx, |view, cx| {
2476 view.move_line_down(&MoveLineDown, cx);
2477 assert_eq!(
2478 view.display_text(cx),
2479 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2480 );
2481 assert_eq!(
2482 view.selections.display_ranges(cx),
2483 vec![
2484 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2485 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2486 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2487 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2488 ]
2489 );
2490 });
2491
2492 view.update(cx, |view, cx| {
2493 view.move_line_up(&MoveLineUp, cx);
2494 assert_eq!(
2495 view.display_text(cx),
2496 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2497 );
2498 assert_eq!(
2499 view.selections.display_ranges(cx),
2500 vec![
2501 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2502 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2503 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2504 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2505 ]
2506 );
2507 });
2508}
2509
2510#[gpui::test]
2511fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2512 init_test(cx, |_| {});
2513
2514 let (_, editor) = cx.add_window(|cx| {
2515 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2516 build_editor(buffer, cx)
2517 });
2518 editor.update(cx, |editor, cx| {
2519 let snapshot = editor.buffer.read(cx).snapshot(cx);
2520 editor.insert_blocks(
2521 [BlockProperties {
2522 style: BlockStyle::Fixed,
2523 position: snapshot.anchor_after(Point::new(2, 0)),
2524 disposition: BlockDisposition::Below,
2525 height: 1,
2526 render: Arc::new(|_| Empty::new().into_any()),
2527 }],
2528 Some(Autoscroll::fit()),
2529 cx,
2530 );
2531 editor.change_selections(None, cx, |s| {
2532 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2533 });
2534 editor.move_line_down(&MoveLineDown, cx);
2535 });
2536}
2537
2538#[gpui::test]
2539fn test_transpose(cx: &mut TestAppContext) {
2540 init_test(cx, |_| {});
2541
2542 _ = cx
2543 .add_window(|cx| {
2544 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2545
2546 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2547 editor.transpose(&Default::default(), cx);
2548 assert_eq!(editor.text(cx), "bac");
2549 assert_eq!(editor.selections.ranges(cx), [2..2]);
2550
2551 editor.transpose(&Default::default(), cx);
2552 assert_eq!(editor.text(cx), "bca");
2553 assert_eq!(editor.selections.ranges(cx), [3..3]);
2554
2555 editor.transpose(&Default::default(), cx);
2556 assert_eq!(editor.text(cx), "bac");
2557 assert_eq!(editor.selections.ranges(cx), [3..3]);
2558
2559 editor
2560 })
2561 .1;
2562
2563 _ = cx
2564 .add_window(|cx| {
2565 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2566
2567 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2568 editor.transpose(&Default::default(), cx);
2569 assert_eq!(editor.text(cx), "acb\nde");
2570 assert_eq!(editor.selections.ranges(cx), [3..3]);
2571
2572 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2573 editor.transpose(&Default::default(), cx);
2574 assert_eq!(editor.text(cx), "acbd\ne");
2575 assert_eq!(editor.selections.ranges(cx), [5..5]);
2576
2577 editor.transpose(&Default::default(), cx);
2578 assert_eq!(editor.text(cx), "acbde\n");
2579 assert_eq!(editor.selections.ranges(cx), [6..6]);
2580
2581 editor.transpose(&Default::default(), cx);
2582 assert_eq!(editor.text(cx), "acbd\ne");
2583 assert_eq!(editor.selections.ranges(cx), [6..6]);
2584
2585 editor
2586 })
2587 .1;
2588
2589 _ = cx
2590 .add_window(|cx| {
2591 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2592
2593 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2594 editor.transpose(&Default::default(), cx);
2595 assert_eq!(editor.text(cx), "bacd\ne");
2596 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2597
2598 editor.transpose(&Default::default(), cx);
2599 assert_eq!(editor.text(cx), "bcade\n");
2600 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2601
2602 editor.transpose(&Default::default(), cx);
2603 assert_eq!(editor.text(cx), "bcda\ne");
2604 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2605
2606 editor.transpose(&Default::default(), cx);
2607 assert_eq!(editor.text(cx), "bcade\n");
2608 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2609
2610 editor.transpose(&Default::default(), cx);
2611 assert_eq!(editor.text(cx), "bcaed\n");
2612 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2613
2614 editor
2615 })
2616 .1;
2617
2618 _ = cx
2619 .add_window(|cx| {
2620 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2621
2622 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2623 editor.transpose(&Default::default(), cx);
2624 assert_eq!(editor.text(cx), "🏀🍐✋");
2625 assert_eq!(editor.selections.ranges(cx), [8..8]);
2626
2627 editor.transpose(&Default::default(), cx);
2628 assert_eq!(editor.text(cx), "🏀✋🍐");
2629 assert_eq!(editor.selections.ranges(cx), [11..11]);
2630
2631 editor.transpose(&Default::default(), cx);
2632 assert_eq!(editor.text(cx), "🏀🍐✋");
2633 assert_eq!(editor.selections.ranges(cx), [11..11]);
2634
2635 editor
2636 })
2637 .1;
2638}
2639
2640#[gpui::test]
2641async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2642 init_test(cx, |_| {});
2643
2644 let mut cx = EditorTestContext::new(cx).await;
2645
2646 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2647 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2648 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2649
2650 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2651 cx.set_state("two ˇfour ˇsix ˇ");
2652 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2653 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2654
2655 // Paste again but with only two cursors. Since the number of cursors doesn't
2656 // match the number of slices in the clipboard, the entire clipboard text
2657 // is pasted at each cursor.
2658 cx.set_state("ˇtwo one✅ four three six five ˇ");
2659 cx.update_editor(|e, cx| {
2660 e.handle_input("( ", cx);
2661 e.paste(&Paste, cx);
2662 e.handle_input(") ", cx);
2663 });
2664 cx.assert_editor_state(
2665 &([
2666 "( one✅ ",
2667 "three ",
2668 "five ) ˇtwo one✅ four three six five ( one✅ ",
2669 "three ",
2670 "five ) ˇ",
2671 ]
2672 .join("\n")),
2673 );
2674
2675 // Cut with three selections, one of which is full-line.
2676 cx.set_state(indoc! {"
2677 1«2ˇ»3
2678 4ˇ567
2679 «8ˇ»9"});
2680 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2681 cx.assert_editor_state(indoc! {"
2682 1ˇ3
2683 ˇ9"});
2684
2685 // Paste with three selections, noticing how the copied selection that was full-line
2686 // gets inserted before the second cursor.
2687 cx.set_state(indoc! {"
2688 1ˇ3
2689 9ˇ
2690 «oˇ»ne"});
2691 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2692 cx.assert_editor_state(indoc! {"
2693 12ˇ3
2694 4567
2695 9ˇ
2696 8ˇne"});
2697
2698 // Copy with a single cursor only, which writes the whole line into the clipboard.
2699 cx.set_state(indoc! {"
2700 The quick brown
2701 fox juˇmps over
2702 the lazy dog"});
2703 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2704 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2705
2706 // Paste with three selections, noticing how the copied full-line selection is inserted
2707 // before the empty selections but replaces the selection that is non-empty.
2708 cx.set_state(indoc! {"
2709 Tˇhe quick brown
2710 «foˇ»x jumps over
2711 tˇhe lazy dog"});
2712 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2713 cx.assert_editor_state(indoc! {"
2714 fox jumps over
2715 Tˇhe quick brown
2716 fox jumps over
2717 ˇx jumps over
2718 fox jumps over
2719 tˇhe lazy dog"});
2720}
2721
2722#[gpui::test]
2723async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2724 init_test(cx, |_| {});
2725
2726 let mut cx = EditorTestContext::new(cx).await;
2727 let language = Arc::new(Language::new(
2728 LanguageConfig::default(),
2729 Some(tree_sitter_rust::language()),
2730 ));
2731 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2732
2733 // Cut an indented block, without the leading whitespace.
2734 cx.set_state(indoc! {"
2735 const a: B = (
2736 c(),
2737 «d(
2738 e,
2739 f
2740 )ˇ»
2741 );
2742 "});
2743 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2744 cx.assert_editor_state(indoc! {"
2745 const a: B = (
2746 c(),
2747 ˇ
2748 );
2749 "});
2750
2751 // Paste it at the same position.
2752 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2753 cx.assert_editor_state(indoc! {"
2754 const a: B = (
2755 c(),
2756 d(
2757 e,
2758 f
2759 )ˇ
2760 );
2761 "});
2762
2763 // Paste it at a line with a lower indent level.
2764 cx.set_state(indoc! {"
2765 ˇ
2766 const a: B = (
2767 c(),
2768 );
2769 "});
2770 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2771 cx.assert_editor_state(indoc! {"
2772 d(
2773 e,
2774 f
2775 )ˇ
2776 const a: B = (
2777 c(),
2778 );
2779 "});
2780
2781 // Cut an indented block, with the leading whitespace.
2782 cx.set_state(indoc! {"
2783 const a: B = (
2784 c(),
2785 « d(
2786 e,
2787 f
2788 )
2789 ˇ»);
2790 "});
2791 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2792 cx.assert_editor_state(indoc! {"
2793 const a: B = (
2794 c(),
2795 ˇ);
2796 "});
2797
2798 // Paste it at the same position.
2799 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2800 cx.assert_editor_state(indoc! {"
2801 const a: B = (
2802 c(),
2803 d(
2804 e,
2805 f
2806 )
2807 ˇ);
2808 "});
2809
2810 // Paste it at a line with a higher indent level.
2811 cx.set_state(indoc! {"
2812 const a: B = (
2813 c(),
2814 d(
2815 e,
2816 fˇ
2817 )
2818 );
2819 "});
2820 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2821 cx.assert_editor_state(indoc! {"
2822 const a: B = (
2823 c(),
2824 d(
2825 e,
2826 f d(
2827 e,
2828 f
2829 )
2830 ˇ
2831 )
2832 );
2833 "});
2834}
2835
2836#[gpui::test]
2837fn test_select_all(cx: &mut TestAppContext) {
2838 init_test(cx, |_| {});
2839
2840 let (_, view) = cx.add_window(|cx| {
2841 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2842 build_editor(buffer, cx)
2843 });
2844 view.update(cx, |view, cx| {
2845 view.select_all(&SelectAll, cx);
2846 assert_eq!(
2847 view.selections.display_ranges(cx),
2848 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2849 );
2850 });
2851}
2852
2853#[gpui::test]
2854fn test_select_line(cx: &mut TestAppContext) {
2855 init_test(cx, |_| {});
2856
2857 let (_, view) = cx.add_window(|cx| {
2858 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2859 build_editor(buffer, cx)
2860 });
2861 view.update(cx, |view, cx| {
2862 view.change_selections(None, cx, |s| {
2863 s.select_display_ranges([
2864 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2865 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2866 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2867 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2868 ])
2869 });
2870 view.select_line(&SelectLine, cx);
2871 assert_eq!(
2872 view.selections.display_ranges(cx),
2873 vec![
2874 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2875 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2876 ]
2877 );
2878 });
2879
2880 view.update(cx, |view, cx| {
2881 view.select_line(&SelectLine, cx);
2882 assert_eq!(
2883 view.selections.display_ranges(cx),
2884 vec![
2885 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2886 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2887 ]
2888 );
2889 });
2890
2891 view.update(cx, |view, cx| {
2892 view.select_line(&SelectLine, cx);
2893 assert_eq!(
2894 view.selections.display_ranges(cx),
2895 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2896 );
2897 });
2898}
2899
2900#[gpui::test]
2901fn test_split_selection_into_lines(cx: &mut TestAppContext) {
2902 init_test(cx, |_| {});
2903
2904 let (_, view) = cx.add_window(|cx| {
2905 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2906 build_editor(buffer, cx)
2907 });
2908 view.update(cx, |view, cx| {
2909 view.fold_ranges(
2910 vec![
2911 Point::new(0, 2)..Point::new(1, 2),
2912 Point::new(2, 3)..Point::new(4, 1),
2913 Point::new(7, 0)..Point::new(8, 4),
2914 ],
2915 true,
2916 cx,
2917 );
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2921 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2922 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2923 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2924 ])
2925 });
2926 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
2927 });
2928
2929 view.update(cx, |view, cx| {
2930 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2931 assert_eq!(
2932 view.display_text(cx),
2933 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
2934 );
2935 assert_eq!(
2936 view.selections.display_ranges(cx),
2937 [
2938 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2939 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2940 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2941 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2942 ]
2943 );
2944 });
2945
2946 view.update(cx, |view, cx| {
2947 view.change_selections(None, cx, |s| {
2948 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2949 });
2950 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2951 assert_eq!(
2952 view.display_text(cx),
2953 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2954 );
2955 assert_eq!(
2956 view.selections.display_ranges(cx),
2957 [
2958 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2959 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2960 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2961 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2962 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2963 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2964 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2965 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2966 ]
2967 );
2968 });
2969}
2970
2971#[gpui::test]
2972fn test_add_selection_above_below(cx: &mut TestAppContext) {
2973 init_test(cx, |_| {});
2974
2975 let (_, view) = cx.add_window(|cx| {
2976 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2977 build_editor(buffer, cx)
2978 });
2979
2980 view.update(cx, |view, cx| {
2981 view.change_selections(None, cx, |s| {
2982 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2983 });
2984 });
2985 view.update(cx, |view, cx| {
2986 view.add_selection_above(&AddSelectionAbove, cx);
2987 assert_eq!(
2988 view.selections.display_ranges(cx),
2989 vec![
2990 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2991 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2992 ]
2993 );
2994 });
2995
2996 view.update(cx, |view, cx| {
2997 view.add_selection_above(&AddSelectionAbove, cx);
2998 assert_eq!(
2999 view.selections.display_ranges(cx),
3000 vec![
3001 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3002 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3003 ]
3004 );
3005 });
3006
3007 view.update(cx, |view, cx| {
3008 view.add_selection_below(&AddSelectionBelow, cx);
3009 assert_eq!(
3010 view.selections.display_ranges(cx),
3011 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3012 );
3013
3014 view.undo_selection(&UndoSelection, cx);
3015 assert_eq!(
3016 view.selections.display_ranges(cx),
3017 vec![
3018 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3019 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3020 ]
3021 );
3022
3023 view.redo_selection(&RedoSelection, cx);
3024 assert_eq!(
3025 view.selections.display_ranges(cx),
3026 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3027 );
3028 });
3029
3030 view.update(cx, |view, cx| {
3031 view.add_selection_below(&AddSelectionBelow, cx);
3032 assert_eq!(
3033 view.selections.display_ranges(cx),
3034 vec![
3035 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3036 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3037 ]
3038 );
3039 });
3040
3041 view.update(cx, |view, cx| {
3042 view.add_selection_below(&AddSelectionBelow, cx);
3043 assert_eq!(
3044 view.selections.display_ranges(cx),
3045 vec![
3046 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3047 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3048 ]
3049 );
3050 });
3051
3052 view.update(cx, |view, cx| {
3053 view.change_selections(None, cx, |s| {
3054 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
3055 });
3056 });
3057 view.update(cx, |view, cx| {
3058 view.add_selection_below(&AddSelectionBelow, cx);
3059 assert_eq!(
3060 view.selections.display_ranges(cx),
3061 vec![
3062 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3063 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3064 ]
3065 );
3066 });
3067
3068 view.update(cx, |view, cx| {
3069 view.add_selection_below(&AddSelectionBelow, cx);
3070 assert_eq!(
3071 view.selections.display_ranges(cx),
3072 vec![
3073 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3074 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3075 ]
3076 );
3077 });
3078
3079 view.update(cx, |view, cx| {
3080 view.add_selection_above(&AddSelectionAbove, cx);
3081 assert_eq!(
3082 view.selections.display_ranges(cx),
3083 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3084 );
3085 });
3086
3087 view.update(cx, |view, cx| {
3088 view.add_selection_above(&AddSelectionAbove, cx);
3089 assert_eq!(
3090 view.selections.display_ranges(cx),
3091 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3092 );
3093 });
3094
3095 view.update(cx, |view, cx| {
3096 view.change_selections(None, cx, |s| {
3097 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3098 });
3099 view.add_selection_below(&AddSelectionBelow, cx);
3100 assert_eq!(
3101 view.selections.display_ranges(cx),
3102 vec![
3103 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3104 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3105 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3106 ]
3107 );
3108 });
3109
3110 view.update(cx, |view, cx| {
3111 view.add_selection_below(&AddSelectionBelow, cx);
3112 assert_eq!(
3113 view.selections.display_ranges(cx),
3114 vec![
3115 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3116 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3117 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3118 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3119 ]
3120 );
3121 });
3122
3123 view.update(cx, |view, cx| {
3124 view.add_selection_above(&AddSelectionAbove, cx);
3125 assert_eq!(
3126 view.selections.display_ranges(cx),
3127 vec![
3128 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3129 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3130 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3131 ]
3132 );
3133 });
3134
3135 view.update(cx, |view, cx| {
3136 view.change_selections(None, cx, |s| {
3137 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3138 });
3139 });
3140 view.update(cx, |view, cx| {
3141 view.add_selection_above(&AddSelectionAbove, cx);
3142 assert_eq!(
3143 view.selections.display_ranges(cx),
3144 vec![
3145 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3146 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3147 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3148 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3149 ]
3150 );
3151 });
3152
3153 view.update(cx, |view, cx| {
3154 view.add_selection_below(&AddSelectionBelow, cx);
3155 assert_eq!(
3156 view.selections.display_ranges(cx),
3157 vec![
3158 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3159 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3160 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3161 ]
3162 );
3163 });
3164}
3165
3166#[gpui::test]
3167async fn test_select_next(cx: &mut gpui::TestAppContext) {
3168 init_test(cx, |_| {});
3169
3170 let mut cx = EditorTestContext::new(cx).await;
3171 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3172
3173 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3174 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3175
3176 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3177 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3178
3179 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3180 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3181
3182 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3183 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3184
3185 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3186 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3187
3188 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3189 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3190}
3191
3192#[gpui::test]
3193async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3194 init_test(cx, |_| {});
3195 {
3196 // `Select previous` without a selection (selects wordwise)
3197 let mut cx = EditorTestContext::new(cx).await;
3198 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3199
3200 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3201 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3202
3203 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3204 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3205
3206 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3207 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3208
3209 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3210 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3211
3212 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3213 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3214
3215 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3216 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3217 }
3218 {
3219 // `Select previous` with a selection
3220 let mut cx = EditorTestContext::new(cx).await;
3221 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3222
3223 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3224 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3225
3226 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3227 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3228
3229 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3230 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3231
3232 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3233 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3234
3235 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3236 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3237
3238 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3239 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3240 }
3241}
3242
3243#[gpui::test]
3244async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3245 init_test(cx, |_| {});
3246
3247 let language = Arc::new(Language::new(
3248 LanguageConfig::default(),
3249 Some(tree_sitter_rust::language()),
3250 ));
3251
3252 let text = r#"
3253 use mod1::mod2::{mod3, mod4};
3254
3255 fn fn_1(param1: bool, param2: &str) {
3256 let var1 = "text";
3257 }
3258 "#
3259 .unindent();
3260
3261 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3262 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3263 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3264 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3265 .await;
3266
3267 view.update(cx, |view, cx| {
3268 view.change_selections(None, cx, |s| {
3269 s.select_display_ranges([
3270 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3271 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3272 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3273 ]);
3274 });
3275 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3276 });
3277 assert_eq!(
3278 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3279 &[
3280 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3281 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3282 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3283 ]
3284 );
3285
3286 view.update(cx, |view, cx| {
3287 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3288 });
3289 assert_eq!(
3290 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3291 &[
3292 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3293 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3294 ]
3295 );
3296
3297 view.update(cx, |view, cx| {
3298 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3299 });
3300 assert_eq!(
3301 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3302 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3303 );
3304
3305 // Trying to expand the selected syntax node one more time has no effect.
3306 view.update(cx, |view, cx| {
3307 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3308 });
3309 assert_eq!(
3310 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3311 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3312 );
3313
3314 view.update(cx, |view, cx| {
3315 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3316 });
3317 assert_eq!(
3318 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3319 &[
3320 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3321 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3322 ]
3323 );
3324
3325 view.update(cx, |view, cx| {
3326 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3327 });
3328 assert_eq!(
3329 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3330 &[
3331 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3332 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3333 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3334 ]
3335 );
3336
3337 view.update(cx, |view, cx| {
3338 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3339 });
3340 assert_eq!(
3341 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3342 &[
3343 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3344 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3345 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3346 ]
3347 );
3348
3349 // Trying to shrink the selected syntax node one more time has no effect.
3350 view.update(cx, |view, cx| {
3351 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3352 });
3353 assert_eq!(
3354 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3355 &[
3356 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3357 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3358 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3359 ]
3360 );
3361
3362 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3363 // a fold.
3364 view.update(cx, |view, cx| {
3365 view.fold_ranges(
3366 vec![
3367 Point::new(0, 21)..Point::new(0, 24),
3368 Point::new(3, 20)..Point::new(3, 22),
3369 ],
3370 true,
3371 cx,
3372 );
3373 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3374 });
3375 assert_eq!(
3376 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3377 &[
3378 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3379 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3380 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3381 ]
3382 );
3383}
3384
3385#[gpui::test]
3386async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3387 init_test(cx, |_| {});
3388
3389 let language = Arc::new(
3390 Language::new(
3391 LanguageConfig {
3392 brackets: BracketPairConfig {
3393 pairs: vec![
3394 BracketPair {
3395 start: "{".to_string(),
3396 end: "}".to_string(),
3397 close: false,
3398 newline: true,
3399 },
3400 BracketPair {
3401 start: "(".to_string(),
3402 end: ")".to_string(),
3403 close: false,
3404 newline: true,
3405 },
3406 ],
3407 ..Default::default()
3408 },
3409 ..Default::default()
3410 },
3411 Some(tree_sitter_rust::language()),
3412 )
3413 .with_indents_query(
3414 r#"
3415 (_ "(" ")" @end) @indent
3416 (_ "{" "}" @end) @indent
3417 "#,
3418 )
3419 .unwrap(),
3420 );
3421
3422 let text = "fn a() {}";
3423
3424 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3425 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3426 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3427 editor
3428 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3429 .await;
3430
3431 editor.update(cx, |editor, cx| {
3432 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3433 editor.newline(&Newline, cx);
3434 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3435 assert_eq!(
3436 editor.selections.ranges(cx),
3437 &[
3438 Point::new(1, 4)..Point::new(1, 4),
3439 Point::new(3, 4)..Point::new(3, 4),
3440 Point::new(5, 0)..Point::new(5, 0)
3441 ]
3442 );
3443 });
3444}
3445
3446#[gpui::test]
3447async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3448 init_test(cx, |_| {});
3449
3450 let mut cx = EditorTestContext::new(cx).await;
3451
3452 let language = Arc::new(Language::new(
3453 LanguageConfig {
3454 brackets: BracketPairConfig {
3455 pairs: vec![
3456 BracketPair {
3457 start: "{".to_string(),
3458 end: "}".to_string(),
3459 close: true,
3460 newline: true,
3461 },
3462 BracketPair {
3463 start: "(".to_string(),
3464 end: ")".to_string(),
3465 close: true,
3466 newline: true,
3467 },
3468 BracketPair {
3469 start: "/*".to_string(),
3470 end: " */".to_string(),
3471 close: true,
3472 newline: true,
3473 },
3474 BracketPair {
3475 start: "[".to_string(),
3476 end: "]".to_string(),
3477 close: false,
3478 newline: true,
3479 },
3480 BracketPair {
3481 start: "\"".to_string(),
3482 end: "\"".to_string(),
3483 close: true,
3484 newline: false,
3485 },
3486 ],
3487 ..Default::default()
3488 },
3489 autoclose_before: "})]".to_string(),
3490 ..Default::default()
3491 },
3492 Some(tree_sitter_rust::language()),
3493 ));
3494
3495 let registry = Arc::new(LanguageRegistry::test());
3496 registry.add(language.clone());
3497 cx.update_buffer(|buffer, cx| {
3498 buffer.set_language_registry(registry);
3499 buffer.set_language(Some(language), cx);
3500 });
3501
3502 cx.set_state(
3503 &r#"
3504 🏀ˇ
3505 εˇ
3506 ❤️ˇ
3507 "#
3508 .unindent(),
3509 );
3510
3511 // autoclose multiple nested brackets at multiple cursors
3512 cx.update_editor(|view, cx| {
3513 view.handle_input("{", cx);
3514 view.handle_input("{", cx);
3515 view.handle_input("{", cx);
3516 });
3517 cx.assert_editor_state(
3518 &"
3519 🏀{{{ˇ}}}
3520 ε{{{ˇ}}}
3521 ❤️{{{ˇ}}}
3522 "
3523 .unindent(),
3524 );
3525
3526 // insert a different closing bracket
3527 cx.update_editor(|view, cx| {
3528 view.handle_input(")", cx);
3529 });
3530 cx.assert_editor_state(
3531 &"
3532 🏀{{{)ˇ}}}
3533 ε{{{)ˇ}}}
3534 ❤️{{{)ˇ}}}
3535 "
3536 .unindent(),
3537 );
3538
3539 // skip over the auto-closed brackets when typing a closing bracket
3540 cx.update_editor(|view, cx| {
3541 view.move_right(&MoveRight, cx);
3542 view.handle_input("}", cx);
3543 view.handle_input("}", cx);
3544 view.handle_input("}", cx);
3545 });
3546 cx.assert_editor_state(
3547 &"
3548 🏀{{{)}}}}ˇ
3549 ε{{{)}}}}ˇ
3550 ❤️{{{)}}}}ˇ
3551 "
3552 .unindent(),
3553 );
3554
3555 // autoclose multi-character pairs
3556 cx.set_state(
3557 &"
3558 ˇ
3559 ˇ
3560 "
3561 .unindent(),
3562 );
3563 cx.update_editor(|view, cx| {
3564 view.handle_input("/", cx);
3565 view.handle_input("*", cx);
3566 });
3567 cx.assert_editor_state(
3568 &"
3569 /*ˇ */
3570 /*ˇ */
3571 "
3572 .unindent(),
3573 );
3574
3575 // one cursor autocloses a multi-character pair, one cursor
3576 // does not autoclose.
3577 cx.set_state(
3578 &"
3579 /ˇ
3580 ˇ
3581 "
3582 .unindent(),
3583 );
3584 cx.update_editor(|view, cx| view.handle_input("*", cx));
3585 cx.assert_editor_state(
3586 &"
3587 /*ˇ */
3588 *ˇ
3589 "
3590 .unindent(),
3591 );
3592
3593 // Don't autoclose if the next character isn't whitespace and isn't
3594 // listed in the language's "autoclose_before" section.
3595 cx.set_state("ˇa b");
3596 cx.update_editor(|view, cx| view.handle_input("{", cx));
3597 cx.assert_editor_state("{ˇa b");
3598
3599 // Don't autoclose if `close` is false for the bracket pair
3600 cx.set_state("ˇ");
3601 cx.update_editor(|view, cx| view.handle_input("[", cx));
3602 cx.assert_editor_state("[ˇ");
3603
3604 // Surround with brackets if text is selected
3605 cx.set_state("«aˇ» b");
3606 cx.update_editor(|view, cx| view.handle_input("{", cx));
3607 cx.assert_editor_state("{«aˇ»} b");
3608
3609 // Autclose pair where the start and end characters are the same
3610 cx.set_state("aˇ");
3611 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3612 cx.assert_editor_state("a\"ˇ\"");
3613 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3614 cx.assert_editor_state("a\"\"ˇ");
3615}
3616
3617#[gpui::test]
3618async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3619 init_test(cx, |_| {});
3620
3621 let mut cx = EditorTestContext::new(cx).await;
3622
3623 let html_language = Arc::new(
3624 Language::new(
3625 LanguageConfig {
3626 name: "HTML".into(),
3627 brackets: BracketPairConfig {
3628 pairs: vec![
3629 BracketPair {
3630 start: "<".into(),
3631 end: ">".into(),
3632 close: true,
3633 ..Default::default()
3634 },
3635 BracketPair {
3636 start: "{".into(),
3637 end: "}".into(),
3638 close: true,
3639 ..Default::default()
3640 },
3641 BracketPair {
3642 start: "(".into(),
3643 end: ")".into(),
3644 close: true,
3645 ..Default::default()
3646 },
3647 ],
3648 ..Default::default()
3649 },
3650 autoclose_before: "})]>".into(),
3651 ..Default::default()
3652 },
3653 Some(tree_sitter_html::language()),
3654 )
3655 .with_injection_query(
3656 r#"
3657 (script_element
3658 (raw_text) @content
3659 (#set! "language" "javascript"))
3660 "#,
3661 )
3662 .unwrap(),
3663 );
3664
3665 let javascript_language = Arc::new(Language::new(
3666 LanguageConfig {
3667 name: "JavaScript".into(),
3668 brackets: BracketPairConfig {
3669 pairs: vec![
3670 BracketPair {
3671 start: "/*".into(),
3672 end: " */".into(),
3673 close: true,
3674 ..Default::default()
3675 },
3676 BracketPair {
3677 start: "{".into(),
3678 end: "}".into(),
3679 close: true,
3680 ..Default::default()
3681 },
3682 BracketPair {
3683 start: "(".into(),
3684 end: ")".into(),
3685 close: true,
3686 ..Default::default()
3687 },
3688 ],
3689 ..Default::default()
3690 },
3691 autoclose_before: "})]>".into(),
3692 ..Default::default()
3693 },
3694 Some(tree_sitter_javascript::language()),
3695 ));
3696
3697 let registry = Arc::new(LanguageRegistry::test());
3698 registry.add(html_language.clone());
3699 registry.add(javascript_language.clone());
3700
3701 cx.update_buffer(|buffer, cx| {
3702 buffer.set_language_registry(registry);
3703 buffer.set_language(Some(html_language), cx);
3704 });
3705
3706 cx.set_state(
3707 &r#"
3708 <body>ˇ
3709 <script>
3710 var x = 1;ˇ
3711 </script>
3712 </body>ˇ
3713 "#
3714 .unindent(),
3715 );
3716
3717 // Precondition: different languages are active at different locations.
3718 cx.update_editor(|editor, cx| {
3719 let snapshot = editor.snapshot(cx);
3720 let cursors = editor.selections.ranges::<usize>(cx);
3721 let languages = cursors
3722 .iter()
3723 .map(|c| snapshot.language_at(c.start).unwrap().name())
3724 .collect::<Vec<_>>();
3725 assert_eq!(
3726 languages,
3727 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3728 );
3729 });
3730
3731 // Angle brackets autoclose in HTML, but not JavaScript.
3732 cx.update_editor(|editor, cx| {
3733 editor.handle_input("<", cx);
3734 editor.handle_input("a", cx);
3735 });
3736 cx.assert_editor_state(
3737 &r#"
3738 <body><aˇ>
3739 <script>
3740 var x = 1;<aˇ
3741 </script>
3742 </body><aˇ>
3743 "#
3744 .unindent(),
3745 );
3746
3747 // Curly braces and parens autoclose in both HTML and JavaScript.
3748 cx.update_editor(|editor, cx| {
3749 editor.handle_input(" b=", cx);
3750 editor.handle_input("{", cx);
3751 editor.handle_input("c", cx);
3752 editor.handle_input("(", cx);
3753 });
3754 cx.assert_editor_state(
3755 &r#"
3756 <body><a b={c(ˇ)}>
3757 <script>
3758 var x = 1;<a b={c(ˇ)}
3759 </script>
3760 </body><a b={c(ˇ)}>
3761 "#
3762 .unindent(),
3763 );
3764
3765 // Brackets that were already autoclosed are skipped.
3766 cx.update_editor(|editor, cx| {
3767 editor.handle_input(")", cx);
3768 editor.handle_input("d", cx);
3769 editor.handle_input("}", cx);
3770 });
3771 cx.assert_editor_state(
3772 &r#"
3773 <body><a b={c()d}ˇ>
3774 <script>
3775 var x = 1;<a b={c()d}ˇ
3776 </script>
3777 </body><a b={c()d}ˇ>
3778 "#
3779 .unindent(),
3780 );
3781 cx.update_editor(|editor, cx| {
3782 editor.handle_input(">", cx);
3783 });
3784 cx.assert_editor_state(
3785 &r#"
3786 <body><a b={c()d}>ˇ
3787 <script>
3788 var x = 1;<a b={c()d}>ˇ
3789 </script>
3790 </body><a b={c()d}>ˇ
3791 "#
3792 .unindent(),
3793 );
3794
3795 // Reset
3796 cx.set_state(
3797 &r#"
3798 <body>ˇ
3799 <script>
3800 var x = 1;ˇ
3801 </script>
3802 </body>ˇ
3803 "#
3804 .unindent(),
3805 );
3806
3807 cx.update_editor(|editor, cx| {
3808 editor.handle_input("<", cx);
3809 });
3810 cx.assert_editor_state(
3811 &r#"
3812 <body><ˇ>
3813 <script>
3814 var x = 1;<ˇ
3815 </script>
3816 </body><ˇ>
3817 "#
3818 .unindent(),
3819 );
3820
3821 // When backspacing, the closing angle brackets are removed.
3822 cx.update_editor(|editor, cx| {
3823 editor.backspace(&Backspace, cx);
3824 });
3825 cx.assert_editor_state(
3826 &r#"
3827 <body>ˇ
3828 <script>
3829 var x = 1;ˇ
3830 </script>
3831 </body>ˇ
3832 "#
3833 .unindent(),
3834 );
3835
3836 // Block comments autoclose in JavaScript, but not HTML.
3837 cx.update_editor(|editor, cx| {
3838 editor.handle_input("/", cx);
3839 editor.handle_input("*", cx);
3840 });
3841 cx.assert_editor_state(
3842 &r#"
3843 <body>/*ˇ
3844 <script>
3845 var x = 1;/*ˇ */
3846 </script>
3847 </body>/*ˇ
3848 "#
3849 .unindent(),
3850 );
3851}
3852
3853#[gpui::test]
3854async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
3855 init_test(cx, |_| {});
3856
3857 let mut cx = EditorTestContext::new(cx).await;
3858
3859 let rust_language = Arc::new(
3860 Language::new(
3861 LanguageConfig {
3862 name: "Rust".into(),
3863 brackets: serde_json::from_value(json!([
3864 { "start": "{", "end": "}", "close": true, "newline": true },
3865 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
3866 ]))
3867 .unwrap(),
3868 autoclose_before: "})]>".into(),
3869 ..Default::default()
3870 },
3871 Some(tree_sitter_rust::language()),
3872 )
3873 .with_override_query("(string_literal) @string")
3874 .unwrap(),
3875 );
3876
3877 let registry = Arc::new(LanguageRegistry::test());
3878 registry.add(rust_language.clone());
3879
3880 cx.update_buffer(|buffer, cx| {
3881 buffer.set_language_registry(registry);
3882 buffer.set_language(Some(rust_language), cx);
3883 });
3884
3885 cx.set_state(
3886 &r#"
3887 let x = ˇ
3888 "#
3889 .unindent(),
3890 );
3891
3892 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
3893 cx.update_editor(|editor, cx| {
3894 editor.handle_input("\"", cx);
3895 });
3896 cx.assert_editor_state(
3897 &r#"
3898 let x = "ˇ"
3899 "#
3900 .unindent(),
3901 );
3902
3903 // Inserting another quotation mark. The cursor moves across the existing
3904 // automatically-inserted quotation mark.
3905 cx.update_editor(|editor, cx| {
3906 editor.handle_input("\"", cx);
3907 });
3908 cx.assert_editor_state(
3909 &r#"
3910 let x = ""ˇ
3911 "#
3912 .unindent(),
3913 );
3914
3915 // Reset
3916 cx.set_state(
3917 &r#"
3918 let x = ˇ
3919 "#
3920 .unindent(),
3921 );
3922
3923 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
3924 cx.update_editor(|editor, cx| {
3925 editor.handle_input("\"", cx);
3926 editor.handle_input(" ", cx);
3927 editor.move_left(&Default::default(), cx);
3928 editor.handle_input("\\", cx);
3929 editor.handle_input("\"", cx);
3930 });
3931 cx.assert_editor_state(
3932 &r#"
3933 let x = "\"ˇ "
3934 "#
3935 .unindent(),
3936 );
3937
3938 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
3939 // mark. Nothing is inserted.
3940 cx.update_editor(|editor, cx| {
3941 editor.move_right(&Default::default(), cx);
3942 editor.handle_input("\"", cx);
3943 });
3944 cx.assert_editor_state(
3945 &r#"
3946 let x = "\" "ˇ
3947 "#
3948 .unindent(),
3949 );
3950}
3951
3952#[gpui::test]
3953async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3954 init_test(cx, |_| {});
3955
3956 let language = Arc::new(Language::new(
3957 LanguageConfig {
3958 brackets: BracketPairConfig {
3959 pairs: vec![
3960 BracketPair {
3961 start: "{".to_string(),
3962 end: "}".to_string(),
3963 close: true,
3964 newline: true,
3965 },
3966 BracketPair {
3967 start: "/* ".to_string(),
3968 end: "*/".to_string(),
3969 close: true,
3970 ..Default::default()
3971 },
3972 ],
3973 ..Default::default()
3974 },
3975 ..Default::default()
3976 },
3977 Some(tree_sitter_rust::language()),
3978 ));
3979
3980 let text = r#"
3981 a
3982 b
3983 c
3984 "#
3985 .unindent();
3986
3987 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3988 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3989 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3990 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3991 .await;
3992
3993 view.update(cx, |view, cx| {
3994 view.change_selections(None, cx, |s| {
3995 s.select_display_ranges([
3996 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3997 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3998 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3999 ])
4000 });
4001
4002 view.handle_input("{", cx);
4003 view.handle_input("{", cx);
4004 view.handle_input("{", cx);
4005 assert_eq!(
4006 view.text(cx),
4007 "
4008 {{{a}}}
4009 {{{b}}}
4010 {{{c}}}
4011 "
4012 .unindent()
4013 );
4014 assert_eq!(
4015 view.selections.display_ranges(cx),
4016 [
4017 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4018 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4019 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4020 ]
4021 );
4022
4023 view.undo(&Undo, cx);
4024 view.undo(&Undo, cx);
4025 view.undo(&Undo, cx);
4026 assert_eq!(
4027 view.text(cx),
4028 "
4029 a
4030 b
4031 c
4032 "
4033 .unindent()
4034 );
4035 assert_eq!(
4036 view.selections.display_ranges(cx),
4037 [
4038 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4039 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4040 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4041 ]
4042 );
4043
4044 // Ensure inserting the first character of a multi-byte bracket pair
4045 // doesn't surround the selections with the bracket.
4046 view.handle_input("/", cx);
4047 assert_eq!(
4048 view.text(cx),
4049 "
4050 /
4051 /
4052 /
4053 "
4054 .unindent()
4055 );
4056 assert_eq!(
4057 view.selections.display_ranges(cx),
4058 [
4059 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4060 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4061 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4062 ]
4063 );
4064
4065 view.undo(&Undo, cx);
4066 assert_eq!(
4067 view.text(cx),
4068 "
4069 a
4070 b
4071 c
4072 "
4073 .unindent()
4074 );
4075 assert_eq!(
4076 view.selections.display_ranges(cx),
4077 [
4078 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4079 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4080 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4081 ]
4082 );
4083
4084 // Ensure inserting the last character of a multi-byte bracket pair
4085 // doesn't surround the selections with the bracket.
4086 view.handle_input("*", cx);
4087 assert_eq!(
4088 view.text(cx),
4089 "
4090 *
4091 *
4092 *
4093 "
4094 .unindent()
4095 );
4096 assert_eq!(
4097 view.selections.display_ranges(cx),
4098 [
4099 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4100 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4101 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4102 ]
4103 );
4104 });
4105}
4106
4107#[gpui::test]
4108async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4109 init_test(cx, |_| {});
4110
4111 let language = Arc::new(Language::new(
4112 LanguageConfig {
4113 brackets: BracketPairConfig {
4114 pairs: vec![BracketPair {
4115 start: "{".to_string(),
4116 end: "}".to_string(),
4117 close: true,
4118 newline: true,
4119 }],
4120 ..Default::default()
4121 },
4122 autoclose_before: "}".to_string(),
4123 ..Default::default()
4124 },
4125 Some(tree_sitter_rust::language()),
4126 ));
4127
4128 let text = r#"
4129 a
4130 b
4131 c
4132 "#
4133 .unindent();
4134
4135 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4136 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4137 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4138 editor
4139 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4140 .await;
4141
4142 editor.update(cx, |editor, cx| {
4143 editor.change_selections(None, cx, |s| {
4144 s.select_ranges([
4145 Point::new(0, 1)..Point::new(0, 1),
4146 Point::new(1, 1)..Point::new(1, 1),
4147 Point::new(2, 1)..Point::new(2, 1),
4148 ])
4149 });
4150
4151 editor.handle_input("{", cx);
4152 editor.handle_input("{", cx);
4153 editor.handle_input("_", cx);
4154 assert_eq!(
4155 editor.text(cx),
4156 "
4157 a{{_}}
4158 b{{_}}
4159 c{{_}}
4160 "
4161 .unindent()
4162 );
4163 assert_eq!(
4164 editor.selections.ranges::<Point>(cx),
4165 [
4166 Point::new(0, 4)..Point::new(0, 4),
4167 Point::new(1, 4)..Point::new(1, 4),
4168 Point::new(2, 4)..Point::new(2, 4)
4169 ]
4170 );
4171
4172 editor.backspace(&Default::default(), cx);
4173 editor.backspace(&Default::default(), cx);
4174 assert_eq!(
4175 editor.text(cx),
4176 "
4177 a{}
4178 b{}
4179 c{}
4180 "
4181 .unindent()
4182 );
4183 assert_eq!(
4184 editor.selections.ranges::<Point>(cx),
4185 [
4186 Point::new(0, 2)..Point::new(0, 2),
4187 Point::new(1, 2)..Point::new(1, 2),
4188 Point::new(2, 2)..Point::new(2, 2)
4189 ]
4190 );
4191
4192 editor.delete_to_previous_word_start(&Default::default(), cx);
4193 assert_eq!(
4194 editor.text(cx),
4195 "
4196 a
4197 b
4198 c
4199 "
4200 .unindent()
4201 );
4202 assert_eq!(
4203 editor.selections.ranges::<Point>(cx),
4204 [
4205 Point::new(0, 1)..Point::new(0, 1),
4206 Point::new(1, 1)..Point::new(1, 1),
4207 Point::new(2, 1)..Point::new(2, 1)
4208 ]
4209 );
4210 });
4211}
4212
4213#[gpui::test]
4214async fn test_snippets(cx: &mut gpui::TestAppContext) {
4215 init_test(cx, |_| {});
4216
4217 let (text, insertion_ranges) = marked_text_ranges(
4218 indoc! {"
4219 a.ˇ b
4220 a.ˇ b
4221 a.ˇ b
4222 "},
4223 false,
4224 );
4225
4226 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4227 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4228
4229 editor.update(cx, |editor, cx| {
4230 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4231
4232 editor
4233 .insert_snippet(&insertion_ranges, snippet, cx)
4234 .unwrap();
4235
4236 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4237 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4238 assert_eq!(editor.text(cx), expected_text);
4239 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4240 }
4241
4242 assert(
4243 editor,
4244 cx,
4245 indoc! {"
4246 a.f(«one», two, «three») b
4247 a.f(«one», two, «three») b
4248 a.f(«one», two, «three») b
4249 "},
4250 );
4251
4252 // Can't move earlier than the first tab stop
4253 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4254 assert(
4255 editor,
4256 cx,
4257 indoc! {"
4258 a.f(«one», two, «three») b
4259 a.f(«one», two, «three») b
4260 a.f(«one», two, «three») b
4261 "},
4262 );
4263
4264 assert!(editor.move_to_next_snippet_tabstop(cx));
4265 assert(
4266 editor,
4267 cx,
4268 indoc! {"
4269 a.f(one, «two», three) b
4270 a.f(one, «two», three) b
4271 a.f(one, «two», three) b
4272 "},
4273 );
4274
4275 editor.move_to_prev_snippet_tabstop(cx);
4276 assert(
4277 editor,
4278 cx,
4279 indoc! {"
4280 a.f(«one», two, «three») b
4281 a.f(«one», two, «three») b
4282 a.f(«one», two, «three») b
4283 "},
4284 );
4285
4286 assert!(editor.move_to_next_snippet_tabstop(cx));
4287 assert(
4288 editor,
4289 cx,
4290 indoc! {"
4291 a.f(one, «two», three) b
4292 a.f(one, «two», three) b
4293 a.f(one, «two», three) b
4294 "},
4295 );
4296 assert!(editor.move_to_next_snippet_tabstop(cx));
4297 assert(
4298 editor,
4299 cx,
4300 indoc! {"
4301 a.f(one, two, three)ˇ b
4302 a.f(one, two, three)ˇ b
4303 a.f(one, two, three)ˇ b
4304 "},
4305 );
4306
4307 // As soon as the last tab stop is reached, snippet state is gone
4308 editor.move_to_prev_snippet_tabstop(cx);
4309 assert(
4310 editor,
4311 cx,
4312 indoc! {"
4313 a.f(one, two, three)ˇ b
4314 a.f(one, two, three)ˇ b
4315 a.f(one, two, three)ˇ b
4316 "},
4317 );
4318 });
4319}
4320
4321#[gpui::test]
4322async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4323 init_test(cx, |_| {});
4324
4325 let mut language = Language::new(
4326 LanguageConfig {
4327 name: "Rust".into(),
4328 path_suffixes: vec!["rs".to_string()],
4329 ..Default::default()
4330 },
4331 Some(tree_sitter_rust::language()),
4332 );
4333 let mut fake_servers = language
4334 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4335 capabilities: lsp::ServerCapabilities {
4336 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4337 ..Default::default()
4338 },
4339 ..Default::default()
4340 }))
4341 .await;
4342
4343 let fs = FakeFs::new(cx.background());
4344 fs.insert_file("/file.rs", Default::default()).await;
4345
4346 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4347 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4348 let buffer = project
4349 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4350 .await
4351 .unwrap();
4352
4353 cx.foreground().start_waiting();
4354 let fake_server = fake_servers.next().await.unwrap();
4355
4356 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4357 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4358 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4359 assert!(cx.read(|cx| editor.is_dirty(cx)));
4360
4361 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4362 fake_server
4363 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4364 assert_eq!(
4365 params.text_document.uri,
4366 lsp::Url::from_file_path("/file.rs").unwrap()
4367 );
4368 assert_eq!(params.options.tab_size, 4);
4369 Ok(Some(vec![lsp::TextEdit::new(
4370 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4371 ", ".to_string(),
4372 )]))
4373 })
4374 .next()
4375 .await;
4376 cx.foreground().start_waiting();
4377 save.await.unwrap();
4378 assert_eq!(
4379 editor.read_with(cx, |editor, cx| editor.text(cx)),
4380 "one, two\nthree\n"
4381 );
4382 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4383
4384 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4385 assert!(cx.read(|cx| editor.is_dirty(cx)));
4386
4387 // Ensure we can still save even if formatting hangs.
4388 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4389 assert_eq!(
4390 params.text_document.uri,
4391 lsp::Url::from_file_path("/file.rs").unwrap()
4392 );
4393 futures::future::pending::<()>().await;
4394 unreachable!()
4395 });
4396 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4397 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4398 cx.foreground().start_waiting();
4399 save.await.unwrap();
4400 assert_eq!(
4401 editor.read_with(cx, |editor, cx| editor.text(cx)),
4402 "one\ntwo\nthree\n"
4403 );
4404 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4405
4406 // Set rust language override and assert overridden tabsize is sent to language server
4407 update_test_settings(cx, |settings| {
4408 settings.languages.insert(
4409 "Rust".into(),
4410 LanguageSettingsContent {
4411 tab_size: NonZeroU32::new(8),
4412 ..Default::default()
4413 },
4414 );
4415 });
4416
4417 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4418 fake_server
4419 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4420 assert_eq!(
4421 params.text_document.uri,
4422 lsp::Url::from_file_path("/file.rs").unwrap()
4423 );
4424 assert_eq!(params.options.tab_size, 8);
4425 Ok(Some(vec![]))
4426 })
4427 .next()
4428 .await;
4429 cx.foreground().start_waiting();
4430 save.await.unwrap();
4431}
4432
4433#[gpui::test]
4434async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4435 init_test(cx, |_| {});
4436
4437 let mut language = Language::new(
4438 LanguageConfig {
4439 name: "Rust".into(),
4440 path_suffixes: vec!["rs".to_string()],
4441 ..Default::default()
4442 },
4443 Some(tree_sitter_rust::language()),
4444 );
4445 let mut fake_servers = language
4446 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4447 capabilities: lsp::ServerCapabilities {
4448 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4449 ..Default::default()
4450 },
4451 ..Default::default()
4452 }))
4453 .await;
4454
4455 let fs = FakeFs::new(cx.background());
4456 fs.insert_file("/file.rs", Default::default()).await;
4457
4458 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4459 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4460 let buffer = project
4461 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4462 .await
4463 .unwrap();
4464
4465 cx.foreground().start_waiting();
4466 let fake_server = fake_servers.next().await.unwrap();
4467
4468 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4469 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4470 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4471 assert!(cx.read(|cx| editor.is_dirty(cx)));
4472
4473 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4474 fake_server
4475 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4476 assert_eq!(
4477 params.text_document.uri,
4478 lsp::Url::from_file_path("/file.rs").unwrap()
4479 );
4480 assert_eq!(params.options.tab_size, 4);
4481 Ok(Some(vec![lsp::TextEdit::new(
4482 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4483 ", ".to_string(),
4484 )]))
4485 })
4486 .next()
4487 .await;
4488 cx.foreground().start_waiting();
4489 save.await.unwrap();
4490 assert_eq!(
4491 editor.read_with(cx, |editor, cx| editor.text(cx)),
4492 "one, two\nthree\n"
4493 );
4494 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4495
4496 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4497 assert!(cx.read(|cx| editor.is_dirty(cx)));
4498
4499 // Ensure we can still save even if formatting hangs.
4500 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4501 move |params, _| async move {
4502 assert_eq!(
4503 params.text_document.uri,
4504 lsp::Url::from_file_path("/file.rs").unwrap()
4505 );
4506 futures::future::pending::<()>().await;
4507 unreachable!()
4508 },
4509 );
4510 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4511 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4512 cx.foreground().start_waiting();
4513 save.await.unwrap();
4514 assert_eq!(
4515 editor.read_with(cx, |editor, cx| editor.text(cx)),
4516 "one\ntwo\nthree\n"
4517 );
4518 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4519
4520 // Set rust language override and assert overridden tabsize is sent to language server
4521 update_test_settings(cx, |settings| {
4522 settings.languages.insert(
4523 "Rust".into(),
4524 LanguageSettingsContent {
4525 tab_size: NonZeroU32::new(8),
4526 ..Default::default()
4527 },
4528 );
4529 });
4530
4531 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4532 fake_server
4533 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4534 assert_eq!(
4535 params.text_document.uri,
4536 lsp::Url::from_file_path("/file.rs").unwrap()
4537 );
4538 assert_eq!(params.options.tab_size, 8);
4539 Ok(Some(vec![]))
4540 })
4541 .next()
4542 .await;
4543 cx.foreground().start_waiting();
4544 save.await.unwrap();
4545}
4546
4547#[gpui::test]
4548async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4549 init_test(cx, |_| {});
4550
4551 let mut language = Language::new(
4552 LanguageConfig {
4553 name: "Rust".into(),
4554 path_suffixes: vec!["rs".to_string()],
4555 ..Default::default()
4556 },
4557 Some(tree_sitter_rust::language()),
4558 );
4559 let mut fake_servers = language
4560 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4561 capabilities: lsp::ServerCapabilities {
4562 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4563 ..Default::default()
4564 },
4565 ..Default::default()
4566 }))
4567 .await;
4568
4569 let fs = FakeFs::new(cx.background());
4570 fs.insert_file("/file.rs", Default::default()).await;
4571
4572 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4573 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4574 let buffer = project
4575 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4576 .await
4577 .unwrap();
4578
4579 cx.foreground().start_waiting();
4580 let fake_server = fake_servers.next().await.unwrap();
4581
4582 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4583 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4584 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4585
4586 let format = editor.update(cx, |editor, cx| {
4587 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4588 });
4589 fake_server
4590 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4591 assert_eq!(
4592 params.text_document.uri,
4593 lsp::Url::from_file_path("/file.rs").unwrap()
4594 );
4595 assert_eq!(params.options.tab_size, 4);
4596 Ok(Some(vec![lsp::TextEdit::new(
4597 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4598 ", ".to_string(),
4599 )]))
4600 })
4601 .next()
4602 .await;
4603 cx.foreground().start_waiting();
4604 format.await.unwrap();
4605 assert_eq!(
4606 editor.read_with(cx, |editor, cx| editor.text(cx)),
4607 "one, two\nthree\n"
4608 );
4609
4610 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4611 // Ensure we don't lock if formatting hangs.
4612 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4613 assert_eq!(
4614 params.text_document.uri,
4615 lsp::Url::from_file_path("/file.rs").unwrap()
4616 );
4617 futures::future::pending::<()>().await;
4618 unreachable!()
4619 });
4620 let format = editor.update(cx, |editor, cx| {
4621 editor.perform_format(project, FormatTrigger::Manual, cx)
4622 });
4623 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4624 cx.foreground().start_waiting();
4625 format.await.unwrap();
4626 assert_eq!(
4627 editor.read_with(cx, |editor, cx| editor.text(cx)),
4628 "one\ntwo\nthree\n"
4629 );
4630}
4631
4632#[gpui::test]
4633async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4634 init_test(cx, |_| {});
4635
4636 let mut cx = EditorLspTestContext::new_rust(
4637 lsp::ServerCapabilities {
4638 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4639 ..Default::default()
4640 },
4641 cx,
4642 )
4643 .await;
4644
4645 cx.set_state(indoc! {"
4646 one.twoˇ
4647 "});
4648
4649 // The format request takes a long time. When it completes, it inserts
4650 // a newline and an indent before the `.`
4651 cx.lsp
4652 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4653 let executor = cx.background();
4654 async move {
4655 executor.timer(Duration::from_millis(100)).await;
4656 Ok(Some(vec![lsp::TextEdit {
4657 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4658 new_text: "\n ".into(),
4659 }]))
4660 }
4661 });
4662
4663 // Submit a format request.
4664 let format_1 = cx
4665 .update_editor(|editor, cx| editor.format(&Format, cx))
4666 .unwrap();
4667 cx.foreground().run_until_parked();
4668
4669 // Submit a second format request.
4670 let format_2 = cx
4671 .update_editor(|editor, cx| editor.format(&Format, cx))
4672 .unwrap();
4673 cx.foreground().run_until_parked();
4674
4675 // Wait for both format requests to complete
4676 cx.foreground().advance_clock(Duration::from_millis(200));
4677 cx.foreground().start_waiting();
4678 format_1.await.unwrap();
4679 cx.foreground().start_waiting();
4680 format_2.await.unwrap();
4681
4682 // The formatting edits only happens once.
4683 cx.assert_editor_state(indoc! {"
4684 one
4685 .twoˇ
4686 "});
4687}
4688
4689#[gpui::test]
4690async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4691 init_test(cx, |_| {});
4692
4693 let mut cx = EditorLspTestContext::new_rust(
4694 lsp::ServerCapabilities {
4695 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4696 ..Default::default()
4697 },
4698 cx,
4699 )
4700 .await;
4701
4702 // Set up a buffer white some trailing whitespace and no trailing newline.
4703 cx.set_state(
4704 &[
4705 "one ", //
4706 "twoˇ", //
4707 "three ", //
4708 "four", //
4709 ]
4710 .join("\n"),
4711 );
4712
4713 // Submit a format request.
4714 let format = cx
4715 .update_editor(|editor, cx| editor.format(&Format, cx))
4716 .unwrap();
4717
4718 // Record which buffer changes have been sent to the language server
4719 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
4720 cx.lsp
4721 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
4722 let buffer_changes = buffer_changes.clone();
4723 move |params, _| {
4724 buffer_changes.lock().extend(
4725 params
4726 .content_changes
4727 .into_iter()
4728 .map(|e| (e.range.unwrap(), e.text)),
4729 );
4730 }
4731 });
4732
4733 // Handle formatting requests to the language server.
4734 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
4735 let buffer_changes = buffer_changes.clone();
4736 move |_, _| {
4737 // When formatting is requested, trailing whitespace has already been stripped,
4738 // and the trailing newline has already been added.
4739 assert_eq!(
4740 &buffer_changes.lock()[1..],
4741 &[
4742 (
4743 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
4744 "".into()
4745 ),
4746 (
4747 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
4748 "".into()
4749 ),
4750 (
4751 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
4752 "\n".into()
4753 ),
4754 ]
4755 );
4756
4757 // Insert blank lines between each line of the buffer.
4758 async move {
4759 Ok(Some(vec![
4760 lsp::TextEdit {
4761 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
4762 new_text: "\n".into(),
4763 },
4764 lsp::TextEdit {
4765 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
4766 new_text: "\n".into(),
4767 },
4768 ]))
4769 }
4770 }
4771 });
4772
4773 // After formatting the buffer, the trailing whitespace is stripped,
4774 // a newline is appended, and the edits provided by the language server
4775 // have been applied.
4776 format.await.unwrap();
4777 cx.assert_editor_state(
4778 &[
4779 "one", //
4780 "", //
4781 "twoˇ", //
4782 "", //
4783 "three", //
4784 "four", //
4785 "", //
4786 ]
4787 .join("\n"),
4788 );
4789
4790 // Undoing the formatting undoes the trailing whitespace removal, the
4791 // trailing newline, and the LSP edits.
4792 cx.update_buffer(|buffer, cx| buffer.undo(cx));
4793 cx.assert_editor_state(
4794 &[
4795 "one ", //
4796 "twoˇ", //
4797 "three ", //
4798 "four", //
4799 ]
4800 .join("\n"),
4801 );
4802}
4803
4804#[gpui::test]
4805async fn test_completion(cx: &mut gpui::TestAppContext) {
4806 init_test(cx, |_| {});
4807
4808 let mut cx = EditorLspTestContext::new_rust(
4809 lsp::ServerCapabilities {
4810 completion_provider: Some(lsp::CompletionOptions {
4811 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4812 ..Default::default()
4813 }),
4814 ..Default::default()
4815 },
4816 cx,
4817 )
4818 .await;
4819
4820 cx.set_state(indoc! {"
4821 oneˇ
4822 two
4823 three
4824 "});
4825 cx.simulate_keystroke(".");
4826 handle_completion_request(
4827 &mut cx,
4828 indoc! {"
4829 one.|<>
4830 two
4831 three
4832 "},
4833 vec!["first_completion", "second_completion"],
4834 )
4835 .await;
4836 cx.condition(|editor, _| editor.context_menu_visible())
4837 .await;
4838 let apply_additional_edits = cx.update_editor(|editor, cx| {
4839 editor.move_down(&MoveDown, cx);
4840 editor
4841 .confirm_completion(&ConfirmCompletion::default(), cx)
4842 .unwrap()
4843 });
4844 cx.assert_editor_state(indoc! {"
4845 one.second_completionˇ
4846 two
4847 three
4848 "});
4849
4850 handle_resolve_completion_request(
4851 &mut cx,
4852 Some(vec![
4853 (
4854 //This overlaps with the primary completion edit which is
4855 //misbehavior from the LSP spec, test that we filter it out
4856 indoc! {"
4857 one.second_ˇcompletion
4858 two
4859 threeˇ
4860 "},
4861 "overlapping additional edit",
4862 ),
4863 (
4864 indoc! {"
4865 one.second_completion
4866 two
4867 threeˇ
4868 "},
4869 "\nadditional edit",
4870 ),
4871 ]),
4872 )
4873 .await;
4874 apply_additional_edits.await.unwrap();
4875 cx.assert_editor_state(indoc! {"
4876 one.second_completionˇ
4877 two
4878 three
4879 additional edit
4880 "});
4881
4882 cx.set_state(indoc! {"
4883 one.second_completion
4884 twoˇ
4885 threeˇ
4886 additional edit
4887 "});
4888 cx.simulate_keystroke(" ");
4889 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4890 cx.simulate_keystroke("s");
4891 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4892
4893 cx.assert_editor_state(indoc! {"
4894 one.second_completion
4895 two sˇ
4896 three sˇ
4897 additional edit
4898 "});
4899 handle_completion_request(
4900 &mut cx,
4901 indoc! {"
4902 one.second_completion
4903 two s
4904 three <s|>
4905 additional edit
4906 "},
4907 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4908 )
4909 .await;
4910 cx.condition(|editor, _| editor.context_menu_visible())
4911 .await;
4912
4913 cx.simulate_keystroke("i");
4914
4915 handle_completion_request(
4916 &mut cx,
4917 indoc! {"
4918 one.second_completion
4919 two si
4920 three <si|>
4921 additional edit
4922 "},
4923 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4924 )
4925 .await;
4926 cx.condition(|editor, _| editor.context_menu_visible())
4927 .await;
4928
4929 let apply_additional_edits = cx.update_editor(|editor, cx| {
4930 editor
4931 .confirm_completion(&ConfirmCompletion::default(), cx)
4932 .unwrap()
4933 });
4934 cx.assert_editor_state(indoc! {"
4935 one.second_completion
4936 two sixth_completionˇ
4937 three sixth_completionˇ
4938 additional edit
4939 "});
4940
4941 handle_resolve_completion_request(&mut cx, None).await;
4942 apply_additional_edits.await.unwrap();
4943
4944 cx.update(|cx| {
4945 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
4946 settings.update_user_settings::<EditorSettings>(cx, |settings| {
4947 settings.show_completions_on_input = Some(false);
4948 });
4949 })
4950 });
4951 cx.set_state("editorˇ");
4952 cx.simulate_keystroke(".");
4953 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4954 cx.simulate_keystroke("c");
4955 cx.simulate_keystroke("l");
4956 cx.simulate_keystroke("o");
4957 cx.assert_editor_state("editor.cloˇ");
4958 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4959 cx.update_editor(|editor, cx| {
4960 editor.show_completions(&ShowCompletions, cx);
4961 });
4962 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4963 cx.condition(|editor, _| editor.context_menu_visible())
4964 .await;
4965 let apply_additional_edits = cx.update_editor(|editor, cx| {
4966 editor
4967 .confirm_completion(&ConfirmCompletion::default(), cx)
4968 .unwrap()
4969 });
4970 cx.assert_editor_state("editor.closeˇ");
4971 handle_resolve_completion_request(&mut cx, None).await;
4972 apply_additional_edits.await.unwrap();
4973}
4974
4975#[gpui::test]
4976async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4977 init_test(cx, |_| {});
4978 let mut cx = EditorTestContext::new(cx).await;
4979 let language = Arc::new(Language::new(
4980 LanguageConfig {
4981 line_comment: Some("// ".into()),
4982 ..Default::default()
4983 },
4984 Some(tree_sitter_rust::language()),
4985 ));
4986 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4987
4988 // If multiple selections intersect a line, the line is only toggled once.
4989 cx.set_state(indoc! {"
4990 fn a() {
4991 «//b();
4992 ˇ»// «c();
4993 //ˇ» d();
4994 }
4995 "});
4996
4997 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
4998
4999 cx.assert_editor_state(indoc! {"
5000 fn a() {
5001 «b();
5002 c();
5003 ˇ» d();
5004 }
5005 "});
5006
5007 // The comment prefix is inserted at the same column for every line in a
5008 // selection.
5009 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5010
5011 cx.assert_editor_state(indoc! {"
5012 fn a() {
5013 // «b();
5014 // c();
5015 ˇ»// d();
5016 }
5017 "});
5018
5019 // If a selection ends at the beginning of a line, that line is not toggled.
5020 cx.set_selections_state(indoc! {"
5021 fn a() {
5022 // b();
5023 «// c();
5024 ˇ» // d();
5025 }
5026 "});
5027
5028 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5029
5030 cx.assert_editor_state(indoc! {"
5031 fn a() {
5032 // b();
5033 «c();
5034 ˇ» // d();
5035 }
5036 "});
5037
5038 // If a selection span a single line and is empty, the line is toggled.
5039 cx.set_state(indoc! {"
5040 fn a() {
5041 a();
5042 b();
5043 ˇ
5044 }
5045 "});
5046
5047 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5048
5049 cx.assert_editor_state(indoc! {"
5050 fn a() {
5051 a();
5052 b();
5053 //•ˇ
5054 }
5055 "});
5056
5057 // If a selection span multiple lines, empty lines are not toggled.
5058 cx.set_state(indoc! {"
5059 fn a() {
5060 «a();
5061
5062 c();ˇ»
5063 }
5064 "});
5065
5066 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5067
5068 cx.assert_editor_state(indoc! {"
5069 fn a() {
5070 // «a();
5071
5072 // c();ˇ»
5073 }
5074 "});
5075}
5076
5077#[gpui::test]
5078async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5079 init_test(cx, |_| {});
5080
5081 let language = Arc::new(Language::new(
5082 LanguageConfig {
5083 line_comment: Some("// ".into()),
5084 ..Default::default()
5085 },
5086 Some(tree_sitter_rust::language()),
5087 ));
5088
5089 let registry = Arc::new(LanguageRegistry::test());
5090 registry.add(language.clone());
5091
5092 let mut cx = EditorTestContext::new(cx).await;
5093 cx.update_buffer(|buffer, cx| {
5094 buffer.set_language_registry(registry);
5095 buffer.set_language(Some(language), cx);
5096 });
5097
5098 let toggle_comments = &ToggleComments {
5099 advance_downwards: true,
5100 };
5101
5102 // Single cursor on one line -> advance
5103 // Cursor moves horizontally 3 characters as well on non-blank line
5104 cx.set_state(indoc!(
5105 "fn a() {
5106 ˇdog();
5107 cat();
5108 }"
5109 ));
5110 cx.update_editor(|editor, cx| {
5111 editor.toggle_comments(toggle_comments, cx);
5112 });
5113 cx.assert_editor_state(indoc!(
5114 "fn a() {
5115 // dog();
5116 catˇ();
5117 }"
5118 ));
5119
5120 // Single selection on one line -> don't advance
5121 cx.set_state(indoc!(
5122 "fn a() {
5123 «dog()ˇ»;
5124 cat();
5125 }"
5126 ));
5127 cx.update_editor(|editor, cx| {
5128 editor.toggle_comments(toggle_comments, cx);
5129 });
5130 cx.assert_editor_state(indoc!(
5131 "fn a() {
5132 // «dog()ˇ»;
5133 cat();
5134 }"
5135 ));
5136
5137 // Multiple cursors on one line -> advance
5138 cx.set_state(indoc!(
5139 "fn a() {
5140 ˇdˇog();
5141 cat();
5142 }"
5143 ));
5144 cx.update_editor(|editor, cx| {
5145 editor.toggle_comments(toggle_comments, cx);
5146 });
5147 cx.assert_editor_state(indoc!(
5148 "fn a() {
5149 // dog();
5150 catˇ(ˇ);
5151 }"
5152 ));
5153
5154 // Multiple cursors on one line, with selection -> don't advance
5155 cx.set_state(indoc!(
5156 "fn a() {
5157 ˇdˇog«()ˇ»;
5158 cat();
5159 }"
5160 ));
5161 cx.update_editor(|editor, cx| {
5162 editor.toggle_comments(toggle_comments, cx);
5163 });
5164 cx.assert_editor_state(indoc!(
5165 "fn a() {
5166 // ˇdˇog«()ˇ»;
5167 cat();
5168 }"
5169 ));
5170
5171 // Single cursor on one line -> advance
5172 // Cursor moves to column 0 on blank line
5173 cx.set_state(indoc!(
5174 "fn a() {
5175 ˇdog();
5176
5177 cat();
5178 }"
5179 ));
5180 cx.update_editor(|editor, cx| {
5181 editor.toggle_comments(toggle_comments, cx);
5182 });
5183 cx.assert_editor_state(indoc!(
5184 "fn a() {
5185 // dog();
5186 ˇ
5187 cat();
5188 }"
5189 ));
5190
5191 // Single cursor on one line -> advance
5192 // Cursor starts and ends at column 0
5193 cx.set_state(indoc!(
5194 "fn a() {
5195 ˇ dog();
5196 cat();
5197 }"
5198 ));
5199 cx.update_editor(|editor, cx| {
5200 editor.toggle_comments(toggle_comments, cx);
5201 });
5202 cx.assert_editor_state(indoc!(
5203 "fn a() {
5204 // dog();
5205 ˇ cat();
5206 }"
5207 ));
5208}
5209
5210#[gpui::test]
5211async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5212 init_test(cx, |_| {});
5213
5214 let mut cx = EditorTestContext::new(cx).await;
5215
5216 let html_language = Arc::new(
5217 Language::new(
5218 LanguageConfig {
5219 name: "HTML".into(),
5220 block_comment: Some(("<!-- ".into(), " -->".into())),
5221 ..Default::default()
5222 },
5223 Some(tree_sitter_html::language()),
5224 )
5225 .with_injection_query(
5226 r#"
5227 (script_element
5228 (raw_text) @content
5229 (#set! "language" "javascript"))
5230 "#,
5231 )
5232 .unwrap(),
5233 );
5234
5235 let javascript_language = Arc::new(Language::new(
5236 LanguageConfig {
5237 name: "JavaScript".into(),
5238 line_comment: Some("// ".into()),
5239 ..Default::default()
5240 },
5241 Some(tree_sitter_javascript::language()),
5242 ));
5243
5244 let registry = Arc::new(LanguageRegistry::test());
5245 registry.add(html_language.clone());
5246 registry.add(javascript_language.clone());
5247
5248 cx.update_buffer(|buffer, cx| {
5249 buffer.set_language_registry(registry);
5250 buffer.set_language(Some(html_language), cx);
5251 });
5252
5253 // Toggle comments for empty selections
5254 cx.set_state(
5255 &r#"
5256 <p>A</p>ˇ
5257 <p>B</p>ˇ
5258 <p>C</p>ˇ
5259 "#
5260 .unindent(),
5261 );
5262 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5263 cx.assert_editor_state(
5264 &r#"
5265 <!-- <p>A</p>ˇ -->
5266 <!-- <p>B</p>ˇ -->
5267 <!-- <p>C</p>ˇ -->
5268 "#
5269 .unindent(),
5270 );
5271 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5272 cx.assert_editor_state(
5273 &r#"
5274 <p>A</p>ˇ
5275 <p>B</p>ˇ
5276 <p>C</p>ˇ
5277 "#
5278 .unindent(),
5279 );
5280
5281 // Toggle comments for mixture of empty and non-empty selections, where
5282 // multiple selections occupy a given line.
5283 cx.set_state(
5284 &r#"
5285 <p>A«</p>
5286 <p>ˇ»B</p>ˇ
5287 <p>C«</p>
5288 <p>ˇ»D</p>ˇ
5289 "#
5290 .unindent(),
5291 );
5292
5293 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5294 cx.assert_editor_state(
5295 &r#"
5296 <!-- <p>A«</p>
5297 <p>ˇ»B</p>ˇ -->
5298 <!-- <p>C«</p>
5299 <p>ˇ»D</p>ˇ -->
5300 "#
5301 .unindent(),
5302 );
5303 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5304 cx.assert_editor_state(
5305 &r#"
5306 <p>A«</p>
5307 <p>ˇ»B</p>ˇ
5308 <p>C«</p>
5309 <p>ˇ»D</p>ˇ
5310 "#
5311 .unindent(),
5312 );
5313
5314 // Toggle comments when different languages are active for different
5315 // selections.
5316 cx.set_state(
5317 &r#"
5318 ˇ<script>
5319 ˇvar x = new Y();
5320 ˇ</script>
5321 "#
5322 .unindent(),
5323 );
5324 cx.foreground().run_until_parked();
5325 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5326 cx.assert_editor_state(
5327 &r#"
5328 <!-- ˇ<script> -->
5329 // ˇvar x = new Y();
5330 <!-- ˇ</script> -->
5331 "#
5332 .unindent(),
5333 );
5334}
5335
5336#[gpui::test]
5337fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5338 init_test(cx, |_| {});
5339
5340 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5341 let multibuffer = cx.add_model(|cx| {
5342 let mut multibuffer = MultiBuffer::new(0);
5343 multibuffer.push_excerpts(
5344 buffer.clone(),
5345 [
5346 ExcerptRange {
5347 context: Point::new(0, 0)..Point::new(0, 4),
5348 primary: None,
5349 },
5350 ExcerptRange {
5351 context: Point::new(1, 0)..Point::new(1, 4),
5352 primary: None,
5353 },
5354 ],
5355 cx,
5356 );
5357 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5358 multibuffer
5359 });
5360
5361 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5362 view.update(cx, |view, cx| {
5363 assert_eq!(view.text(cx), "aaaa\nbbbb");
5364 view.change_selections(None, cx, |s| {
5365 s.select_ranges([
5366 Point::new(0, 0)..Point::new(0, 0),
5367 Point::new(1, 0)..Point::new(1, 0),
5368 ])
5369 });
5370
5371 view.handle_input("X", cx);
5372 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5373 assert_eq!(
5374 view.selections.ranges(cx),
5375 [
5376 Point::new(0, 1)..Point::new(0, 1),
5377 Point::new(1, 1)..Point::new(1, 1),
5378 ]
5379 );
5380
5381 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5382 view.change_selections(None, cx, |s| {
5383 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5384 });
5385 view.backspace(&Default::default(), cx);
5386 assert_eq!(view.text(cx), "Xa\nbbb");
5387 assert_eq!(
5388 view.selections.ranges(cx),
5389 [Point::new(1, 0)..Point::new(1, 0)]
5390 );
5391
5392 view.change_selections(None, cx, |s| {
5393 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5394 });
5395 view.backspace(&Default::default(), cx);
5396 assert_eq!(view.text(cx), "X\nbb");
5397 assert_eq!(
5398 view.selections.ranges(cx),
5399 [Point::new(0, 1)..Point::new(0, 1)]
5400 );
5401 });
5402}
5403
5404#[gpui::test]
5405fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5406 init_test(cx, |_| {});
5407
5408 let markers = vec![('[', ']').into(), ('(', ')').into()];
5409 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5410 indoc! {"
5411 [aaaa
5412 (bbbb]
5413 cccc)",
5414 },
5415 markers.clone(),
5416 );
5417 let excerpt_ranges = markers.into_iter().map(|marker| {
5418 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5419 ExcerptRange {
5420 context,
5421 primary: None,
5422 }
5423 });
5424 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5425 let multibuffer = cx.add_model(|cx| {
5426 let mut multibuffer = MultiBuffer::new(0);
5427 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5428 multibuffer
5429 });
5430
5431 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5432 view.update(cx, |view, cx| {
5433 let (expected_text, selection_ranges) = marked_text_ranges(
5434 indoc! {"
5435 aaaa
5436 bˇbbb
5437 bˇbbˇb
5438 cccc"
5439 },
5440 true,
5441 );
5442 assert_eq!(view.text(cx), expected_text);
5443 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5444
5445 view.handle_input("X", cx);
5446
5447 let (expected_text, expected_selections) = marked_text_ranges(
5448 indoc! {"
5449 aaaa
5450 bXˇbbXb
5451 bXˇbbXˇb
5452 cccc"
5453 },
5454 false,
5455 );
5456 assert_eq!(view.text(cx), expected_text);
5457 assert_eq!(view.selections.ranges(cx), expected_selections);
5458
5459 view.newline(&Newline, cx);
5460 let (expected_text, expected_selections) = marked_text_ranges(
5461 indoc! {"
5462 aaaa
5463 bX
5464 ˇbbX
5465 b
5466 bX
5467 ˇbbX
5468 ˇb
5469 cccc"
5470 },
5471 false,
5472 );
5473 assert_eq!(view.text(cx), expected_text);
5474 assert_eq!(view.selections.ranges(cx), expected_selections);
5475 });
5476}
5477
5478#[gpui::test]
5479fn test_refresh_selections(cx: &mut TestAppContext) {
5480 init_test(cx, |_| {});
5481
5482 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5483 let mut excerpt1_id = None;
5484 let multibuffer = cx.add_model(|cx| {
5485 let mut multibuffer = MultiBuffer::new(0);
5486 excerpt1_id = multibuffer
5487 .push_excerpts(
5488 buffer.clone(),
5489 [
5490 ExcerptRange {
5491 context: Point::new(0, 0)..Point::new(1, 4),
5492 primary: None,
5493 },
5494 ExcerptRange {
5495 context: Point::new(1, 0)..Point::new(2, 4),
5496 primary: None,
5497 },
5498 ],
5499 cx,
5500 )
5501 .into_iter()
5502 .next();
5503 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5504 multibuffer
5505 });
5506
5507 let (_, editor) = cx.add_window(|cx| {
5508 let mut editor = build_editor(multibuffer.clone(), cx);
5509 let snapshot = editor.snapshot(cx);
5510 editor.change_selections(None, cx, |s| {
5511 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5512 });
5513 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5514 assert_eq!(
5515 editor.selections.ranges(cx),
5516 [
5517 Point::new(1, 3)..Point::new(1, 3),
5518 Point::new(2, 1)..Point::new(2, 1),
5519 ]
5520 );
5521 editor
5522 });
5523
5524 // Refreshing selections is a no-op when excerpts haven't changed.
5525 editor.update(cx, |editor, cx| {
5526 editor.change_selections(None, cx, |s| s.refresh());
5527 assert_eq!(
5528 editor.selections.ranges(cx),
5529 [
5530 Point::new(1, 3)..Point::new(1, 3),
5531 Point::new(2, 1)..Point::new(2, 1),
5532 ]
5533 );
5534 });
5535
5536 multibuffer.update(cx, |multibuffer, cx| {
5537 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5538 });
5539 editor.update(cx, |editor, cx| {
5540 // Removing an excerpt causes the first selection to become degenerate.
5541 assert_eq!(
5542 editor.selections.ranges(cx),
5543 [
5544 Point::new(0, 0)..Point::new(0, 0),
5545 Point::new(0, 1)..Point::new(0, 1)
5546 ]
5547 );
5548
5549 // Refreshing selections will relocate the first selection to the original buffer
5550 // location.
5551 editor.change_selections(None, cx, |s| s.refresh());
5552 assert_eq!(
5553 editor.selections.ranges(cx),
5554 [
5555 Point::new(0, 1)..Point::new(0, 1),
5556 Point::new(0, 3)..Point::new(0, 3)
5557 ]
5558 );
5559 assert!(editor.selections.pending_anchor().is_some());
5560 });
5561}
5562
5563#[gpui::test]
5564fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5565 init_test(cx, |_| {});
5566
5567 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5568 let mut excerpt1_id = None;
5569 let multibuffer = cx.add_model(|cx| {
5570 let mut multibuffer = MultiBuffer::new(0);
5571 excerpt1_id = multibuffer
5572 .push_excerpts(
5573 buffer.clone(),
5574 [
5575 ExcerptRange {
5576 context: Point::new(0, 0)..Point::new(1, 4),
5577 primary: None,
5578 },
5579 ExcerptRange {
5580 context: Point::new(1, 0)..Point::new(2, 4),
5581 primary: None,
5582 },
5583 ],
5584 cx,
5585 )
5586 .into_iter()
5587 .next();
5588 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5589 multibuffer
5590 });
5591
5592 let (_, editor) = cx.add_window(|cx| {
5593 let mut editor = build_editor(multibuffer.clone(), cx);
5594 let snapshot = editor.snapshot(cx);
5595 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5596 assert_eq!(
5597 editor.selections.ranges(cx),
5598 [Point::new(1, 3)..Point::new(1, 3)]
5599 );
5600 editor
5601 });
5602
5603 multibuffer.update(cx, |multibuffer, cx| {
5604 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5605 });
5606 editor.update(cx, |editor, cx| {
5607 assert_eq!(
5608 editor.selections.ranges(cx),
5609 [Point::new(0, 0)..Point::new(0, 0)]
5610 );
5611
5612 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5613 editor.change_selections(None, cx, |s| s.refresh());
5614 assert_eq!(
5615 editor.selections.ranges(cx),
5616 [Point::new(0, 3)..Point::new(0, 3)]
5617 );
5618 assert!(editor.selections.pending_anchor().is_some());
5619 });
5620}
5621
5622#[gpui::test]
5623async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5624 init_test(cx, |_| {});
5625
5626 let language = Arc::new(
5627 Language::new(
5628 LanguageConfig {
5629 brackets: BracketPairConfig {
5630 pairs: vec![
5631 BracketPair {
5632 start: "{".to_string(),
5633 end: "}".to_string(),
5634 close: true,
5635 newline: true,
5636 },
5637 BracketPair {
5638 start: "/* ".to_string(),
5639 end: " */".to_string(),
5640 close: true,
5641 newline: true,
5642 },
5643 ],
5644 ..Default::default()
5645 },
5646 ..Default::default()
5647 },
5648 Some(tree_sitter_rust::language()),
5649 )
5650 .with_indents_query("")
5651 .unwrap(),
5652 );
5653
5654 let text = concat!(
5655 "{ }\n", //
5656 " x\n", //
5657 " /* */\n", //
5658 "x\n", //
5659 "{{} }\n", //
5660 );
5661
5662 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5663 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5664 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5665 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5666 .await;
5667
5668 view.update(cx, |view, cx| {
5669 view.change_selections(None, cx, |s| {
5670 s.select_display_ranges([
5671 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5672 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5673 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5674 ])
5675 });
5676 view.newline(&Newline, cx);
5677
5678 assert_eq!(
5679 view.buffer().read(cx).read(cx).text(),
5680 concat!(
5681 "{ \n", // Suppress rustfmt
5682 "\n", //
5683 "}\n", //
5684 " x\n", //
5685 " /* \n", //
5686 " \n", //
5687 " */\n", //
5688 "x\n", //
5689 "{{} \n", //
5690 "}\n", //
5691 )
5692 );
5693 });
5694}
5695
5696#[gpui::test]
5697fn test_highlighted_ranges(cx: &mut TestAppContext) {
5698 init_test(cx, |_| {});
5699
5700 let (_, editor) = cx.add_window(|cx| {
5701 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5702 build_editor(buffer.clone(), cx)
5703 });
5704
5705 editor.update(cx, |editor, cx| {
5706 struct Type1;
5707 struct Type2;
5708
5709 let buffer = editor.buffer.read(cx).snapshot(cx);
5710
5711 let anchor_range =
5712 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5713
5714 editor.highlight_background::<Type1>(
5715 vec![
5716 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5717 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5718 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5719 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5720 ],
5721 |_| Color::red(),
5722 cx,
5723 );
5724 editor.highlight_background::<Type2>(
5725 vec![
5726 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5727 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5728 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5729 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5730 ],
5731 |_| Color::green(),
5732 cx,
5733 );
5734
5735 let snapshot = editor.snapshot(cx);
5736 let mut highlighted_ranges = editor.background_highlights_in_range(
5737 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5738 &snapshot,
5739 theme::current(cx).as_ref(),
5740 );
5741 // Enforce a consistent ordering based on color without relying on the ordering of the
5742 // highlight's `TypeId` which is non-deterministic.
5743 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5744 assert_eq!(
5745 highlighted_ranges,
5746 &[
5747 (
5748 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5749 Color::green(),
5750 ),
5751 (
5752 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5753 Color::green(),
5754 ),
5755 (
5756 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5757 Color::red(),
5758 ),
5759 (
5760 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5761 Color::red(),
5762 ),
5763 ]
5764 );
5765 assert_eq!(
5766 editor.background_highlights_in_range(
5767 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5768 &snapshot,
5769 theme::current(cx).as_ref(),
5770 ),
5771 &[(
5772 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5773 Color::red(),
5774 )]
5775 );
5776 });
5777}
5778
5779#[gpui::test]
5780async fn test_following(cx: &mut gpui::TestAppContext) {
5781 init_test(cx, |_| {});
5782
5783 let fs = FakeFs::new(cx.background());
5784 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5785
5786 let buffer = project.update(cx, |project, cx| {
5787 let buffer = project
5788 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5789 .unwrap();
5790 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5791 });
5792 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5793 let (_, follower) = cx.update(|cx| {
5794 cx.add_window(
5795 WindowOptions {
5796 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5797 ..Default::default()
5798 },
5799 |cx| build_editor(buffer.clone(), cx),
5800 )
5801 });
5802
5803 let is_still_following = Rc::new(RefCell::new(true));
5804 let follower_edit_event_count = Rc::new(RefCell::new(0));
5805 let pending_update = Rc::new(RefCell::new(None));
5806 follower.update(cx, {
5807 let update = pending_update.clone();
5808 let is_still_following = is_still_following.clone();
5809 let follower_edit_event_count = follower_edit_event_count.clone();
5810 |_, cx| {
5811 cx.subscribe(&leader, move |_, leader, event, cx| {
5812 leader
5813 .read(cx)
5814 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5815 })
5816 .detach();
5817
5818 cx.subscribe(&follower, move |_, _, event, cx| {
5819 if Editor::should_unfollow_on_event(event, cx) {
5820 *is_still_following.borrow_mut() = false;
5821 }
5822 if let Event::BufferEdited = event {
5823 *follower_edit_event_count.borrow_mut() += 1;
5824 }
5825 })
5826 .detach();
5827 }
5828 });
5829
5830 // Update the selections only
5831 leader.update(cx, |leader, cx| {
5832 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5833 });
5834 follower
5835 .update(cx, |follower, cx| {
5836 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5837 })
5838 .await
5839 .unwrap();
5840 follower.read_with(cx, |follower, cx| {
5841 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5842 });
5843 assert_eq!(*is_still_following.borrow(), true);
5844 assert_eq!(*follower_edit_event_count.borrow(), 0);
5845
5846 // Update the scroll position only
5847 leader.update(cx, |leader, cx| {
5848 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5849 });
5850 follower
5851 .update(cx, |follower, cx| {
5852 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5853 })
5854 .await
5855 .unwrap();
5856 assert_eq!(
5857 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5858 vec2f(1.5, 3.5)
5859 );
5860 assert_eq!(*is_still_following.borrow(), true);
5861 assert_eq!(*follower_edit_event_count.borrow(), 0);
5862
5863 // Update the selections and scroll position. The follower's scroll position is updated
5864 // via autoscroll, not via the leader's exact scroll position.
5865 leader.update(cx, |leader, cx| {
5866 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5867 leader.request_autoscroll(Autoscroll::newest(), cx);
5868 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5869 });
5870 follower
5871 .update(cx, |follower, cx| {
5872 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5873 })
5874 .await
5875 .unwrap();
5876 follower.update(cx, |follower, cx| {
5877 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5878 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5879 });
5880 assert_eq!(*is_still_following.borrow(), true);
5881
5882 // Creating a pending selection that precedes another selection
5883 leader.update(cx, |leader, cx| {
5884 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5885 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5886 });
5887 follower
5888 .update(cx, |follower, cx| {
5889 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5890 })
5891 .await
5892 .unwrap();
5893 follower.read_with(cx, |follower, cx| {
5894 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5895 });
5896 assert_eq!(*is_still_following.borrow(), true);
5897
5898 // Extend the pending selection so that it surrounds another selection
5899 leader.update(cx, |leader, cx| {
5900 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5901 });
5902 follower
5903 .update(cx, |follower, cx| {
5904 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5905 })
5906 .await
5907 .unwrap();
5908 follower.read_with(cx, |follower, cx| {
5909 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5910 });
5911
5912 // Scrolling locally breaks the follow
5913 follower.update(cx, |follower, cx| {
5914 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5915 follower.set_scroll_anchor(
5916 ScrollAnchor {
5917 anchor: top_anchor,
5918 offset: vec2f(0.0, 0.5),
5919 },
5920 cx,
5921 );
5922 });
5923 assert_eq!(*is_still_following.borrow(), false);
5924}
5925
5926#[gpui::test]
5927async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5928 init_test(cx, |_| {});
5929
5930 let fs = FakeFs::new(cx.background());
5931 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5932 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5933 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5934
5935 let leader = pane.update(cx, |_, cx| {
5936 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5937 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5938 });
5939
5940 // Start following the editor when it has no excerpts.
5941 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5942 let follower_1 = cx
5943 .update(|cx| {
5944 Editor::from_state_proto(
5945 pane.clone(),
5946 project.clone(),
5947 ViewId {
5948 creator: Default::default(),
5949 id: 0,
5950 },
5951 &mut state_message,
5952 cx,
5953 )
5954 })
5955 .unwrap()
5956 .await
5957 .unwrap();
5958
5959 let update_message = Rc::new(RefCell::new(None));
5960 follower_1.update(cx, {
5961 let update = update_message.clone();
5962 |_, cx| {
5963 cx.subscribe(&leader, move |_, leader, event, cx| {
5964 leader
5965 .read(cx)
5966 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5967 })
5968 .detach();
5969 }
5970 });
5971
5972 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5973 (
5974 project
5975 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5976 .unwrap(),
5977 project
5978 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5979 .unwrap(),
5980 )
5981 });
5982
5983 // Insert some excerpts.
5984 leader.update(cx, |leader, cx| {
5985 leader.buffer.update(cx, |multibuffer, cx| {
5986 let excerpt_ids = multibuffer.push_excerpts(
5987 buffer_1.clone(),
5988 [
5989 ExcerptRange {
5990 context: 1..6,
5991 primary: None,
5992 },
5993 ExcerptRange {
5994 context: 12..15,
5995 primary: None,
5996 },
5997 ExcerptRange {
5998 context: 0..3,
5999 primary: None,
6000 },
6001 ],
6002 cx,
6003 );
6004 multibuffer.insert_excerpts_after(
6005 excerpt_ids[0],
6006 buffer_2.clone(),
6007 [
6008 ExcerptRange {
6009 context: 8..12,
6010 primary: None,
6011 },
6012 ExcerptRange {
6013 context: 0..6,
6014 primary: None,
6015 },
6016 ],
6017 cx,
6018 );
6019 });
6020 });
6021
6022 // Apply the update of adding the excerpts.
6023 follower_1
6024 .update(cx, |follower, cx| {
6025 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6026 })
6027 .await
6028 .unwrap();
6029 assert_eq!(
6030 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6031 leader.read_with(cx, |editor, cx| editor.text(cx))
6032 );
6033 update_message.borrow_mut().take();
6034
6035 // Start following separately after it already has excerpts.
6036 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6037 let follower_2 = cx
6038 .update(|cx| {
6039 Editor::from_state_proto(
6040 pane.clone(),
6041 project.clone(),
6042 ViewId {
6043 creator: Default::default(),
6044 id: 0,
6045 },
6046 &mut state_message,
6047 cx,
6048 )
6049 })
6050 .unwrap()
6051 .await
6052 .unwrap();
6053 assert_eq!(
6054 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6055 leader.read_with(cx, |editor, cx| editor.text(cx))
6056 );
6057
6058 // Remove some excerpts.
6059 leader.update(cx, |leader, cx| {
6060 leader.buffer.update(cx, |multibuffer, cx| {
6061 let excerpt_ids = multibuffer.excerpt_ids();
6062 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6063 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6064 });
6065 });
6066
6067 // Apply the update of removing the excerpts.
6068 follower_1
6069 .update(cx, |follower, cx| {
6070 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6071 })
6072 .await
6073 .unwrap();
6074 follower_2
6075 .update(cx, |follower, cx| {
6076 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6077 })
6078 .await
6079 .unwrap();
6080 update_message.borrow_mut().take();
6081 assert_eq!(
6082 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6083 leader.read_with(cx, |editor, cx| editor.text(cx))
6084 );
6085}
6086
6087#[test]
6088fn test_combine_syntax_and_fuzzy_match_highlights() {
6089 let string = "abcdefghijklmnop";
6090 let syntax_ranges = [
6091 (
6092 0..3,
6093 HighlightStyle {
6094 color: Some(Color::red()),
6095 ..Default::default()
6096 },
6097 ),
6098 (
6099 4..8,
6100 HighlightStyle {
6101 color: Some(Color::green()),
6102 ..Default::default()
6103 },
6104 ),
6105 ];
6106 let match_indices = [4, 6, 7, 8];
6107 assert_eq!(
6108 combine_syntax_and_fuzzy_match_highlights(
6109 string,
6110 Default::default(),
6111 syntax_ranges.into_iter(),
6112 &match_indices,
6113 ),
6114 &[
6115 (
6116 0..3,
6117 HighlightStyle {
6118 color: Some(Color::red()),
6119 ..Default::default()
6120 },
6121 ),
6122 (
6123 4..5,
6124 HighlightStyle {
6125 color: Some(Color::green()),
6126 weight: Some(fonts::Weight::BOLD),
6127 ..Default::default()
6128 },
6129 ),
6130 (
6131 5..6,
6132 HighlightStyle {
6133 color: Some(Color::green()),
6134 ..Default::default()
6135 },
6136 ),
6137 (
6138 6..8,
6139 HighlightStyle {
6140 color: Some(Color::green()),
6141 weight: Some(fonts::Weight::BOLD),
6142 ..Default::default()
6143 },
6144 ),
6145 (
6146 8..9,
6147 HighlightStyle {
6148 weight: Some(fonts::Weight::BOLD),
6149 ..Default::default()
6150 },
6151 ),
6152 ]
6153 );
6154}
6155
6156#[gpui::test]
6157async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6158 init_test(cx, |_| {});
6159
6160 let mut cx = EditorTestContext::new(cx).await;
6161
6162 let diff_base = r#"
6163 use some::mod;
6164
6165 const A: u32 = 42;
6166
6167 fn main() {
6168 println!("hello");
6169
6170 println!("world");
6171 }
6172 "#
6173 .unindent();
6174
6175 // Edits are modified, removed, modified, added
6176 cx.set_state(
6177 &r#"
6178 use some::modified;
6179
6180 ˇ
6181 fn main() {
6182 println!("hello there");
6183
6184 println!("around the");
6185 println!("world");
6186 }
6187 "#
6188 .unindent(),
6189 );
6190
6191 cx.set_diff_base(Some(&diff_base));
6192 deterministic.run_until_parked();
6193
6194 cx.update_editor(|editor, cx| {
6195 //Wrap around the bottom of the buffer
6196 for _ in 0..3 {
6197 editor.go_to_hunk(&GoToHunk, cx);
6198 }
6199 });
6200
6201 cx.assert_editor_state(
6202 &r#"
6203 ˇuse some::modified;
6204
6205
6206 fn main() {
6207 println!("hello there");
6208
6209 println!("around the");
6210 println!("world");
6211 }
6212 "#
6213 .unindent(),
6214 );
6215
6216 cx.update_editor(|editor, cx| {
6217 //Wrap around the top of the buffer
6218 for _ in 0..2 {
6219 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6220 }
6221 });
6222
6223 cx.assert_editor_state(
6224 &r#"
6225 use some::modified;
6226
6227
6228 fn main() {
6229 ˇ println!("hello there");
6230
6231 println!("around the");
6232 println!("world");
6233 }
6234 "#
6235 .unindent(),
6236 );
6237
6238 cx.update_editor(|editor, cx| {
6239 editor.fold(&Fold, cx);
6240
6241 //Make sure that the fold only gets one hunk
6242 for _ in 0..4 {
6243 editor.go_to_hunk(&GoToHunk, cx);
6244 }
6245 });
6246
6247 cx.assert_editor_state(
6248 &r#"
6249 ˇuse some::modified;
6250
6251
6252 fn main() {
6253 println!("hello there");
6254
6255 println!("around the");
6256 println!("world");
6257 }
6258 "#
6259 .unindent(),
6260 );
6261}
6262
6263#[test]
6264fn test_split_words() {
6265 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6266 split_words(text).collect()
6267 }
6268
6269 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6270 assert_eq!(split("hello_world"), &["hello_", "world"]);
6271 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6272 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6273 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6274 assert_eq!(split("helloworld"), &["helloworld"]);
6275}
6276
6277#[gpui::test]
6278async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6279 init_test(cx, |_| {});
6280
6281 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6282 let mut assert = |before, after| {
6283 let _state_context = cx.set_state(before);
6284 cx.update_editor(|editor, cx| {
6285 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6286 });
6287 cx.assert_editor_state(after);
6288 };
6289
6290 // Outside bracket jumps to outside of matching bracket
6291 assert("console.logˇ(var);", "console.log(var)ˇ;");
6292 assert("console.log(var)ˇ;", "console.logˇ(var);");
6293
6294 // Inside bracket jumps to inside of matching bracket
6295 assert("console.log(ˇvar);", "console.log(varˇ);");
6296 assert("console.log(varˇ);", "console.log(ˇvar);");
6297
6298 // When outside a bracket and inside, favor jumping to the inside bracket
6299 assert(
6300 "console.log('foo', [1, 2, 3]ˇ);",
6301 "console.log(ˇ'foo', [1, 2, 3]);",
6302 );
6303 assert(
6304 "console.log(ˇ'foo', [1, 2, 3]);",
6305 "console.log('foo', [1, 2, 3]ˇ);",
6306 );
6307
6308 // Bias forward if two options are equally likely
6309 assert(
6310 "let result = curried_fun()ˇ();",
6311 "let result = curried_fun()()ˇ;",
6312 );
6313
6314 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6315 assert(
6316 indoc! {"
6317 function test() {
6318 console.log('test')ˇ
6319 }"},
6320 indoc! {"
6321 function test() {
6322 console.logˇ('test')
6323 }"},
6324 );
6325}
6326
6327#[gpui::test(iterations = 10)]
6328async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6329 init_test(cx, |_| {});
6330
6331 let (copilot, copilot_lsp) = Copilot::fake(cx);
6332 cx.update(|cx| cx.set_global(copilot));
6333 let mut cx = EditorLspTestContext::new_rust(
6334 lsp::ServerCapabilities {
6335 completion_provider: Some(lsp::CompletionOptions {
6336 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6337 ..Default::default()
6338 }),
6339 ..Default::default()
6340 },
6341 cx,
6342 )
6343 .await;
6344
6345 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6346 cx.set_state(indoc! {"
6347 oneˇ
6348 two
6349 three
6350 "});
6351 cx.simulate_keystroke(".");
6352 let _ = handle_completion_request(
6353 &mut cx,
6354 indoc! {"
6355 one.|<>
6356 two
6357 three
6358 "},
6359 vec!["completion_a", "completion_b"],
6360 );
6361 handle_copilot_completion_request(
6362 &copilot_lsp,
6363 vec![copilot::request::Completion {
6364 text: "one.copilot1".into(),
6365 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6366 ..Default::default()
6367 }],
6368 vec![],
6369 );
6370 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6371 cx.update_editor(|editor, cx| {
6372 assert!(editor.context_menu_visible());
6373 assert!(!editor.has_active_copilot_suggestion(cx));
6374
6375 // Confirming a completion inserts it and hides the context menu, without showing
6376 // the copilot suggestion afterwards.
6377 editor
6378 .confirm_completion(&Default::default(), cx)
6379 .unwrap()
6380 .detach();
6381 assert!(!editor.context_menu_visible());
6382 assert!(!editor.has_active_copilot_suggestion(cx));
6383 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6384 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6385 });
6386
6387 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6388 cx.set_state(indoc! {"
6389 oneˇ
6390 two
6391 three
6392 "});
6393 cx.simulate_keystroke(".");
6394 let _ = handle_completion_request(
6395 &mut cx,
6396 indoc! {"
6397 one.|<>
6398 two
6399 three
6400 "},
6401 vec![],
6402 );
6403 handle_copilot_completion_request(
6404 &copilot_lsp,
6405 vec![copilot::request::Completion {
6406 text: "one.copilot1".into(),
6407 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6408 ..Default::default()
6409 }],
6410 vec![],
6411 );
6412 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6413 cx.update_editor(|editor, cx| {
6414 assert!(!editor.context_menu_visible());
6415 assert!(editor.has_active_copilot_suggestion(cx));
6416 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6417 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6418 });
6419
6420 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6421 cx.set_state(indoc! {"
6422 oneˇ
6423 two
6424 three
6425 "});
6426 cx.simulate_keystroke(".");
6427 let _ = handle_completion_request(
6428 &mut cx,
6429 indoc! {"
6430 one.|<>
6431 two
6432 three
6433 "},
6434 vec!["completion_a", "completion_b"],
6435 );
6436 handle_copilot_completion_request(
6437 &copilot_lsp,
6438 vec![copilot::request::Completion {
6439 text: "one.copilot1".into(),
6440 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6441 ..Default::default()
6442 }],
6443 vec![],
6444 );
6445 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6446 cx.update_editor(|editor, cx| {
6447 assert!(editor.context_menu_visible());
6448 assert!(!editor.has_active_copilot_suggestion(cx));
6449
6450 // When hiding the context menu, the Copilot suggestion becomes visible.
6451 editor.hide_context_menu(cx);
6452 assert!(!editor.context_menu_visible());
6453 assert!(editor.has_active_copilot_suggestion(cx));
6454 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6455 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6456 });
6457
6458 // Ensure existing completion is interpolated when inserting again.
6459 cx.simulate_keystroke("c");
6460 deterministic.run_until_parked();
6461 cx.update_editor(|editor, cx| {
6462 assert!(!editor.context_menu_visible());
6463 assert!(editor.has_active_copilot_suggestion(cx));
6464 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6465 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6466 });
6467
6468 // After debouncing, new Copilot completions should be requested.
6469 handle_copilot_completion_request(
6470 &copilot_lsp,
6471 vec![copilot::request::Completion {
6472 text: "one.copilot2".into(),
6473 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6474 ..Default::default()
6475 }],
6476 vec![],
6477 );
6478 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6479 cx.update_editor(|editor, cx| {
6480 assert!(!editor.context_menu_visible());
6481 assert!(editor.has_active_copilot_suggestion(cx));
6482 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6483 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6484
6485 // Canceling should remove the active Copilot suggestion.
6486 editor.cancel(&Default::default(), cx);
6487 assert!(!editor.has_active_copilot_suggestion(cx));
6488 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6489 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6490
6491 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6492 editor.tab(&Default::default(), cx);
6493 assert!(!editor.has_active_copilot_suggestion(cx));
6494 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6495 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6496
6497 // When undoing the previously active suggestion is shown again.
6498 editor.undo(&Default::default(), cx);
6499 assert!(editor.has_active_copilot_suggestion(cx));
6500 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6501 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6502 });
6503
6504 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6505 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6506 cx.update_editor(|editor, cx| {
6507 assert!(editor.has_active_copilot_suggestion(cx));
6508 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6509 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6510
6511 // Tabbing when there is an active suggestion inserts it.
6512 editor.tab(&Default::default(), cx);
6513 assert!(!editor.has_active_copilot_suggestion(cx));
6514 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6515 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6516
6517 // When undoing the previously active suggestion is shown again.
6518 editor.undo(&Default::default(), cx);
6519 assert!(editor.has_active_copilot_suggestion(cx));
6520 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6521 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6522
6523 // Hide suggestion.
6524 editor.cancel(&Default::default(), cx);
6525 assert!(!editor.has_active_copilot_suggestion(cx));
6526 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6527 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6528 });
6529
6530 // If an edit occurs outside of this editor but no suggestion is being shown,
6531 // we won't make it visible.
6532 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6533 cx.update_editor(|editor, cx| {
6534 assert!(!editor.has_active_copilot_suggestion(cx));
6535 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6536 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6537 });
6538
6539 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6540 cx.update_editor(|editor, cx| {
6541 editor.set_text("fn foo() {\n \n}", cx);
6542 editor.change_selections(None, cx, |s| {
6543 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6544 });
6545 });
6546 handle_copilot_completion_request(
6547 &copilot_lsp,
6548 vec![copilot::request::Completion {
6549 text: " let x = 4;".into(),
6550 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6551 ..Default::default()
6552 }],
6553 vec![],
6554 );
6555
6556 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6557 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6558 cx.update_editor(|editor, cx| {
6559 assert!(editor.has_active_copilot_suggestion(cx));
6560 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6561 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6562
6563 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6564 editor.tab(&Default::default(), cx);
6565 assert!(editor.has_active_copilot_suggestion(cx));
6566 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6567 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6568
6569 // Tabbing again accepts the suggestion.
6570 editor.tab(&Default::default(), cx);
6571 assert!(!editor.has_active_copilot_suggestion(cx));
6572 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6573 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6574 });
6575}
6576
6577#[gpui::test]
6578async fn test_copilot_completion_invalidation(
6579 deterministic: Arc<Deterministic>,
6580 cx: &mut gpui::TestAppContext,
6581) {
6582 init_test(cx, |_| {});
6583
6584 let (copilot, copilot_lsp) = Copilot::fake(cx);
6585 cx.update(|cx| cx.set_global(copilot));
6586 let mut cx = EditorLspTestContext::new_rust(
6587 lsp::ServerCapabilities {
6588 completion_provider: Some(lsp::CompletionOptions {
6589 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6590 ..Default::default()
6591 }),
6592 ..Default::default()
6593 },
6594 cx,
6595 )
6596 .await;
6597
6598 cx.set_state(indoc! {"
6599 one
6600 twˇ
6601 three
6602 "});
6603
6604 handle_copilot_completion_request(
6605 &copilot_lsp,
6606 vec![copilot::request::Completion {
6607 text: "two.foo()".into(),
6608 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6609 ..Default::default()
6610 }],
6611 vec![],
6612 );
6613 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6614 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6615 cx.update_editor(|editor, cx| {
6616 assert!(editor.has_active_copilot_suggestion(cx));
6617 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6618 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6619
6620 editor.backspace(&Default::default(), cx);
6621 assert!(editor.has_active_copilot_suggestion(cx));
6622 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6623 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6624
6625 editor.backspace(&Default::default(), cx);
6626 assert!(editor.has_active_copilot_suggestion(cx));
6627 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6628 assert_eq!(editor.text(cx), "one\n\nthree\n");
6629
6630 // Deleting across the original suggestion range invalidates it.
6631 editor.backspace(&Default::default(), cx);
6632 assert!(!editor.has_active_copilot_suggestion(cx));
6633 assert_eq!(editor.display_text(cx), "one\nthree\n");
6634 assert_eq!(editor.text(cx), "one\nthree\n");
6635
6636 // Undoing the deletion restores the suggestion.
6637 editor.undo(&Default::default(), cx);
6638 assert!(editor.has_active_copilot_suggestion(cx));
6639 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6640 assert_eq!(editor.text(cx), "one\n\nthree\n");
6641 });
6642}
6643
6644#[gpui::test]
6645async fn test_copilot_multibuffer(
6646 deterministic: Arc<Deterministic>,
6647 cx: &mut gpui::TestAppContext,
6648) {
6649 init_test(cx, |_| {});
6650
6651 let (copilot, copilot_lsp) = Copilot::fake(cx);
6652 cx.update(|cx| cx.set_global(copilot));
6653
6654 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6655 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6656 let multibuffer = cx.add_model(|cx| {
6657 let mut multibuffer = MultiBuffer::new(0);
6658 multibuffer.push_excerpts(
6659 buffer_1.clone(),
6660 [ExcerptRange {
6661 context: Point::new(0, 0)..Point::new(2, 0),
6662 primary: None,
6663 }],
6664 cx,
6665 );
6666 multibuffer.push_excerpts(
6667 buffer_2.clone(),
6668 [ExcerptRange {
6669 context: Point::new(0, 0)..Point::new(2, 0),
6670 primary: None,
6671 }],
6672 cx,
6673 );
6674 multibuffer
6675 });
6676 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6677
6678 handle_copilot_completion_request(
6679 &copilot_lsp,
6680 vec![copilot::request::Completion {
6681 text: "b = 2 + a".into(),
6682 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6683 ..Default::default()
6684 }],
6685 vec![],
6686 );
6687 editor.update(cx, |editor, cx| {
6688 // Ensure copilot suggestions are shown for the first excerpt.
6689 editor.change_selections(None, cx, |s| {
6690 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6691 });
6692 editor.next_copilot_suggestion(&Default::default(), cx);
6693 });
6694 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6695 editor.update(cx, |editor, cx| {
6696 assert!(editor.has_active_copilot_suggestion(cx));
6697 assert_eq!(
6698 editor.display_text(cx),
6699 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6700 );
6701 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6702 });
6703
6704 handle_copilot_completion_request(
6705 &copilot_lsp,
6706 vec![copilot::request::Completion {
6707 text: "d = 4 + c".into(),
6708 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
6709 ..Default::default()
6710 }],
6711 vec![],
6712 );
6713 editor.update(cx, |editor, cx| {
6714 // Move to another excerpt, ensuring the suggestion gets cleared.
6715 editor.change_selections(None, cx, |s| {
6716 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
6717 });
6718 assert!(!editor.has_active_copilot_suggestion(cx));
6719 assert_eq!(
6720 editor.display_text(cx),
6721 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
6722 );
6723 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6724
6725 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
6726 editor.handle_input(" ", cx);
6727 assert!(!editor.has_active_copilot_suggestion(cx));
6728 assert_eq!(
6729 editor.display_text(cx),
6730 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
6731 );
6732 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6733 });
6734
6735 // Ensure the new suggestion is displayed when the debounce timeout expires.
6736 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6737 editor.update(cx, |editor, cx| {
6738 assert!(editor.has_active_copilot_suggestion(cx));
6739 assert_eq!(
6740 editor.display_text(cx),
6741 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
6742 );
6743 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6744 });
6745}
6746
6747#[gpui::test]
6748async fn test_copilot_disabled_globs(
6749 deterministic: Arc<Deterministic>,
6750 cx: &mut gpui::TestAppContext,
6751) {
6752 init_test(cx, |settings| {
6753 settings
6754 .copilot
6755 .get_or_insert(Default::default())
6756 .disabled_globs = Some(vec![".env*".to_string()]);
6757 });
6758
6759 let (copilot, copilot_lsp) = Copilot::fake(cx);
6760 cx.update(|cx| cx.set_global(copilot));
6761
6762 let fs = FakeFs::new(cx.background());
6763 fs.insert_tree(
6764 "/test",
6765 json!({
6766 ".env": "SECRET=something\n",
6767 "README.md": "hello\n"
6768 }),
6769 )
6770 .await;
6771 let project = Project::test(fs, ["/test".as_ref()], cx).await;
6772
6773 let private_buffer = project
6774 .update(cx, |project, cx| {
6775 project.open_local_buffer("/test/.env", cx)
6776 })
6777 .await
6778 .unwrap();
6779 let public_buffer = project
6780 .update(cx, |project, cx| {
6781 project.open_local_buffer("/test/README.md", cx)
6782 })
6783 .await
6784 .unwrap();
6785
6786 let multibuffer = cx.add_model(|cx| {
6787 let mut multibuffer = MultiBuffer::new(0);
6788 multibuffer.push_excerpts(
6789 private_buffer.clone(),
6790 [ExcerptRange {
6791 context: Point::new(0, 0)..Point::new(1, 0),
6792 primary: None,
6793 }],
6794 cx,
6795 );
6796 multibuffer.push_excerpts(
6797 public_buffer.clone(),
6798 [ExcerptRange {
6799 context: Point::new(0, 0)..Point::new(1, 0),
6800 primary: None,
6801 }],
6802 cx,
6803 );
6804 multibuffer
6805 });
6806 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6807
6808 let mut copilot_requests = copilot_lsp
6809 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
6810 Ok(copilot::request::GetCompletionsResult {
6811 completions: vec![copilot::request::Completion {
6812 text: "next line".into(),
6813 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6814 ..Default::default()
6815 }],
6816 })
6817 });
6818
6819 editor.update(cx, |editor, cx| {
6820 editor.change_selections(None, cx, |selections| {
6821 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
6822 });
6823 editor.next_copilot_suggestion(&Default::default(), cx);
6824 });
6825
6826 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6827 assert!(copilot_requests.try_next().is_err());
6828
6829 editor.update(cx, |editor, cx| {
6830 editor.change_selections(None, cx, |s| {
6831 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
6832 });
6833 editor.next_copilot_suggestion(&Default::default(), cx);
6834 });
6835
6836 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6837 assert!(copilot_requests.try_next().is_ok());
6838}
6839
6840fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
6841 let point = DisplayPoint::new(row as u32, column as u32);
6842 point..point
6843}
6844
6845fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
6846 let (text, ranges) = marked_text_ranges(marked_text, true);
6847 assert_eq!(view.text(cx), text);
6848 assert_eq!(
6849 view.selections.ranges(cx),
6850 ranges,
6851 "Assert selections are {}",
6852 marked_text
6853 );
6854}
6855
6856/// Handle completion request passing a marked string specifying where the completion
6857/// should be triggered from using '|' character, what range should be replaced, and what completions
6858/// should be returned using '<' and '>' to delimit the range
6859fn handle_completion_request<'a>(
6860 cx: &mut EditorLspTestContext<'a>,
6861 marked_string: &str,
6862 completions: Vec<&'static str>,
6863) -> impl Future<Output = ()> {
6864 let complete_from_marker: TextRangeMarker = '|'.into();
6865 let replace_range_marker: TextRangeMarker = ('<', '>').into();
6866 let (_, mut marked_ranges) = marked_text_ranges_by(
6867 marked_string,
6868 vec![complete_from_marker.clone(), replace_range_marker.clone()],
6869 );
6870
6871 let complete_from_position =
6872 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
6873 let replace_range =
6874 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
6875
6876 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
6877 let completions = completions.clone();
6878 async move {
6879 assert_eq!(params.text_document_position.text_document.uri, url.clone());
6880 assert_eq!(
6881 params.text_document_position.position,
6882 complete_from_position
6883 );
6884 Ok(Some(lsp::CompletionResponse::Array(
6885 completions
6886 .iter()
6887 .map(|completion_text| lsp::CompletionItem {
6888 label: completion_text.to_string(),
6889 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6890 range: replace_range,
6891 new_text: completion_text.to_string(),
6892 })),
6893 ..Default::default()
6894 })
6895 .collect(),
6896 )))
6897 }
6898 });
6899
6900 async move {
6901 request.next().await;
6902 }
6903}
6904
6905fn handle_resolve_completion_request<'a>(
6906 cx: &mut EditorLspTestContext<'a>,
6907 edits: Option<Vec<(&'static str, &'static str)>>,
6908) -> impl Future<Output = ()> {
6909 let edits = edits.map(|edits| {
6910 edits
6911 .iter()
6912 .map(|(marked_string, new_text)| {
6913 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
6914 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
6915 lsp::TextEdit::new(replace_range, new_text.to_string())
6916 })
6917 .collect::<Vec<_>>()
6918 });
6919
6920 let mut request =
6921 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
6922 let edits = edits.clone();
6923 async move {
6924 Ok(lsp::CompletionItem {
6925 additional_text_edits: edits,
6926 ..Default::default()
6927 })
6928 }
6929 });
6930
6931 async move {
6932 request.next().await;
6933 }
6934}
6935
6936fn handle_copilot_completion_request(
6937 lsp: &lsp::FakeLanguageServer,
6938 completions: Vec<copilot::request::Completion>,
6939 completions_cycling: Vec<copilot::request::Completion>,
6940) {
6941 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
6942 let completions = completions.clone();
6943 async move {
6944 Ok(copilot::request::GetCompletionsResult {
6945 completions: completions.clone(),
6946 })
6947 }
6948 });
6949 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
6950 let completions_cycling = completions_cycling.clone();
6951 async move {
6952 Ok(copilot::request::GetCompletionsResult {
6953 completions: completions_cycling.clone(),
6954 })
6955 }
6956 });
6957}
6958
6959pub(crate) fn update_test_settings(
6960 cx: &mut TestAppContext,
6961 f: impl Fn(&mut AllLanguageSettingsContent),
6962) {
6963 cx.update(|cx| {
6964 cx.update_global::<SettingsStore, _, _>(|store, cx| {
6965 store.update_user_settings::<AllLanguageSettings>(cx, f);
6966 });
6967 });
6968}
6969
6970pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
6971 cx.foreground().forbid_parking();
6972
6973 cx.update(|cx| {
6974 cx.set_global(SettingsStore::test(cx));
6975 theme::init((), cx);
6976 client::init_settings(cx);
6977 language::init(cx);
6978 Project::init_settings(cx);
6979 workspace::init_settings(cx);
6980 crate::init(cx);
6981 });
6982
6983 update_test_settings(cx, f);
6984}