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 cx.add_window(|cx| {
2336 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\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 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2347 assert_eq!(
2348 editor.selections.ranges::<Point>(cx),
2349 &[Point::new(0, 3)..Point::new(0, 3)]
2350 );
2351
2352 editor.change_selections(None, cx, |s| {
2353 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2354 });
2355 editor.join_lines(&JoinLines, cx);
2356 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2357 assert_eq!(
2358 editor.selections.ranges::<Point>(cx),
2359 &[Point::new(0, 11)..Point::new(0, 11)]
2360 );
2361
2362 editor.undo(&Undo, cx);
2363 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2364 assert_eq!(
2365 editor.selections.ranges::<Point>(cx),
2366 &[Point::new(0, 5)..Point::new(2, 2)]
2367 );
2368
2369 editor.change_selections(None, cx, |s| {
2370 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2371 });
2372 editor.join_lines(&JoinLines, cx);
2373 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2374 assert_eq!(
2375 editor.selections.ranges::<Point>(cx),
2376 [Point::new(2, 3)..Point::new(2, 3)]
2377 );
2378
2379 editor.join_lines(&JoinLines, cx);
2380 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2381 assert_eq!(
2382 editor.selections.ranges::<Point>(cx),
2383 [Point::new(2, 3)..Point::new(2, 3)]
2384 );
2385
2386 editor.join_lines(&JoinLines, cx);
2387 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2388 assert_eq!(
2389 editor.selections.ranges::<Point>(cx),
2390 [Point::new(2, 3)..Point::new(2, 3)]
2391 );
2392
2393 editor
2394 });
2395}
2396
2397#[gpui::test]
2398fn test_duplicate_line(cx: &mut TestAppContext) {
2399 init_test(cx, |_| {});
2400
2401 let (_, view) = cx.add_window(|cx| {
2402 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2403 build_editor(buffer, cx)
2404 });
2405 view.update(cx, |view, cx| {
2406 view.change_selections(None, cx, |s| {
2407 s.select_display_ranges([
2408 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2409 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2410 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2411 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2412 ])
2413 });
2414 view.duplicate_line(&DuplicateLine, cx);
2415 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2416 assert_eq!(
2417 view.selections.display_ranges(cx),
2418 vec![
2419 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2420 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2421 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2422 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2423 ]
2424 );
2425 });
2426
2427 let (_, view) = cx.add_window(|cx| {
2428 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2429 build_editor(buffer, cx)
2430 });
2431 view.update(cx, |view, cx| {
2432 view.change_selections(None, cx, |s| {
2433 s.select_display_ranges([
2434 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2435 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2436 ])
2437 });
2438 view.duplicate_line(&DuplicateLine, cx);
2439 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2440 assert_eq!(
2441 view.selections.display_ranges(cx),
2442 vec![
2443 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2444 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2445 ]
2446 );
2447 });
2448}
2449
2450#[gpui::test]
2451fn test_move_line_up_down(cx: &mut TestAppContext) {
2452 init_test(cx, |_| {});
2453
2454 let (_, view) = cx.add_window(|cx| {
2455 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2456 build_editor(buffer, cx)
2457 });
2458 view.update(cx, |view, cx| {
2459 view.fold_ranges(
2460 vec![
2461 Point::new(0, 2)..Point::new(1, 2),
2462 Point::new(2, 3)..Point::new(4, 1),
2463 Point::new(7, 0)..Point::new(8, 4),
2464 ],
2465 true,
2466 cx,
2467 );
2468 view.change_selections(None, cx, |s| {
2469 s.select_display_ranges([
2470 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2471 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2472 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2473 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2474 ])
2475 });
2476 assert_eq!(
2477 view.display_text(cx),
2478 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2479 );
2480
2481 view.move_line_up(&MoveLineUp, cx);
2482 assert_eq!(
2483 view.display_text(cx),
2484 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2485 );
2486 assert_eq!(
2487 view.selections.display_ranges(cx),
2488 vec![
2489 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2490 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2491 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2492 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2493 ]
2494 );
2495 });
2496
2497 view.update(cx, |view, cx| {
2498 view.move_line_down(&MoveLineDown, cx);
2499 assert_eq!(
2500 view.display_text(cx),
2501 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2502 );
2503 assert_eq!(
2504 view.selections.display_ranges(cx),
2505 vec![
2506 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2507 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2508 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2509 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2510 ]
2511 );
2512 });
2513
2514 view.update(cx, |view, cx| {
2515 view.move_line_down(&MoveLineDown, cx);
2516 assert_eq!(
2517 view.display_text(cx),
2518 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2519 );
2520 assert_eq!(
2521 view.selections.display_ranges(cx),
2522 vec![
2523 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2524 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2525 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2526 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2527 ]
2528 );
2529 });
2530
2531 view.update(cx, |view, cx| {
2532 view.move_line_up(&MoveLineUp, cx);
2533 assert_eq!(
2534 view.display_text(cx),
2535 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2536 );
2537 assert_eq!(
2538 view.selections.display_ranges(cx),
2539 vec![
2540 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2541 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2542 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2543 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2544 ]
2545 );
2546 });
2547}
2548
2549#[gpui::test]
2550fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2551 init_test(cx, |_| {});
2552
2553 let (_, editor) = cx.add_window(|cx| {
2554 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2555 build_editor(buffer, cx)
2556 });
2557 editor.update(cx, |editor, cx| {
2558 let snapshot = editor.buffer.read(cx).snapshot(cx);
2559 editor.insert_blocks(
2560 [BlockProperties {
2561 style: BlockStyle::Fixed,
2562 position: snapshot.anchor_after(Point::new(2, 0)),
2563 disposition: BlockDisposition::Below,
2564 height: 1,
2565 render: Arc::new(|_| Empty::new().into_any()),
2566 }],
2567 Some(Autoscroll::fit()),
2568 cx,
2569 );
2570 editor.change_selections(None, cx, |s| {
2571 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2572 });
2573 editor.move_line_down(&MoveLineDown, cx);
2574 });
2575}
2576
2577#[gpui::test]
2578fn test_transpose(cx: &mut TestAppContext) {
2579 init_test(cx, |_| {});
2580
2581 _ = cx
2582 .add_window(|cx| {
2583 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2584
2585 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2586 editor.transpose(&Default::default(), cx);
2587 assert_eq!(editor.text(cx), "bac");
2588 assert_eq!(editor.selections.ranges(cx), [2..2]);
2589
2590 editor.transpose(&Default::default(), cx);
2591 assert_eq!(editor.text(cx), "bca");
2592 assert_eq!(editor.selections.ranges(cx), [3..3]);
2593
2594 editor.transpose(&Default::default(), cx);
2595 assert_eq!(editor.text(cx), "bac");
2596 assert_eq!(editor.selections.ranges(cx), [3..3]);
2597
2598 editor
2599 })
2600 .1;
2601
2602 _ = cx
2603 .add_window(|cx| {
2604 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2605
2606 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2607 editor.transpose(&Default::default(), cx);
2608 assert_eq!(editor.text(cx), "acb\nde");
2609 assert_eq!(editor.selections.ranges(cx), [3..3]);
2610
2611 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2612 editor.transpose(&Default::default(), cx);
2613 assert_eq!(editor.text(cx), "acbd\ne");
2614 assert_eq!(editor.selections.ranges(cx), [5..5]);
2615
2616 editor.transpose(&Default::default(), cx);
2617 assert_eq!(editor.text(cx), "acbde\n");
2618 assert_eq!(editor.selections.ranges(cx), [6..6]);
2619
2620 editor.transpose(&Default::default(), cx);
2621 assert_eq!(editor.text(cx), "acbd\ne");
2622 assert_eq!(editor.selections.ranges(cx), [6..6]);
2623
2624 editor
2625 })
2626 .1;
2627
2628 _ = cx
2629 .add_window(|cx| {
2630 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2631
2632 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2633 editor.transpose(&Default::default(), cx);
2634 assert_eq!(editor.text(cx), "bacd\ne");
2635 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2636
2637 editor.transpose(&Default::default(), cx);
2638 assert_eq!(editor.text(cx), "bcade\n");
2639 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2640
2641 editor.transpose(&Default::default(), cx);
2642 assert_eq!(editor.text(cx), "bcda\ne");
2643 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2644
2645 editor.transpose(&Default::default(), cx);
2646 assert_eq!(editor.text(cx), "bcade\n");
2647 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2648
2649 editor.transpose(&Default::default(), cx);
2650 assert_eq!(editor.text(cx), "bcaed\n");
2651 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2652
2653 editor
2654 })
2655 .1;
2656
2657 _ = cx
2658 .add_window(|cx| {
2659 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2660
2661 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2662 editor.transpose(&Default::default(), cx);
2663 assert_eq!(editor.text(cx), "🏀🍐✋");
2664 assert_eq!(editor.selections.ranges(cx), [8..8]);
2665
2666 editor.transpose(&Default::default(), cx);
2667 assert_eq!(editor.text(cx), "🏀✋🍐");
2668 assert_eq!(editor.selections.ranges(cx), [11..11]);
2669
2670 editor.transpose(&Default::default(), cx);
2671 assert_eq!(editor.text(cx), "🏀🍐✋");
2672 assert_eq!(editor.selections.ranges(cx), [11..11]);
2673
2674 editor
2675 })
2676 .1;
2677}
2678
2679#[gpui::test]
2680async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2681 init_test(cx, |_| {});
2682
2683 let mut cx = EditorTestContext::new(cx).await;
2684
2685 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2686 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2687 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2688
2689 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2690 cx.set_state("two ˇfour ˇsix ˇ");
2691 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2692 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2693
2694 // Paste again but with only two cursors. Since the number of cursors doesn't
2695 // match the number of slices in the clipboard, the entire clipboard text
2696 // is pasted at each cursor.
2697 cx.set_state("ˇtwo one✅ four three six five ˇ");
2698 cx.update_editor(|e, cx| {
2699 e.handle_input("( ", cx);
2700 e.paste(&Paste, cx);
2701 e.handle_input(") ", cx);
2702 });
2703 cx.assert_editor_state(
2704 &([
2705 "( one✅ ",
2706 "three ",
2707 "five ) ˇtwo one✅ four three six five ( one✅ ",
2708 "three ",
2709 "five ) ˇ",
2710 ]
2711 .join("\n")),
2712 );
2713
2714 // Cut with three selections, one of which is full-line.
2715 cx.set_state(indoc! {"
2716 1«2ˇ»3
2717 4ˇ567
2718 «8ˇ»9"});
2719 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2720 cx.assert_editor_state(indoc! {"
2721 1ˇ3
2722 ˇ9"});
2723
2724 // Paste with three selections, noticing how the copied selection that was full-line
2725 // gets inserted before the second cursor.
2726 cx.set_state(indoc! {"
2727 1ˇ3
2728 9ˇ
2729 «oˇ»ne"});
2730 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2731 cx.assert_editor_state(indoc! {"
2732 12ˇ3
2733 4567
2734 9ˇ
2735 8ˇne"});
2736
2737 // Copy with a single cursor only, which writes the whole line into the clipboard.
2738 cx.set_state(indoc! {"
2739 The quick brown
2740 fox juˇmps over
2741 the lazy dog"});
2742 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2743 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2744
2745 // Paste with three selections, noticing how the copied full-line selection is inserted
2746 // before the empty selections but replaces the selection that is non-empty.
2747 cx.set_state(indoc! {"
2748 Tˇhe quick brown
2749 «foˇ»x jumps over
2750 tˇhe lazy dog"});
2751 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2752 cx.assert_editor_state(indoc! {"
2753 fox jumps over
2754 Tˇhe quick brown
2755 fox jumps over
2756 ˇx jumps over
2757 fox jumps over
2758 tˇhe lazy dog"});
2759}
2760
2761#[gpui::test]
2762async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2763 init_test(cx, |_| {});
2764
2765 let mut cx = EditorTestContext::new(cx).await;
2766 let language = Arc::new(Language::new(
2767 LanguageConfig::default(),
2768 Some(tree_sitter_rust::language()),
2769 ));
2770 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2771
2772 // Cut an indented block, without the leading whitespace.
2773 cx.set_state(indoc! {"
2774 const a: B = (
2775 c(),
2776 «d(
2777 e,
2778 f
2779 )ˇ»
2780 );
2781 "});
2782 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2783 cx.assert_editor_state(indoc! {"
2784 const a: B = (
2785 c(),
2786 ˇ
2787 );
2788 "});
2789
2790 // Paste it at the same position.
2791 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2792 cx.assert_editor_state(indoc! {"
2793 const a: B = (
2794 c(),
2795 d(
2796 e,
2797 f
2798 )ˇ
2799 );
2800 "});
2801
2802 // Paste it at a line with a lower indent level.
2803 cx.set_state(indoc! {"
2804 ˇ
2805 const a: B = (
2806 c(),
2807 );
2808 "});
2809 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2810 cx.assert_editor_state(indoc! {"
2811 d(
2812 e,
2813 f
2814 )ˇ
2815 const a: B = (
2816 c(),
2817 );
2818 "});
2819
2820 // Cut an indented block, with the leading whitespace.
2821 cx.set_state(indoc! {"
2822 const a: B = (
2823 c(),
2824 « d(
2825 e,
2826 f
2827 )
2828 ˇ»);
2829 "});
2830 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2831 cx.assert_editor_state(indoc! {"
2832 const a: B = (
2833 c(),
2834 ˇ);
2835 "});
2836
2837 // Paste it at the same position.
2838 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2839 cx.assert_editor_state(indoc! {"
2840 const a: B = (
2841 c(),
2842 d(
2843 e,
2844 f
2845 )
2846 ˇ);
2847 "});
2848
2849 // Paste it at a line with a higher indent level.
2850 cx.set_state(indoc! {"
2851 const a: B = (
2852 c(),
2853 d(
2854 e,
2855 fˇ
2856 )
2857 );
2858 "});
2859 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2860 cx.assert_editor_state(indoc! {"
2861 const a: B = (
2862 c(),
2863 d(
2864 e,
2865 f d(
2866 e,
2867 f
2868 )
2869 ˇ
2870 )
2871 );
2872 "});
2873}
2874
2875#[gpui::test]
2876fn test_select_all(cx: &mut TestAppContext) {
2877 init_test(cx, |_| {});
2878
2879 let (_, view) = cx.add_window(|cx| {
2880 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2881 build_editor(buffer, cx)
2882 });
2883 view.update(cx, |view, cx| {
2884 view.select_all(&SelectAll, cx);
2885 assert_eq!(
2886 view.selections.display_ranges(cx),
2887 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2888 );
2889 });
2890}
2891
2892#[gpui::test]
2893fn test_select_line(cx: &mut TestAppContext) {
2894 init_test(cx, |_| {});
2895
2896 let (_, view) = cx.add_window(|cx| {
2897 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2898 build_editor(buffer, cx)
2899 });
2900 view.update(cx, |view, cx| {
2901 view.change_selections(None, cx, |s| {
2902 s.select_display_ranges([
2903 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2904 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2905 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2906 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2907 ])
2908 });
2909 view.select_line(&SelectLine, cx);
2910 assert_eq!(
2911 view.selections.display_ranges(cx),
2912 vec![
2913 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2914 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2915 ]
2916 );
2917 });
2918
2919 view.update(cx, |view, cx| {
2920 view.select_line(&SelectLine, cx);
2921 assert_eq!(
2922 view.selections.display_ranges(cx),
2923 vec![
2924 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2925 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2926 ]
2927 );
2928 });
2929
2930 view.update(cx, |view, cx| {
2931 view.select_line(&SelectLine, cx);
2932 assert_eq!(
2933 view.selections.display_ranges(cx),
2934 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2935 );
2936 });
2937}
2938
2939#[gpui::test]
2940fn test_split_selection_into_lines(cx: &mut TestAppContext) {
2941 init_test(cx, |_| {});
2942
2943 let (_, view) = cx.add_window(|cx| {
2944 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2945 build_editor(buffer, cx)
2946 });
2947 view.update(cx, |view, cx| {
2948 view.fold_ranges(
2949 vec![
2950 Point::new(0, 2)..Point::new(1, 2),
2951 Point::new(2, 3)..Point::new(4, 1),
2952 Point::new(7, 0)..Point::new(8, 4),
2953 ],
2954 true,
2955 cx,
2956 );
2957 view.change_selections(None, cx, |s| {
2958 s.select_display_ranges([
2959 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2960 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2961 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2962 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2963 ])
2964 });
2965 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
2966 });
2967
2968 view.update(cx, |view, cx| {
2969 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2970 assert_eq!(
2971 view.display_text(cx),
2972 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
2973 );
2974 assert_eq!(
2975 view.selections.display_ranges(cx),
2976 [
2977 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2978 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2979 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2980 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2981 ]
2982 );
2983 });
2984
2985 view.update(cx, |view, cx| {
2986 view.change_selections(None, cx, |s| {
2987 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2988 });
2989 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2990 assert_eq!(
2991 view.display_text(cx),
2992 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2993 );
2994 assert_eq!(
2995 view.selections.display_ranges(cx),
2996 [
2997 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2998 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2999 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3000 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3001 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3002 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3003 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3004 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3005 ]
3006 );
3007 });
3008}
3009
3010#[gpui::test]
3011fn test_add_selection_above_below(cx: &mut TestAppContext) {
3012 init_test(cx, |_| {});
3013
3014 let (_, view) = cx.add_window(|cx| {
3015 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3016 build_editor(buffer, cx)
3017 });
3018
3019 view.update(cx, |view, cx| {
3020 view.change_selections(None, cx, |s| {
3021 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
3022 });
3023 });
3024 view.update(cx, |view, cx| {
3025 view.add_selection_above(&AddSelectionAbove, cx);
3026 assert_eq!(
3027 view.selections.display_ranges(cx),
3028 vec![
3029 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3030 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3031 ]
3032 );
3033 });
3034
3035 view.update(cx, |view, cx| {
3036 view.add_selection_above(&AddSelectionAbove, cx);
3037 assert_eq!(
3038 view.selections.display_ranges(cx),
3039 vec![
3040 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3041 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3042 ]
3043 );
3044 });
3045
3046 view.update(cx, |view, cx| {
3047 view.add_selection_below(&AddSelectionBelow, cx);
3048 assert_eq!(
3049 view.selections.display_ranges(cx),
3050 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3051 );
3052
3053 view.undo_selection(&UndoSelection, cx);
3054 assert_eq!(
3055 view.selections.display_ranges(cx),
3056 vec![
3057 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
3058 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
3059 ]
3060 );
3061
3062 view.redo_selection(&RedoSelection, cx);
3063 assert_eq!(
3064 view.selections.display_ranges(cx),
3065 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
3066 );
3067 });
3068
3069 view.update(cx, |view, cx| {
3070 view.add_selection_below(&AddSelectionBelow, cx);
3071 assert_eq!(
3072 view.selections.display_ranges(cx),
3073 vec![
3074 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3075 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3076 ]
3077 );
3078 });
3079
3080 view.update(cx, |view, cx| {
3081 view.add_selection_below(&AddSelectionBelow, cx);
3082 assert_eq!(
3083 view.selections.display_ranges(cx),
3084 vec![
3085 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3086 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3087 ]
3088 );
3089 });
3090
3091 view.update(cx, |view, cx| {
3092 view.change_selections(None, cx, |s| {
3093 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
3094 });
3095 });
3096 view.update(cx, |view, cx| {
3097 view.add_selection_below(&AddSelectionBelow, cx);
3098 assert_eq!(
3099 view.selections.display_ranges(cx),
3100 vec![
3101 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3102 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3103 ]
3104 );
3105 });
3106
3107 view.update(cx, |view, cx| {
3108 view.add_selection_below(&AddSelectionBelow, cx);
3109 assert_eq!(
3110 view.selections.display_ranges(cx),
3111 vec![
3112 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3113 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3114 ]
3115 );
3116 });
3117
3118 view.update(cx, |view, cx| {
3119 view.add_selection_above(&AddSelectionAbove, cx);
3120 assert_eq!(
3121 view.selections.display_ranges(cx),
3122 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3123 );
3124 });
3125
3126 view.update(cx, |view, cx| {
3127 view.add_selection_above(&AddSelectionAbove, cx);
3128 assert_eq!(
3129 view.selections.display_ranges(cx),
3130 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3131 );
3132 });
3133
3134 view.update(cx, |view, cx| {
3135 view.change_selections(None, cx, |s| {
3136 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3137 });
3138 view.add_selection_below(&AddSelectionBelow, cx);
3139 assert_eq!(
3140 view.selections.display_ranges(cx),
3141 vec![
3142 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3143 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3144 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3145 ]
3146 );
3147 });
3148
3149 view.update(cx, |view, cx| {
3150 view.add_selection_below(&AddSelectionBelow, cx);
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![
3154 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3155 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3156 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3157 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3158 ]
3159 );
3160 });
3161
3162 view.update(cx, |view, cx| {
3163 view.add_selection_above(&AddSelectionAbove, cx);
3164 assert_eq!(
3165 view.selections.display_ranges(cx),
3166 vec![
3167 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3168 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3169 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3170 ]
3171 );
3172 });
3173
3174 view.update(cx, |view, cx| {
3175 view.change_selections(None, cx, |s| {
3176 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3177 });
3178 });
3179 view.update(cx, |view, cx| {
3180 view.add_selection_above(&AddSelectionAbove, cx);
3181 assert_eq!(
3182 view.selections.display_ranges(cx),
3183 vec![
3184 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3185 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3186 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3187 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3188 ]
3189 );
3190 });
3191
3192 view.update(cx, |view, cx| {
3193 view.add_selection_below(&AddSelectionBelow, cx);
3194 assert_eq!(
3195 view.selections.display_ranges(cx),
3196 vec![
3197 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3198 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3199 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3200 ]
3201 );
3202 });
3203}
3204
3205#[gpui::test]
3206async fn test_select_next(cx: &mut gpui::TestAppContext) {
3207 init_test(cx, |_| {});
3208
3209 let mut cx = EditorTestContext::new(cx).await;
3210 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3211
3212 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3213 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3214
3215 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3216 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3217
3218 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3219 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3220
3221 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3222 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3223
3224 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3225 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3226
3227 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3228 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3229}
3230
3231#[gpui::test]
3232async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3233 init_test(cx, |_| {});
3234 {
3235 // `Select previous` without a selection (selects wordwise)
3236 let mut cx = EditorTestContext::new(cx).await;
3237 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3238
3239 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3240 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3241
3242 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3243 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3244
3245 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3246 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3247
3248 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3249 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3250
3251 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3252 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3253
3254 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3255 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3256 }
3257 {
3258 // `Select previous` with a selection
3259 let mut cx = EditorTestContext::new(cx).await;
3260 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3261
3262 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3263 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3264
3265 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3266 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3267
3268 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3269 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3270
3271 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3272 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3273
3274 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3275 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3276
3277 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3278 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3279 }
3280}
3281
3282#[gpui::test]
3283async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3284 init_test(cx, |_| {});
3285
3286 let language = Arc::new(Language::new(
3287 LanguageConfig::default(),
3288 Some(tree_sitter_rust::language()),
3289 ));
3290
3291 let text = r#"
3292 use mod1::mod2::{mod3, mod4};
3293
3294 fn fn_1(param1: bool, param2: &str) {
3295 let var1 = "text";
3296 }
3297 "#
3298 .unindent();
3299
3300 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3301 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3302 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3303 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3304 .await;
3305
3306 view.update(cx, |view, cx| {
3307 view.change_selections(None, cx, |s| {
3308 s.select_display_ranges([
3309 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3310 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3311 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3312 ]);
3313 });
3314 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3315 });
3316 assert_eq!(
3317 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3318 &[
3319 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3320 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3321 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3322 ]
3323 );
3324
3325 view.update(cx, |view, cx| {
3326 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3327 });
3328 assert_eq!(
3329 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3330 &[
3331 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3332 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3333 ]
3334 );
3335
3336 view.update(cx, |view, cx| {
3337 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3338 });
3339 assert_eq!(
3340 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3341 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3342 );
3343
3344 // Trying to expand the selected syntax node one more time has no effect.
3345 view.update(cx, |view, cx| {
3346 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3347 });
3348 assert_eq!(
3349 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3350 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3351 );
3352
3353 view.update(cx, |view, cx| {
3354 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3355 });
3356 assert_eq!(
3357 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3358 &[
3359 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3360 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3361 ]
3362 );
3363
3364 view.update(cx, |view, cx| {
3365 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3366 });
3367 assert_eq!(
3368 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3369 &[
3370 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3371 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3372 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3373 ]
3374 );
3375
3376 view.update(cx, |view, cx| {
3377 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3378 });
3379 assert_eq!(
3380 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3381 &[
3382 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3383 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3384 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3385 ]
3386 );
3387
3388 // Trying to shrink the selected syntax node one more time has no effect.
3389 view.update(cx, |view, cx| {
3390 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3391 });
3392 assert_eq!(
3393 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3394 &[
3395 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3396 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3397 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3398 ]
3399 );
3400
3401 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3402 // a fold.
3403 view.update(cx, |view, cx| {
3404 view.fold_ranges(
3405 vec![
3406 Point::new(0, 21)..Point::new(0, 24),
3407 Point::new(3, 20)..Point::new(3, 22),
3408 ],
3409 true,
3410 cx,
3411 );
3412 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3413 });
3414 assert_eq!(
3415 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3416 &[
3417 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3418 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3419 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3420 ]
3421 );
3422}
3423
3424#[gpui::test]
3425async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3426 init_test(cx, |_| {});
3427
3428 let language = Arc::new(
3429 Language::new(
3430 LanguageConfig {
3431 brackets: BracketPairConfig {
3432 pairs: vec![
3433 BracketPair {
3434 start: "{".to_string(),
3435 end: "}".to_string(),
3436 close: false,
3437 newline: true,
3438 },
3439 BracketPair {
3440 start: "(".to_string(),
3441 end: ")".to_string(),
3442 close: false,
3443 newline: true,
3444 },
3445 ],
3446 ..Default::default()
3447 },
3448 ..Default::default()
3449 },
3450 Some(tree_sitter_rust::language()),
3451 )
3452 .with_indents_query(
3453 r#"
3454 (_ "(" ")" @end) @indent
3455 (_ "{" "}" @end) @indent
3456 "#,
3457 )
3458 .unwrap(),
3459 );
3460
3461 let text = "fn a() {}";
3462
3463 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3464 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3465 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3466 editor
3467 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3468 .await;
3469
3470 editor.update(cx, |editor, cx| {
3471 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3472 editor.newline(&Newline, cx);
3473 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3474 assert_eq!(
3475 editor.selections.ranges(cx),
3476 &[
3477 Point::new(1, 4)..Point::new(1, 4),
3478 Point::new(3, 4)..Point::new(3, 4),
3479 Point::new(5, 0)..Point::new(5, 0)
3480 ]
3481 );
3482 });
3483}
3484
3485#[gpui::test]
3486async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 let language = Arc::new(Language::new(
3492 LanguageConfig {
3493 brackets: BracketPairConfig {
3494 pairs: vec![
3495 BracketPair {
3496 start: "{".to_string(),
3497 end: "}".to_string(),
3498 close: true,
3499 newline: true,
3500 },
3501 BracketPair {
3502 start: "(".to_string(),
3503 end: ")".to_string(),
3504 close: true,
3505 newline: true,
3506 },
3507 BracketPair {
3508 start: "/*".to_string(),
3509 end: " */".to_string(),
3510 close: true,
3511 newline: true,
3512 },
3513 BracketPair {
3514 start: "[".to_string(),
3515 end: "]".to_string(),
3516 close: false,
3517 newline: true,
3518 },
3519 BracketPair {
3520 start: "\"".to_string(),
3521 end: "\"".to_string(),
3522 close: true,
3523 newline: false,
3524 },
3525 ],
3526 ..Default::default()
3527 },
3528 autoclose_before: "})]".to_string(),
3529 ..Default::default()
3530 },
3531 Some(tree_sitter_rust::language()),
3532 ));
3533
3534 let registry = Arc::new(LanguageRegistry::test());
3535 registry.add(language.clone());
3536 cx.update_buffer(|buffer, cx| {
3537 buffer.set_language_registry(registry);
3538 buffer.set_language(Some(language), cx);
3539 });
3540
3541 cx.set_state(
3542 &r#"
3543 🏀ˇ
3544 εˇ
3545 ❤️ˇ
3546 "#
3547 .unindent(),
3548 );
3549
3550 // autoclose multiple nested brackets at multiple cursors
3551 cx.update_editor(|view, cx| {
3552 view.handle_input("{", cx);
3553 view.handle_input("{", cx);
3554 view.handle_input("{", cx);
3555 });
3556 cx.assert_editor_state(
3557 &"
3558 🏀{{{ˇ}}}
3559 ε{{{ˇ}}}
3560 ❤️{{{ˇ}}}
3561 "
3562 .unindent(),
3563 );
3564
3565 // insert a different closing bracket
3566 cx.update_editor(|view, cx| {
3567 view.handle_input(")", cx);
3568 });
3569 cx.assert_editor_state(
3570 &"
3571 🏀{{{)ˇ}}}
3572 ε{{{)ˇ}}}
3573 ❤️{{{)ˇ}}}
3574 "
3575 .unindent(),
3576 );
3577
3578 // skip over the auto-closed brackets when typing a closing bracket
3579 cx.update_editor(|view, cx| {
3580 view.move_right(&MoveRight, cx);
3581 view.handle_input("}", cx);
3582 view.handle_input("}", cx);
3583 view.handle_input("}", cx);
3584 });
3585 cx.assert_editor_state(
3586 &"
3587 🏀{{{)}}}}ˇ
3588 ε{{{)}}}}ˇ
3589 ❤️{{{)}}}}ˇ
3590 "
3591 .unindent(),
3592 );
3593
3594 // autoclose multi-character pairs
3595 cx.set_state(
3596 &"
3597 ˇ
3598 ˇ
3599 "
3600 .unindent(),
3601 );
3602 cx.update_editor(|view, cx| {
3603 view.handle_input("/", cx);
3604 view.handle_input("*", cx);
3605 });
3606 cx.assert_editor_state(
3607 &"
3608 /*ˇ */
3609 /*ˇ */
3610 "
3611 .unindent(),
3612 );
3613
3614 // one cursor autocloses a multi-character pair, one cursor
3615 // does not autoclose.
3616 cx.set_state(
3617 &"
3618 /ˇ
3619 ˇ
3620 "
3621 .unindent(),
3622 );
3623 cx.update_editor(|view, cx| view.handle_input("*", cx));
3624 cx.assert_editor_state(
3625 &"
3626 /*ˇ */
3627 *ˇ
3628 "
3629 .unindent(),
3630 );
3631
3632 // Don't autoclose if the next character isn't whitespace and isn't
3633 // listed in the language's "autoclose_before" section.
3634 cx.set_state("ˇa b");
3635 cx.update_editor(|view, cx| view.handle_input("{", cx));
3636 cx.assert_editor_state("{ˇa b");
3637
3638 // Don't autoclose if `close` is false for the bracket pair
3639 cx.set_state("ˇ");
3640 cx.update_editor(|view, cx| view.handle_input("[", cx));
3641 cx.assert_editor_state("[ˇ");
3642
3643 // Surround with brackets if text is selected
3644 cx.set_state("«aˇ» b");
3645 cx.update_editor(|view, cx| view.handle_input("{", cx));
3646 cx.assert_editor_state("{«aˇ»} b");
3647
3648 // Autclose pair where the start and end characters are the same
3649 cx.set_state("aˇ");
3650 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3651 cx.assert_editor_state("a\"ˇ\"");
3652 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3653 cx.assert_editor_state("a\"\"ˇ");
3654}
3655
3656#[gpui::test]
3657async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3658 init_test(cx, |_| {});
3659
3660 let mut cx = EditorTestContext::new(cx).await;
3661
3662 let html_language = Arc::new(
3663 Language::new(
3664 LanguageConfig {
3665 name: "HTML".into(),
3666 brackets: BracketPairConfig {
3667 pairs: vec![
3668 BracketPair {
3669 start: "<".into(),
3670 end: ">".into(),
3671 close: true,
3672 ..Default::default()
3673 },
3674 BracketPair {
3675 start: "{".into(),
3676 end: "}".into(),
3677 close: true,
3678 ..Default::default()
3679 },
3680 BracketPair {
3681 start: "(".into(),
3682 end: ")".into(),
3683 close: true,
3684 ..Default::default()
3685 },
3686 ],
3687 ..Default::default()
3688 },
3689 autoclose_before: "})]>".into(),
3690 ..Default::default()
3691 },
3692 Some(tree_sitter_html::language()),
3693 )
3694 .with_injection_query(
3695 r#"
3696 (script_element
3697 (raw_text) @content
3698 (#set! "language" "javascript"))
3699 "#,
3700 )
3701 .unwrap(),
3702 );
3703
3704 let javascript_language = Arc::new(Language::new(
3705 LanguageConfig {
3706 name: "JavaScript".into(),
3707 brackets: BracketPairConfig {
3708 pairs: vec![
3709 BracketPair {
3710 start: "/*".into(),
3711 end: " */".into(),
3712 close: true,
3713 ..Default::default()
3714 },
3715 BracketPair {
3716 start: "{".into(),
3717 end: "}".into(),
3718 close: true,
3719 ..Default::default()
3720 },
3721 BracketPair {
3722 start: "(".into(),
3723 end: ")".into(),
3724 close: true,
3725 ..Default::default()
3726 },
3727 ],
3728 ..Default::default()
3729 },
3730 autoclose_before: "})]>".into(),
3731 ..Default::default()
3732 },
3733 Some(tree_sitter_javascript::language()),
3734 ));
3735
3736 let registry = Arc::new(LanguageRegistry::test());
3737 registry.add(html_language.clone());
3738 registry.add(javascript_language.clone());
3739
3740 cx.update_buffer(|buffer, cx| {
3741 buffer.set_language_registry(registry);
3742 buffer.set_language(Some(html_language), cx);
3743 });
3744
3745 cx.set_state(
3746 &r#"
3747 <body>ˇ
3748 <script>
3749 var x = 1;ˇ
3750 </script>
3751 </body>ˇ
3752 "#
3753 .unindent(),
3754 );
3755
3756 // Precondition: different languages are active at different locations.
3757 cx.update_editor(|editor, cx| {
3758 let snapshot = editor.snapshot(cx);
3759 let cursors = editor.selections.ranges::<usize>(cx);
3760 let languages = cursors
3761 .iter()
3762 .map(|c| snapshot.language_at(c.start).unwrap().name())
3763 .collect::<Vec<_>>();
3764 assert_eq!(
3765 languages,
3766 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3767 );
3768 });
3769
3770 // Angle brackets autoclose in HTML, but not JavaScript.
3771 cx.update_editor(|editor, cx| {
3772 editor.handle_input("<", cx);
3773 editor.handle_input("a", cx);
3774 });
3775 cx.assert_editor_state(
3776 &r#"
3777 <body><aˇ>
3778 <script>
3779 var x = 1;<aˇ
3780 </script>
3781 </body><aˇ>
3782 "#
3783 .unindent(),
3784 );
3785
3786 // Curly braces and parens autoclose in both HTML and JavaScript.
3787 cx.update_editor(|editor, cx| {
3788 editor.handle_input(" b=", cx);
3789 editor.handle_input("{", cx);
3790 editor.handle_input("c", cx);
3791 editor.handle_input("(", cx);
3792 });
3793 cx.assert_editor_state(
3794 &r#"
3795 <body><a b={c(ˇ)}>
3796 <script>
3797 var x = 1;<a b={c(ˇ)}
3798 </script>
3799 </body><a b={c(ˇ)}>
3800 "#
3801 .unindent(),
3802 );
3803
3804 // Brackets that were already autoclosed are skipped.
3805 cx.update_editor(|editor, cx| {
3806 editor.handle_input(")", cx);
3807 editor.handle_input("d", cx);
3808 editor.handle_input("}", cx);
3809 });
3810 cx.assert_editor_state(
3811 &r#"
3812 <body><a b={c()d}ˇ>
3813 <script>
3814 var x = 1;<a b={c()d}ˇ
3815 </script>
3816 </body><a b={c()d}ˇ>
3817 "#
3818 .unindent(),
3819 );
3820 cx.update_editor(|editor, cx| {
3821 editor.handle_input(">", cx);
3822 });
3823 cx.assert_editor_state(
3824 &r#"
3825 <body><a b={c()d}>ˇ
3826 <script>
3827 var x = 1;<a b={c()d}>ˇ
3828 </script>
3829 </body><a b={c()d}>ˇ
3830 "#
3831 .unindent(),
3832 );
3833
3834 // Reset
3835 cx.set_state(
3836 &r#"
3837 <body>ˇ
3838 <script>
3839 var x = 1;ˇ
3840 </script>
3841 </body>ˇ
3842 "#
3843 .unindent(),
3844 );
3845
3846 cx.update_editor(|editor, cx| {
3847 editor.handle_input("<", cx);
3848 });
3849 cx.assert_editor_state(
3850 &r#"
3851 <body><ˇ>
3852 <script>
3853 var x = 1;<ˇ
3854 </script>
3855 </body><ˇ>
3856 "#
3857 .unindent(),
3858 );
3859
3860 // When backspacing, the closing angle brackets are removed.
3861 cx.update_editor(|editor, cx| {
3862 editor.backspace(&Backspace, cx);
3863 });
3864 cx.assert_editor_state(
3865 &r#"
3866 <body>ˇ
3867 <script>
3868 var x = 1;ˇ
3869 </script>
3870 </body>ˇ
3871 "#
3872 .unindent(),
3873 );
3874
3875 // Block comments autoclose in JavaScript, but not HTML.
3876 cx.update_editor(|editor, cx| {
3877 editor.handle_input("/", cx);
3878 editor.handle_input("*", cx);
3879 });
3880 cx.assert_editor_state(
3881 &r#"
3882 <body>/*ˇ
3883 <script>
3884 var x = 1;/*ˇ */
3885 </script>
3886 </body>/*ˇ
3887 "#
3888 .unindent(),
3889 );
3890}
3891
3892#[gpui::test]
3893async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
3894 init_test(cx, |_| {});
3895
3896 let mut cx = EditorTestContext::new(cx).await;
3897
3898 let rust_language = Arc::new(
3899 Language::new(
3900 LanguageConfig {
3901 name: "Rust".into(),
3902 brackets: serde_json::from_value(json!([
3903 { "start": "{", "end": "}", "close": true, "newline": true },
3904 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
3905 ]))
3906 .unwrap(),
3907 autoclose_before: "})]>".into(),
3908 ..Default::default()
3909 },
3910 Some(tree_sitter_rust::language()),
3911 )
3912 .with_override_query("(string_literal) @string")
3913 .unwrap(),
3914 );
3915
3916 let registry = Arc::new(LanguageRegistry::test());
3917 registry.add(rust_language.clone());
3918
3919 cx.update_buffer(|buffer, cx| {
3920 buffer.set_language_registry(registry);
3921 buffer.set_language(Some(rust_language), cx);
3922 });
3923
3924 cx.set_state(
3925 &r#"
3926 let x = ˇ
3927 "#
3928 .unindent(),
3929 );
3930
3931 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
3932 cx.update_editor(|editor, cx| {
3933 editor.handle_input("\"", cx);
3934 });
3935 cx.assert_editor_state(
3936 &r#"
3937 let x = "ˇ"
3938 "#
3939 .unindent(),
3940 );
3941
3942 // Inserting another quotation mark. The cursor moves across the existing
3943 // automatically-inserted quotation mark.
3944 cx.update_editor(|editor, cx| {
3945 editor.handle_input("\"", cx);
3946 });
3947 cx.assert_editor_state(
3948 &r#"
3949 let x = ""ˇ
3950 "#
3951 .unindent(),
3952 );
3953
3954 // Reset
3955 cx.set_state(
3956 &r#"
3957 let x = ˇ
3958 "#
3959 .unindent(),
3960 );
3961
3962 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
3963 cx.update_editor(|editor, cx| {
3964 editor.handle_input("\"", cx);
3965 editor.handle_input(" ", cx);
3966 editor.move_left(&Default::default(), cx);
3967 editor.handle_input("\\", cx);
3968 editor.handle_input("\"", cx);
3969 });
3970 cx.assert_editor_state(
3971 &r#"
3972 let x = "\"ˇ "
3973 "#
3974 .unindent(),
3975 );
3976
3977 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
3978 // mark. Nothing is inserted.
3979 cx.update_editor(|editor, cx| {
3980 editor.move_right(&Default::default(), cx);
3981 editor.handle_input("\"", cx);
3982 });
3983 cx.assert_editor_state(
3984 &r#"
3985 let x = "\" "ˇ
3986 "#
3987 .unindent(),
3988 );
3989}
3990
3991#[gpui::test]
3992async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3993 init_test(cx, |_| {});
3994
3995 let language = Arc::new(Language::new(
3996 LanguageConfig {
3997 brackets: BracketPairConfig {
3998 pairs: vec![
3999 BracketPair {
4000 start: "{".to_string(),
4001 end: "}".to_string(),
4002 close: true,
4003 newline: true,
4004 },
4005 BracketPair {
4006 start: "/* ".to_string(),
4007 end: "*/".to_string(),
4008 close: true,
4009 ..Default::default()
4010 },
4011 ],
4012 ..Default::default()
4013 },
4014 ..Default::default()
4015 },
4016 Some(tree_sitter_rust::language()),
4017 ));
4018
4019 let text = r#"
4020 a
4021 b
4022 c
4023 "#
4024 .unindent();
4025
4026 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4027 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4028 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4029 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4030 .await;
4031
4032 view.update(cx, |view, cx| {
4033 view.change_selections(None, cx, |s| {
4034 s.select_display_ranges([
4035 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4036 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4037 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4038 ])
4039 });
4040
4041 view.handle_input("{", cx);
4042 view.handle_input("{", cx);
4043 view.handle_input("{", cx);
4044 assert_eq!(
4045 view.text(cx),
4046 "
4047 {{{a}}}
4048 {{{b}}}
4049 {{{c}}}
4050 "
4051 .unindent()
4052 );
4053 assert_eq!(
4054 view.selections.display_ranges(cx),
4055 [
4056 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4057 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4058 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4059 ]
4060 );
4061
4062 view.undo(&Undo, cx);
4063 view.undo(&Undo, cx);
4064 view.undo(&Undo, cx);
4065 assert_eq!(
4066 view.text(cx),
4067 "
4068 a
4069 b
4070 c
4071 "
4072 .unindent()
4073 );
4074 assert_eq!(
4075 view.selections.display_ranges(cx),
4076 [
4077 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4078 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4079 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4080 ]
4081 );
4082
4083 // Ensure inserting the first character of a multi-byte bracket pair
4084 // doesn't surround the selections with the bracket.
4085 view.handle_input("/", cx);
4086 assert_eq!(
4087 view.text(cx),
4088 "
4089 /
4090 /
4091 /
4092 "
4093 .unindent()
4094 );
4095 assert_eq!(
4096 view.selections.display_ranges(cx),
4097 [
4098 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4099 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4100 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4101 ]
4102 );
4103
4104 view.undo(&Undo, cx);
4105 assert_eq!(
4106 view.text(cx),
4107 "
4108 a
4109 b
4110 c
4111 "
4112 .unindent()
4113 );
4114 assert_eq!(
4115 view.selections.display_ranges(cx),
4116 [
4117 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4118 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4119 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4120 ]
4121 );
4122
4123 // Ensure inserting the last character of a multi-byte bracket pair
4124 // doesn't surround the selections with the bracket.
4125 view.handle_input("*", cx);
4126 assert_eq!(
4127 view.text(cx),
4128 "
4129 *
4130 *
4131 *
4132 "
4133 .unindent()
4134 );
4135 assert_eq!(
4136 view.selections.display_ranges(cx),
4137 [
4138 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4139 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4140 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4141 ]
4142 );
4143 });
4144}
4145
4146#[gpui::test]
4147async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4148 init_test(cx, |_| {});
4149
4150 let language = Arc::new(Language::new(
4151 LanguageConfig {
4152 brackets: BracketPairConfig {
4153 pairs: vec![BracketPair {
4154 start: "{".to_string(),
4155 end: "}".to_string(),
4156 close: true,
4157 newline: true,
4158 }],
4159 ..Default::default()
4160 },
4161 autoclose_before: "}".to_string(),
4162 ..Default::default()
4163 },
4164 Some(tree_sitter_rust::language()),
4165 ));
4166
4167 let text = r#"
4168 a
4169 b
4170 c
4171 "#
4172 .unindent();
4173
4174 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4175 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4176 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4177 editor
4178 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4179 .await;
4180
4181 editor.update(cx, |editor, cx| {
4182 editor.change_selections(None, cx, |s| {
4183 s.select_ranges([
4184 Point::new(0, 1)..Point::new(0, 1),
4185 Point::new(1, 1)..Point::new(1, 1),
4186 Point::new(2, 1)..Point::new(2, 1),
4187 ])
4188 });
4189
4190 editor.handle_input("{", cx);
4191 editor.handle_input("{", cx);
4192 editor.handle_input("_", 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, 4)..Point::new(0, 4),
4206 Point::new(1, 4)..Point::new(1, 4),
4207 Point::new(2, 4)..Point::new(2, 4)
4208 ]
4209 );
4210
4211 editor.backspace(&Default::default(), cx);
4212 editor.backspace(&Default::default(), cx);
4213 assert_eq!(
4214 editor.text(cx),
4215 "
4216 a{}
4217 b{}
4218 c{}
4219 "
4220 .unindent()
4221 );
4222 assert_eq!(
4223 editor.selections.ranges::<Point>(cx),
4224 [
4225 Point::new(0, 2)..Point::new(0, 2),
4226 Point::new(1, 2)..Point::new(1, 2),
4227 Point::new(2, 2)..Point::new(2, 2)
4228 ]
4229 );
4230
4231 editor.delete_to_previous_word_start(&Default::default(), cx);
4232 assert_eq!(
4233 editor.text(cx),
4234 "
4235 a
4236 b
4237 c
4238 "
4239 .unindent()
4240 );
4241 assert_eq!(
4242 editor.selections.ranges::<Point>(cx),
4243 [
4244 Point::new(0, 1)..Point::new(0, 1),
4245 Point::new(1, 1)..Point::new(1, 1),
4246 Point::new(2, 1)..Point::new(2, 1)
4247 ]
4248 );
4249 });
4250}
4251
4252#[gpui::test]
4253async fn test_snippets(cx: &mut gpui::TestAppContext) {
4254 init_test(cx, |_| {});
4255
4256 let (text, insertion_ranges) = marked_text_ranges(
4257 indoc! {"
4258 a.ˇ b
4259 a.ˇ b
4260 a.ˇ b
4261 "},
4262 false,
4263 );
4264
4265 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4266 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4267
4268 editor.update(cx, |editor, cx| {
4269 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4270
4271 editor
4272 .insert_snippet(&insertion_ranges, snippet, cx)
4273 .unwrap();
4274
4275 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4276 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4277 assert_eq!(editor.text(cx), expected_text);
4278 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4279 }
4280
4281 assert(
4282 editor,
4283 cx,
4284 indoc! {"
4285 a.f(«one», two, «three») b
4286 a.f(«one», two, «three») b
4287 a.f(«one», two, «three») b
4288 "},
4289 );
4290
4291 // Can't move earlier than the first tab stop
4292 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4293 assert(
4294 editor,
4295 cx,
4296 indoc! {"
4297 a.f(«one», two, «three») b
4298 a.f(«one», two, «three») b
4299 a.f(«one», two, «three») b
4300 "},
4301 );
4302
4303 assert!(editor.move_to_next_snippet_tabstop(cx));
4304 assert(
4305 editor,
4306 cx,
4307 indoc! {"
4308 a.f(one, «two», three) b
4309 a.f(one, «two», three) b
4310 a.f(one, «two», three) b
4311 "},
4312 );
4313
4314 editor.move_to_prev_snippet_tabstop(cx);
4315 assert(
4316 editor,
4317 cx,
4318 indoc! {"
4319 a.f(«one», two, «three») b
4320 a.f(«one», two, «three») b
4321 a.f(«one», two, «three») b
4322 "},
4323 );
4324
4325 assert!(editor.move_to_next_snippet_tabstop(cx));
4326 assert(
4327 editor,
4328 cx,
4329 indoc! {"
4330 a.f(one, «two», three) b
4331 a.f(one, «two», three) b
4332 a.f(one, «two», three) b
4333 "},
4334 );
4335 assert!(editor.move_to_next_snippet_tabstop(cx));
4336 assert(
4337 editor,
4338 cx,
4339 indoc! {"
4340 a.f(one, two, three)ˇ b
4341 a.f(one, two, three)ˇ b
4342 a.f(one, two, three)ˇ b
4343 "},
4344 );
4345
4346 // As soon as the last tab stop is reached, snippet state is gone
4347 editor.move_to_prev_snippet_tabstop(cx);
4348 assert(
4349 editor,
4350 cx,
4351 indoc! {"
4352 a.f(one, two, three)ˇ b
4353 a.f(one, two, three)ˇ b
4354 a.f(one, two, three)ˇ b
4355 "},
4356 );
4357 });
4358}
4359
4360#[gpui::test]
4361async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4362 init_test(cx, |_| {});
4363
4364 let mut language = Language::new(
4365 LanguageConfig {
4366 name: "Rust".into(),
4367 path_suffixes: vec!["rs".to_string()],
4368 ..Default::default()
4369 },
4370 Some(tree_sitter_rust::language()),
4371 );
4372 let mut fake_servers = language
4373 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4374 capabilities: lsp::ServerCapabilities {
4375 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4376 ..Default::default()
4377 },
4378 ..Default::default()
4379 }))
4380 .await;
4381
4382 let fs = FakeFs::new(cx.background());
4383 fs.insert_file("/file.rs", Default::default()).await;
4384
4385 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4386 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4387 let buffer = project
4388 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4389 .await
4390 .unwrap();
4391
4392 cx.foreground().start_waiting();
4393 let fake_server = fake_servers.next().await.unwrap();
4394
4395 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4396 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4397 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4398 assert!(cx.read(|cx| editor.is_dirty(cx)));
4399
4400 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4401 fake_server
4402 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4403 assert_eq!(
4404 params.text_document.uri,
4405 lsp::Url::from_file_path("/file.rs").unwrap()
4406 );
4407 assert_eq!(params.options.tab_size, 4);
4408 Ok(Some(vec![lsp::TextEdit::new(
4409 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4410 ", ".to_string(),
4411 )]))
4412 })
4413 .next()
4414 .await;
4415 cx.foreground().start_waiting();
4416 save.await.unwrap();
4417 assert_eq!(
4418 editor.read_with(cx, |editor, cx| editor.text(cx)),
4419 "one, two\nthree\n"
4420 );
4421 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4422
4423 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4424 assert!(cx.read(|cx| editor.is_dirty(cx)));
4425
4426 // Ensure we can still save even if formatting hangs.
4427 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4428 assert_eq!(
4429 params.text_document.uri,
4430 lsp::Url::from_file_path("/file.rs").unwrap()
4431 );
4432 futures::future::pending::<()>().await;
4433 unreachable!()
4434 });
4435 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4436 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4437 cx.foreground().start_waiting();
4438 save.await.unwrap();
4439 assert_eq!(
4440 editor.read_with(cx, |editor, cx| editor.text(cx)),
4441 "one\ntwo\nthree\n"
4442 );
4443 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4444
4445 // Set rust language override and assert overridden tabsize is sent to language server
4446 update_test_settings(cx, |settings| {
4447 settings.languages.insert(
4448 "Rust".into(),
4449 LanguageSettingsContent {
4450 tab_size: NonZeroU32::new(8),
4451 ..Default::default()
4452 },
4453 );
4454 });
4455
4456 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4457 fake_server
4458 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4459 assert_eq!(
4460 params.text_document.uri,
4461 lsp::Url::from_file_path("/file.rs").unwrap()
4462 );
4463 assert_eq!(params.options.tab_size, 8);
4464 Ok(Some(vec![]))
4465 })
4466 .next()
4467 .await;
4468 cx.foreground().start_waiting();
4469 save.await.unwrap();
4470}
4471
4472#[gpui::test]
4473async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4474 init_test(cx, |_| {});
4475
4476 let mut language = Language::new(
4477 LanguageConfig {
4478 name: "Rust".into(),
4479 path_suffixes: vec!["rs".to_string()],
4480 ..Default::default()
4481 },
4482 Some(tree_sitter_rust::language()),
4483 );
4484 let mut fake_servers = language
4485 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4486 capabilities: lsp::ServerCapabilities {
4487 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4488 ..Default::default()
4489 },
4490 ..Default::default()
4491 }))
4492 .await;
4493
4494 let fs = FakeFs::new(cx.background());
4495 fs.insert_file("/file.rs", Default::default()).await;
4496
4497 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4498 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4499 let buffer = project
4500 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4501 .await
4502 .unwrap();
4503
4504 cx.foreground().start_waiting();
4505 let fake_server = fake_servers.next().await.unwrap();
4506
4507 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4508 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4509 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4510 assert!(cx.read(|cx| editor.is_dirty(cx)));
4511
4512 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4513 fake_server
4514 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4515 assert_eq!(
4516 params.text_document.uri,
4517 lsp::Url::from_file_path("/file.rs").unwrap()
4518 );
4519 assert_eq!(params.options.tab_size, 4);
4520 Ok(Some(vec![lsp::TextEdit::new(
4521 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4522 ", ".to_string(),
4523 )]))
4524 })
4525 .next()
4526 .await;
4527 cx.foreground().start_waiting();
4528 save.await.unwrap();
4529 assert_eq!(
4530 editor.read_with(cx, |editor, cx| editor.text(cx)),
4531 "one, two\nthree\n"
4532 );
4533 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4534
4535 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4536 assert!(cx.read(|cx| editor.is_dirty(cx)));
4537
4538 // Ensure we can still save even if formatting hangs.
4539 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4540 move |params, _| async move {
4541 assert_eq!(
4542 params.text_document.uri,
4543 lsp::Url::from_file_path("/file.rs").unwrap()
4544 );
4545 futures::future::pending::<()>().await;
4546 unreachable!()
4547 },
4548 );
4549 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4550 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4551 cx.foreground().start_waiting();
4552 save.await.unwrap();
4553 assert_eq!(
4554 editor.read_with(cx, |editor, cx| editor.text(cx)),
4555 "one\ntwo\nthree\n"
4556 );
4557 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4558
4559 // Set rust language override and assert overridden tabsize is sent to language server
4560 update_test_settings(cx, |settings| {
4561 settings.languages.insert(
4562 "Rust".into(),
4563 LanguageSettingsContent {
4564 tab_size: NonZeroU32::new(8),
4565 ..Default::default()
4566 },
4567 );
4568 });
4569
4570 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4571 fake_server
4572 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4573 assert_eq!(
4574 params.text_document.uri,
4575 lsp::Url::from_file_path("/file.rs").unwrap()
4576 );
4577 assert_eq!(params.options.tab_size, 8);
4578 Ok(Some(vec![]))
4579 })
4580 .next()
4581 .await;
4582 cx.foreground().start_waiting();
4583 save.await.unwrap();
4584}
4585
4586#[gpui::test]
4587async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4588 init_test(cx, |_| {});
4589
4590 let mut language = Language::new(
4591 LanguageConfig {
4592 name: "Rust".into(),
4593 path_suffixes: vec!["rs".to_string()],
4594 ..Default::default()
4595 },
4596 Some(tree_sitter_rust::language()),
4597 );
4598 let mut fake_servers = language
4599 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4600 capabilities: lsp::ServerCapabilities {
4601 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4602 ..Default::default()
4603 },
4604 ..Default::default()
4605 }))
4606 .await;
4607
4608 let fs = FakeFs::new(cx.background());
4609 fs.insert_file("/file.rs", Default::default()).await;
4610
4611 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4612 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4613 let buffer = project
4614 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4615 .await
4616 .unwrap();
4617
4618 cx.foreground().start_waiting();
4619 let fake_server = fake_servers.next().await.unwrap();
4620
4621 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4622 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4623 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4624
4625 let format = editor.update(cx, |editor, cx| {
4626 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4627 });
4628 fake_server
4629 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4630 assert_eq!(
4631 params.text_document.uri,
4632 lsp::Url::from_file_path("/file.rs").unwrap()
4633 );
4634 assert_eq!(params.options.tab_size, 4);
4635 Ok(Some(vec![lsp::TextEdit::new(
4636 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4637 ", ".to_string(),
4638 )]))
4639 })
4640 .next()
4641 .await;
4642 cx.foreground().start_waiting();
4643 format.await.unwrap();
4644 assert_eq!(
4645 editor.read_with(cx, |editor, cx| editor.text(cx)),
4646 "one, two\nthree\n"
4647 );
4648
4649 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4650 // Ensure we don't lock if formatting hangs.
4651 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4652 assert_eq!(
4653 params.text_document.uri,
4654 lsp::Url::from_file_path("/file.rs").unwrap()
4655 );
4656 futures::future::pending::<()>().await;
4657 unreachable!()
4658 });
4659 let format = editor.update(cx, |editor, cx| {
4660 editor.perform_format(project, FormatTrigger::Manual, cx)
4661 });
4662 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4663 cx.foreground().start_waiting();
4664 format.await.unwrap();
4665 assert_eq!(
4666 editor.read_with(cx, |editor, cx| editor.text(cx)),
4667 "one\ntwo\nthree\n"
4668 );
4669}
4670
4671#[gpui::test]
4672async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4673 init_test(cx, |_| {});
4674
4675 let mut cx = EditorLspTestContext::new_rust(
4676 lsp::ServerCapabilities {
4677 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4678 ..Default::default()
4679 },
4680 cx,
4681 )
4682 .await;
4683
4684 cx.set_state(indoc! {"
4685 one.twoˇ
4686 "});
4687
4688 // The format request takes a long time. When it completes, it inserts
4689 // a newline and an indent before the `.`
4690 cx.lsp
4691 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4692 let executor = cx.background();
4693 async move {
4694 executor.timer(Duration::from_millis(100)).await;
4695 Ok(Some(vec![lsp::TextEdit {
4696 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4697 new_text: "\n ".into(),
4698 }]))
4699 }
4700 });
4701
4702 // Submit a format request.
4703 let format_1 = cx
4704 .update_editor(|editor, cx| editor.format(&Format, cx))
4705 .unwrap();
4706 cx.foreground().run_until_parked();
4707
4708 // Submit a second format request.
4709 let format_2 = cx
4710 .update_editor(|editor, cx| editor.format(&Format, cx))
4711 .unwrap();
4712 cx.foreground().run_until_parked();
4713
4714 // Wait for both format requests to complete
4715 cx.foreground().advance_clock(Duration::from_millis(200));
4716 cx.foreground().start_waiting();
4717 format_1.await.unwrap();
4718 cx.foreground().start_waiting();
4719 format_2.await.unwrap();
4720
4721 // The formatting edits only happens once.
4722 cx.assert_editor_state(indoc! {"
4723 one
4724 .twoˇ
4725 "});
4726}
4727
4728#[gpui::test]
4729async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4730 init_test(cx, |_| {});
4731
4732 let mut cx = EditorLspTestContext::new_rust(
4733 lsp::ServerCapabilities {
4734 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4735 ..Default::default()
4736 },
4737 cx,
4738 )
4739 .await;
4740
4741 // Set up a buffer white some trailing whitespace and no trailing newline.
4742 cx.set_state(
4743 &[
4744 "one ", //
4745 "twoˇ", //
4746 "three ", //
4747 "four", //
4748 ]
4749 .join("\n"),
4750 );
4751
4752 // Submit a format request.
4753 let format = cx
4754 .update_editor(|editor, cx| editor.format(&Format, cx))
4755 .unwrap();
4756
4757 // Record which buffer changes have been sent to the language server
4758 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
4759 cx.lsp
4760 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
4761 let buffer_changes = buffer_changes.clone();
4762 move |params, _| {
4763 buffer_changes.lock().extend(
4764 params
4765 .content_changes
4766 .into_iter()
4767 .map(|e| (e.range.unwrap(), e.text)),
4768 );
4769 }
4770 });
4771
4772 // Handle formatting requests to the language server.
4773 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
4774 let buffer_changes = buffer_changes.clone();
4775 move |_, _| {
4776 // When formatting is requested, trailing whitespace has already been stripped,
4777 // and the trailing newline has already been added.
4778 assert_eq!(
4779 &buffer_changes.lock()[1..],
4780 &[
4781 (
4782 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
4783 "".into()
4784 ),
4785 (
4786 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
4787 "".into()
4788 ),
4789 (
4790 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
4791 "\n".into()
4792 ),
4793 ]
4794 );
4795
4796 // Insert blank lines between each line of the buffer.
4797 async move {
4798 Ok(Some(vec![
4799 lsp::TextEdit {
4800 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
4801 new_text: "\n".into(),
4802 },
4803 lsp::TextEdit {
4804 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
4805 new_text: "\n".into(),
4806 },
4807 ]))
4808 }
4809 }
4810 });
4811
4812 // After formatting the buffer, the trailing whitespace is stripped,
4813 // a newline is appended, and the edits provided by the language server
4814 // have been applied.
4815 format.await.unwrap();
4816 cx.assert_editor_state(
4817 &[
4818 "one", //
4819 "", //
4820 "twoˇ", //
4821 "", //
4822 "three", //
4823 "four", //
4824 "", //
4825 ]
4826 .join("\n"),
4827 );
4828
4829 // Undoing the formatting undoes the trailing whitespace removal, the
4830 // trailing newline, and the LSP edits.
4831 cx.update_buffer(|buffer, cx| buffer.undo(cx));
4832 cx.assert_editor_state(
4833 &[
4834 "one ", //
4835 "twoˇ", //
4836 "three ", //
4837 "four", //
4838 ]
4839 .join("\n"),
4840 );
4841}
4842
4843#[gpui::test]
4844async fn test_completion(cx: &mut gpui::TestAppContext) {
4845 init_test(cx, |_| {});
4846
4847 let mut cx = EditorLspTestContext::new_rust(
4848 lsp::ServerCapabilities {
4849 completion_provider: Some(lsp::CompletionOptions {
4850 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4851 ..Default::default()
4852 }),
4853 ..Default::default()
4854 },
4855 cx,
4856 )
4857 .await;
4858
4859 cx.set_state(indoc! {"
4860 oneˇ
4861 two
4862 three
4863 "});
4864 cx.simulate_keystroke(".");
4865 handle_completion_request(
4866 &mut cx,
4867 indoc! {"
4868 one.|<>
4869 two
4870 three
4871 "},
4872 vec!["first_completion", "second_completion"],
4873 )
4874 .await;
4875 cx.condition(|editor, _| editor.context_menu_visible())
4876 .await;
4877 let apply_additional_edits = cx.update_editor(|editor, cx| {
4878 editor.move_down(&MoveDown, cx);
4879 editor
4880 .confirm_completion(&ConfirmCompletion::default(), cx)
4881 .unwrap()
4882 });
4883 cx.assert_editor_state(indoc! {"
4884 one.second_completionˇ
4885 two
4886 three
4887 "});
4888
4889 handle_resolve_completion_request(
4890 &mut cx,
4891 Some(vec![
4892 (
4893 //This overlaps with the primary completion edit which is
4894 //misbehavior from the LSP spec, test that we filter it out
4895 indoc! {"
4896 one.second_ˇcompletion
4897 two
4898 threeˇ
4899 "},
4900 "overlapping additional edit",
4901 ),
4902 (
4903 indoc! {"
4904 one.second_completion
4905 two
4906 threeˇ
4907 "},
4908 "\nadditional edit",
4909 ),
4910 ]),
4911 )
4912 .await;
4913 apply_additional_edits.await.unwrap();
4914 cx.assert_editor_state(indoc! {"
4915 one.second_completionˇ
4916 two
4917 three
4918 additional edit
4919 "});
4920
4921 cx.set_state(indoc! {"
4922 one.second_completion
4923 twoˇ
4924 threeˇ
4925 additional edit
4926 "});
4927 cx.simulate_keystroke(" ");
4928 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4929 cx.simulate_keystroke("s");
4930 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4931
4932 cx.assert_editor_state(indoc! {"
4933 one.second_completion
4934 two sˇ
4935 three sˇ
4936 additional edit
4937 "});
4938 handle_completion_request(
4939 &mut cx,
4940 indoc! {"
4941 one.second_completion
4942 two s
4943 three <s|>
4944 additional edit
4945 "},
4946 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4947 )
4948 .await;
4949 cx.condition(|editor, _| editor.context_menu_visible())
4950 .await;
4951
4952 cx.simulate_keystroke("i");
4953
4954 handle_completion_request(
4955 &mut cx,
4956 indoc! {"
4957 one.second_completion
4958 two si
4959 three <si|>
4960 additional edit
4961 "},
4962 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4963 )
4964 .await;
4965 cx.condition(|editor, _| editor.context_menu_visible())
4966 .await;
4967
4968 let apply_additional_edits = cx.update_editor(|editor, cx| {
4969 editor
4970 .confirm_completion(&ConfirmCompletion::default(), cx)
4971 .unwrap()
4972 });
4973 cx.assert_editor_state(indoc! {"
4974 one.second_completion
4975 two sixth_completionˇ
4976 three sixth_completionˇ
4977 additional edit
4978 "});
4979
4980 handle_resolve_completion_request(&mut cx, None).await;
4981 apply_additional_edits.await.unwrap();
4982
4983 cx.update(|cx| {
4984 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
4985 settings.update_user_settings::<EditorSettings>(cx, |settings| {
4986 settings.show_completions_on_input = Some(false);
4987 });
4988 })
4989 });
4990 cx.set_state("editorˇ");
4991 cx.simulate_keystroke(".");
4992 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4993 cx.simulate_keystroke("c");
4994 cx.simulate_keystroke("l");
4995 cx.simulate_keystroke("o");
4996 cx.assert_editor_state("editor.cloˇ");
4997 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4998 cx.update_editor(|editor, cx| {
4999 editor.show_completions(&ShowCompletions, cx);
5000 });
5001 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5002 cx.condition(|editor, _| editor.context_menu_visible())
5003 .await;
5004 let apply_additional_edits = cx.update_editor(|editor, cx| {
5005 editor
5006 .confirm_completion(&ConfirmCompletion::default(), cx)
5007 .unwrap()
5008 });
5009 cx.assert_editor_state("editor.closeˇ");
5010 handle_resolve_completion_request(&mut cx, None).await;
5011 apply_additional_edits.await.unwrap();
5012}
5013
5014#[gpui::test]
5015async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5016 init_test(cx, |_| {});
5017 let mut cx = EditorTestContext::new(cx).await;
5018 let language = Arc::new(Language::new(
5019 LanguageConfig {
5020 line_comment: Some("// ".into()),
5021 ..Default::default()
5022 },
5023 Some(tree_sitter_rust::language()),
5024 ));
5025 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5026
5027 // If multiple selections intersect a line, the line is only toggled once.
5028 cx.set_state(indoc! {"
5029 fn a() {
5030 «//b();
5031 ˇ»// «c();
5032 //ˇ» d();
5033 }
5034 "});
5035
5036 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5037
5038 cx.assert_editor_state(indoc! {"
5039 fn a() {
5040 «b();
5041 c();
5042 ˇ» d();
5043 }
5044 "});
5045
5046 // The comment prefix is inserted at the same column for every line in a
5047 // selection.
5048 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5049
5050 cx.assert_editor_state(indoc! {"
5051 fn a() {
5052 // «b();
5053 // c();
5054 ˇ»// d();
5055 }
5056 "});
5057
5058 // If a selection ends at the beginning of a line, that line is not toggled.
5059 cx.set_selections_state(indoc! {"
5060 fn a() {
5061 // b();
5062 «// c();
5063 ˇ» // d();
5064 }
5065 "});
5066
5067 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5068
5069 cx.assert_editor_state(indoc! {"
5070 fn a() {
5071 // b();
5072 «c();
5073 ˇ» // d();
5074 }
5075 "});
5076
5077 // If a selection span a single line and is empty, the line is toggled.
5078 cx.set_state(indoc! {"
5079 fn a() {
5080 a();
5081 b();
5082 ˇ
5083 }
5084 "});
5085
5086 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5087
5088 cx.assert_editor_state(indoc! {"
5089 fn a() {
5090 a();
5091 b();
5092 //•ˇ
5093 }
5094 "});
5095
5096 // If a selection span multiple lines, empty lines are not toggled.
5097 cx.set_state(indoc! {"
5098 fn a() {
5099 «a();
5100
5101 c();ˇ»
5102 }
5103 "});
5104
5105 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5106
5107 cx.assert_editor_state(indoc! {"
5108 fn a() {
5109 // «a();
5110
5111 // c();ˇ»
5112 }
5113 "});
5114}
5115
5116#[gpui::test]
5117async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5118 init_test(cx, |_| {});
5119
5120 let language = Arc::new(Language::new(
5121 LanguageConfig {
5122 line_comment: Some("// ".into()),
5123 ..Default::default()
5124 },
5125 Some(tree_sitter_rust::language()),
5126 ));
5127
5128 let registry = Arc::new(LanguageRegistry::test());
5129 registry.add(language.clone());
5130
5131 let mut cx = EditorTestContext::new(cx).await;
5132 cx.update_buffer(|buffer, cx| {
5133 buffer.set_language_registry(registry);
5134 buffer.set_language(Some(language), cx);
5135 });
5136
5137 let toggle_comments = &ToggleComments {
5138 advance_downwards: true,
5139 };
5140
5141 // Single cursor on one line -> advance
5142 // Cursor moves horizontally 3 characters as well on non-blank line
5143 cx.set_state(indoc!(
5144 "fn a() {
5145 ˇdog();
5146 cat();
5147 }"
5148 ));
5149 cx.update_editor(|editor, cx| {
5150 editor.toggle_comments(toggle_comments, cx);
5151 });
5152 cx.assert_editor_state(indoc!(
5153 "fn a() {
5154 // dog();
5155 catˇ();
5156 }"
5157 ));
5158
5159 // Single selection on one line -> don't advance
5160 cx.set_state(indoc!(
5161 "fn a() {
5162 «dog()ˇ»;
5163 cat();
5164 }"
5165 ));
5166 cx.update_editor(|editor, cx| {
5167 editor.toggle_comments(toggle_comments, cx);
5168 });
5169 cx.assert_editor_state(indoc!(
5170 "fn a() {
5171 // «dog()ˇ»;
5172 cat();
5173 }"
5174 ));
5175
5176 // Multiple cursors on one line -> advance
5177 cx.set_state(indoc!(
5178 "fn a() {
5179 ˇdˇog();
5180 cat();
5181 }"
5182 ));
5183 cx.update_editor(|editor, cx| {
5184 editor.toggle_comments(toggle_comments, cx);
5185 });
5186 cx.assert_editor_state(indoc!(
5187 "fn a() {
5188 // dog();
5189 catˇ(ˇ);
5190 }"
5191 ));
5192
5193 // Multiple cursors on one line, with selection -> don't advance
5194 cx.set_state(indoc!(
5195 "fn a() {
5196 ˇdˇog«()ˇ»;
5197 cat();
5198 }"
5199 ));
5200 cx.update_editor(|editor, cx| {
5201 editor.toggle_comments(toggle_comments, cx);
5202 });
5203 cx.assert_editor_state(indoc!(
5204 "fn a() {
5205 // ˇdˇog«()ˇ»;
5206 cat();
5207 }"
5208 ));
5209
5210 // Single cursor on one line -> advance
5211 // Cursor moves to column 0 on blank line
5212 cx.set_state(indoc!(
5213 "fn a() {
5214 ˇdog();
5215
5216 cat();
5217 }"
5218 ));
5219 cx.update_editor(|editor, cx| {
5220 editor.toggle_comments(toggle_comments, cx);
5221 });
5222 cx.assert_editor_state(indoc!(
5223 "fn a() {
5224 // dog();
5225 ˇ
5226 cat();
5227 }"
5228 ));
5229
5230 // Single cursor on one line -> advance
5231 // Cursor starts and ends at column 0
5232 cx.set_state(indoc!(
5233 "fn a() {
5234 ˇ dog();
5235 cat();
5236 }"
5237 ));
5238 cx.update_editor(|editor, cx| {
5239 editor.toggle_comments(toggle_comments, cx);
5240 });
5241 cx.assert_editor_state(indoc!(
5242 "fn a() {
5243 // dog();
5244 ˇ cat();
5245 }"
5246 ));
5247}
5248
5249#[gpui::test]
5250async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5251 init_test(cx, |_| {});
5252
5253 let mut cx = EditorTestContext::new(cx).await;
5254
5255 let html_language = Arc::new(
5256 Language::new(
5257 LanguageConfig {
5258 name: "HTML".into(),
5259 block_comment: Some(("<!-- ".into(), " -->".into())),
5260 ..Default::default()
5261 },
5262 Some(tree_sitter_html::language()),
5263 )
5264 .with_injection_query(
5265 r#"
5266 (script_element
5267 (raw_text) @content
5268 (#set! "language" "javascript"))
5269 "#,
5270 )
5271 .unwrap(),
5272 );
5273
5274 let javascript_language = Arc::new(Language::new(
5275 LanguageConfig {
5276 name: "JavaScript".into(),
5277 line_comment: Some("// ".into()),
5278 ..Default::default()
5279 },
5280 Some(tree_sitter_javascript::language()),
5281 ));
5282
5283 let registry = Arc::new(LanguageRegistry::test());
5284 registry.add(html_language.clone());
5285 registry.add(javascript_language.clone());
5286
5287 cx.update_buffer(|buffer, cx| {
5288 buffer.set_language_registry(registry);
5289 buffer.set_language(Some(html_language), cx);
5290 });
5291
5292 // Toggle comments for empty selections
5293 cx.set_state(
5294 &r#"
5295 <p>A</p>ˇ
5296 <p>B</p>ˇ
5297 <p>C</p>ˇ
5298 "#
5299 .unindent(),
5300 );
5301 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5302 cx.assert_editor_state(
5303 &r#"
5304 <!-- <p>A</p>ˇ -->
5305 <!-- <p>B</p>ˇ -->
5306 <!-- <p>C</p>ˇ -->
5307 "#
5308 .unindent(),
5309 );
5310 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5311 cx.assert_editor_state(
5312 &r#"
5313 <p>A</p>ˇ
5314 <p>B</p>ˇ
5315 <p>C</p>ˇ
5316 "#
5317 .unindent(),
5318 );
5319
5320 // Toggle comments for mixture of empty and non-empty selections, where
5321 // multiple selections occupy a given line.
5322 cx.set_state(
5323 &r#"
5324 <p>A«</p>
5325 <p>ˇ»B</p>ˇ
5326 <p>C«</p>
5327 <p>ˇ»D</p>ˇ
5328 "#
5329 .unindent(),
5330 );
5331
5332 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5333 cx.assert_editor_state(
5334 &r#"
5335 <!-- <p>A«</p>
5336 <p>ˇ»B</p>ˇ -->
5337 <!-- <p>C«</p>
5338 <p>ˇ»D</p>ˇ -->
5339 "#
5340 .unindent(),
5341 );
5342 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5343 cx.assert_editor_state(
5344 &r#"
5345 <p>A«</p>
5346 <p>ˇ»B</p>ˇ
5347 <p>C«</p>
5348 <p>ˇ»D</p>ˇ
5349 "#
5350 .unindent(),
5351 );
5352
5353 // Toggle comments when different languages are active for different
5354 // selections.
5355 cx.set_state(
5356 &r#"
5357 ˇ<script>
5358 ˇvar x = new Y();
5359 ˇ</script>
5360 "#
5361 .unindent(),
5362 );
5363 cx.foreground().run_until_parked();
5364 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5365 cx.assert_editor_state(
5366 &r#"
5367 <!-- ˇ<script> -->
5368 // ˇvar x = new Y();
5369 <!-- ˇ</script> -->
5370 "#
5371 .unindent(),
5372 );
5373}
5374
5375#[gpui::test]
5376fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5377 init_test(cx, |_| {});
5378
5379 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5380 let multibuffer = cx.add_model(|cx| {
5381 let mut multibuffer = MultiBuffer::new(0);
5382 multibuffer.push_excerpts(
5383 buffer.clone(),
5384 [
5385 ExcerptRange {
5386 context: Point::new(0, 0)..Point::new(0, 4),
5387 primary: None,
5388 },
5389 ExcerptRange {
5390 context: Point::new(1, 0)..Point::new(1, 4),
5391 primary: None,
5392 },
5393 ],
5394 cx,
5395 );
5396 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5397 multibuffer
5398 });
5399
5400 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5401 view.update(cx, |view, cx| {
5402 assert_eq!(view.text(cx), "aaaa\nbbbb");
5403 view.change_selections(None, cx, |s| {
5404 s.select_ranges([
5405 Point::new(0, 0)..Point::new(0, 0),
5406 Point::new(1, 0)..Point::new(1, 0),
5407 ])
5408 });
5409
5410 view.handle_input("X", cx);
5411 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5412 assert_eq!(
5413 view.selections.ranges(cx),
5414 [
5415 Point::new(0, 1)..Point::new(0, 1),
5416 Point::new(1, 1)..Point::new(1, 1),
5417 ]
5418 );
5419
5420 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5421 view.change_selections(None, cx, |s| {
5422 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5423 });
5424 view.backspace(&Default::default(), cx);
5425 assert_eq!(view.text(cx), "Xa\nbbb");
5426 assert_eq!(
5427 view.selections.ranges(cx),
5428 [Point::new(1, 0)..Point::new(1, 0)]
5429 );
5430
5431 view.change_selections(None, cx, |s| {
5432 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5433 });
5434 view.backspace(&Default::default(), cx);
5435 assert_eq!(view.text(cx), "X\nbb");
5436 assert_eq!(
5437 view.selections.ranges(cx),
5438 [Point::new(0, 1)..Point::new(0, 1)]
5439 );
5440 });
5441}
5442
5443#[gpui::test]
5444fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5445 init_test(cx, |_| {});
5446
5447 let markers = vec![('[', ']').into(), ('(', ')').into()];
5448 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5449 indoc! {"
5450 [aaaa
5451 (bbbb]
5452 cccc)",
5453 },
5454 markers.clone(),
5455 );
5456 let excerpt_ranges = markers.into_iter().map(|marker| {
5457 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5458 ExcerptRange {
5459 context,
5460 primary: None,
5461 }
5462 });
5463 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5464 let multibuffer = cx.add_model(|cx| {
5465 let mut multibuffer = MultiBuffer::new(0);
5466 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5467 multibuffer
5468 });
5469
5470 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5471 view.update(cx, |view, cx| {
5472 let (expected_text, selection_ranges) = marked_text_ranges(
5473 indoc! {"
5474 aaaa
5475 bˇbbb
5476 bˇbbˇb
5477 cccc"
5478 },
5479 true,
5480 );
5481 assert_eq!(view.text(cx), expected_text);
5482 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5483
5484 view.handle_input("X", cx);
5485
5486 let (expected_text, expected_selections) = marked_text_ranges(
5487 indoc! {"
5488 aaaa
5489 bXˇbbXb
5490 bXˇbbXˇb
5491 cccc"
5492 },
5493 false,
5494 );
5495 assert_eq!(view.text(cx), expected_text);
5496 assert_eq!(view.selections.ranges(cx), expected_selections);
5497
5498 view.newline(&Newline, cx);
5499 let (expected_text, expected_selections) = marked_text_ranges(
5500 indoc! {"
5501 aaaa
5502 bX
5503 ˇbbX
5504 b
5505 bX
5506 ˇbbX
5507 ˇb
5508 cccc"
5509 },
5510 false,
5511 );
5512 assert_eq!(view.text(cx), expected_text);
5513 assert_eq!(view.selections.ranges(cx), expected_selections);
5514 });
5515}
5516
5517#[gpui::test]
5518fn test_refresh_selections(cx: &mut TestAppContext) {
5519 init_test(cx, |_| {});
5520
5521 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5522 let mut excerpt1_id = None;
5523 let multibuffer = cx.add_model(|cx| {
5524 let mut multibuffer = MultiBuffer::new(0);
5525 excerpt1_id = multibuffer
5526 .push_excerpts(
5527 buffer.clone(),
5528 [
5529 ExcerptRange {
5530 context: Point::new(0, 0)..Point::new(1, 4),
5531 primary: None,
5532 },
5533 ExcerptRange {
5534 context: Point::new(1, 0)..Point::new(2, 4),
5535 primary: None,
5536 },
5537 ],
5538 cx,
5539 )
5540 .into_iter()
5541 .next();
5542 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5543 multibuffer
5544 });
5545
5546 let (_, editor) = cx.add_window(|cx| {
5547 let mut editor = build_editor(multibuffer.clone(), cx);
5548 let snapshot = editor.snapshot(cx);
5549 editor.change_selections(None, cx, |s| {
5550 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5551 });
5552 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5553 assert_eq!(
5554 editor.selections.ranges(cx),
5555 [
5556 Point::new(1, 3)..Point::new(1, 3),
5557 Point::new(2, 1)..Point::new(2, 1),
5558 ]
5559 );
5560 editor
5561 });
5562
5563 // Refreshing selections is a no-op when excerpts haven't changed.
5564 editor.update(cx, |editor, cx| {
5565 editor.change_selections(None, cx, |s| s.refresh());
5566 assert_eq!(
5567 editor.selections.ranges(cx),
5568 [
5569 Point::new(1, 3)..Point::new(1, 3),
5570 Point::new(2, 1)..Point::new(2, 1),
5571 ]
5572 );
5573 });
5574
5575 multibuffer.update(cx, |multibuffer, cx| {
5576 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5577 });
5578 editor.update(cx, |editor, cx| {
5579 // Removing an excerpt causes the first selection to become degenerate.
5580 assert_eq!(
5581 editor.selections.ranges(cx),
5582 [
5583 Point::new(0, 0)..Point::new(0, 0),
5584 Point::new(0, 1)..Point::new(0, 1)
5585 ]
5586 );
5587
5588 // Refreshing selections will relocate the first selection to the original buffer
5589 // location.
5590 editor.change_selections(None, cx, |s| s.refresh());
5591 assert_eq!(
5592 editor.selections.ranges(cx),
5593 [
5594 Point::new(0, 1)..Point::new(0, 1),
5595 Point::new(0, 3)..Point::new(0, 3)
5596 ]
5597 );
5598 assert!(editor.selections.pending_anchor().is_some());
5599 });
5600}
5601
5602#[gpui::test]
5603fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5604 init_test(cx, |_| {});
5605
5606 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5607 let mut excerpt1_id = None;
5608 let multibuffer = cx.add_model(|cx| {
5609 let mut multibuffer = MultiBuffer::new(0);
5610 excerpt1_id = multibuffer
5611 .push_excerpts(
5612 buffer.clone(),
5613 [
5614 ExcerptRange {
5615 context: Point::new(0, 0)..Point::new(1, 4),
5616 primary: None,
5617 },
5618 ExcerptRange {
5619 context: Point::new(1, 0)..Point::new(2, 4),
5620 primary: None,
5621 },
5622 ],
5623 cx,
5624 )
5625 .into_iter()
5626 .next();
5627 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5628 multibuffer
5629 });
5630
5631 let (_, editor) = cx.add_window(|cx| {
5632 let mut editor = build_editor(multibuffer.clone(), cx);
5633 let snapshot = editor.snapshot(cx);
5634 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5635 assert_eq!(
5636 editor.selections.ranges(cx),
5637 [Point::new(1, 3)..Point::new(1, 3)]
5638 );
5639 editor
5640 });
5641
5642 multibuffer.update(cx, |multibuffer, cx| {
5643 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5644 });
5645 editor.update(cx, |editor, cx| {
5646 assert_eq!(
5647 editor.selections.ranges(cx),
5648 [Point::new(0, 0)..Point::new(0, 0)]
5649 );
5650
5651 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5652 editor.change_selections(None, cx, |s| s.refresh());
5653 assert_eq!(
5654 editor.selections.ranges(cx),
5655 [Point::new(0, 3)..Point::new(0, 3)]
5656 );
5657 assert!(editor.selections.pending_anchor().is_some());
5658 });
5659}
5660
5661#[gpui::test]
5662async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5663 init_test(cx, |_| {});
5664
5665 let language = Arc::new(
5666 Language::new(
5667 LanguageConfig {
5668 brackets: BracketPairConfig {
5669 pairs: vec![
5670 BracketPair {
5671 start: "{".to_string(),
5672 end: "}".to_string(),
5673 close: true,
5674 newline: true,
5675 },
5676 BracketPair {
5677 start: "/* ".to_string(),
5678 end: " */".to_string(),
5679 close: true,
5680 newline: true,
5681 },
5682 ],
5683 ..Default::default()
5684 },
5685 ..Default::default()
5686 },
5687 Some(tree_sitter_rust::language()),
5688 )
5689 .with_indents_query("")
5690 .unwrap(),
5691 );
5692
5693 let text = concat!(
5694 "{ }\n", //
5695 " x\n", //
5696 " /* */\n", //
5697 "x\n", //
5698 "{{} }\n", //
5699 );
5700
5701 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5702 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5703 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5704 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5705 .await;
5706
5707 view.update(cx, |view, cx| {
5708 view.change_selections(None, cx, |s| {
5709 s.select_display_ranges([
5710 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5711 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5712 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5713 ])
5714 });
5715 view.newline(&Newline, cx);
5716
5717 assert_eq!(
5718 view.buffer().read(cx).read(cx).text(),
5719 concat!(
5720 "{ \n", // Suppress rustfmt
5721 "\n", //
5722 "}\n", //
5723 " x\n", //
5724 " /* \n", //
5725 " \n", //
5726 " */\n", //
5727 "x\n", //
5728 "{{} \n", //
5729 "}\n", //
5730 )
5731 );
5732 });
5733}
5734
5735#[gpui::test]
5736fn test_highlighted_ranges(cx: &mut TestAppContext) {
5737 init_test(cx, |_| {});
5738
5739 let (_, editor) = cx.add_window(|cx| {
5740 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5741 build_editor(buffer.clone(), cx)
5742 });
5743
5744 editor.update(cx, |editor, cx| {
5745 struct Type1;
5746 struct Type2;
5747
5748 let buffer = editor.buffer.read(cx).snapshot(cx);
5749
5750 let anchor_range =
5751 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5752
5753 editor.highlight_background::<Type1>(
5754 vec![
5755 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5756 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5757 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5758 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5759 ],
5760 |_| Color::red(),
5761 cx,
5762 );
5763 editor.highlight_background::<Type2>(
5764 vec![
5765 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5766 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5767 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5768 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5769 ],
5770 |_| Color::green(),
5771 cx,
5772 );
5773
5774 let snapshot = editor.snapshot(cx);
5775 let mut highlighted_ranges = editor.background_highlights_in_range(
5776 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5777 &snapshot,
5778 theme::current(cx).as_ref(),
5779 );
5780 // Enforce a consistent ordering based on color without relying on the ordering of the
5781 // highlight's `TypeId` which is non-deterministic.
5782 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5783 assert_eq!(
5784 highlighted_ranges,
5785 &[
5786 (
5787 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5788 Color::green(),
5789 ),
5790 (
5791 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5792 Color::green(),
5793 ),
5794 (
5795 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5796 Color::red(),
5797 ),
5798 (
5799 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5800 Color::red(),
5801 ),
5802 ]
5803 );
5804 assert_eq!(
5805 editor.background_highlights_in_range(
5806 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5807 &snapshot,
5808 theme::current(cx).as_ref(),
5809 ),
5810 &[(
5811 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5812 Color::red(),
5813 )]
5814 );
5815 });
5816}
5817
5818#[gpui::test]
5819async fn test_following(cx: &mut gpui::TestAppContext) {
5820 init_test(cx, |_| {});
5821
5822 let fs = FakeFs::new(cx.background());
5823 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5824
5825 let buffer = project.update(cx, |project, cx| {
5826 let buffer = project
5827 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5828 .unwrap();
5829 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5830 });
5831 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5832 let (_, follower) = cx.update(|cx| {
5833 cx.add_window(
5834 WindowOptions {
5835 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5836 ..Default::default()
5837 },
5838 |cx| build_editor(buffer.clone(), cx),
5839 )
5840 });
5841
5842 let is_still_following = Rc::new(RefCell::new(true));
5843 let follower_edit_event_count = Rc::new(RefCell::new(0));
5844 let pending_update = Rc::new(RefCell::new(None));
5845 follower.update(cx, {
5846 let update = pending_update.clone();
5847 let is_still_following = is_still_following.clone();
5848 let follower_edit_event_count = follower_edit_event_count.clone();
5849 |_, cx| {
5850 cx.subscribe(&leader, move |_, leader, event, cx| {
5851 leader
5852 .read(cx)
5853 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5854 })
5855 .detach();
5856
5857 cx.subscribe(&follower, move |_, _, event, cx| {
5858 if Editor::should_unfollow_on_event(event, cx) {
5859 *is_still_following.borrow_mut() = false;
5860 }
5861 if let Event::BufferEdited = event {
5862 *follower_edit_event_count.borrow_mut() += 1;
5863 }
5864 })
5865 .detach();
5866 }
5867 });
5868
5869 // Update the selections only
5870 leader.update(cx, |leader, cx| {
5871 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5872 });
5873 follower
5874 .update(cx, |follower, cx| {
5875 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5876 })
5877 .await
5878 .unwrap();
5879 follower.read_with(cx, |follower, cx| {
5880 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5881 });
5882 assert_eq!(*is_still_following.borrow(), true);
5883 assert_eq!(*follower_edit_event_count.borrow(), 0);
5884
5885 // Update the scroll position only
5886 leader.update(cx, |leader, cx| {
5887 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5888 });
5889 follower
5890 .update(cx, |follower, cx| {
5891 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5892 })
5893 .await
5894 .unwrap();
5895 assert_eq!(
5896 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5897 vec2f(1.5, 3.5)
5898 );
5899 assert_eq!(*is_still_following.borrow(), true);
5900 assert_eq!(*follower_edit_event_count.borrow(), 0);
5901
5902 // Update the selections and scroll position. The follower's scroll position is updated
5903 // via autoscroll, not via the leader's exact scroll position.
5904 leader.update(cx, |leader, cx| {
5905 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5906 leader.request_autoscroll(Autoscroll::newest(), cx);
5907 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5908 });
5909 follower
5910 .update(cx, |follower, cx| {
5911 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5912 })
5913 .await
5914 .unwrap();
5915 follower.update(cx, |follower, cx| {
5916 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5917 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5918 });
5919 assert_eq!(*is_still_following.borrow(), true);
5920
5921 // Creating a pending selection that precedes another selection
5922 leader.update(cx, |leader, cx| {
5923 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5924 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5925 });
5926 follower
5927 .update(cx, |follower, cx| {
5928 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5929 })
5930 .await
5931 .unwrap();
5932 follower.read_with(cx, |follower, cx| {
5933 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5934 });
5935 assert_eq!(*is_still_following.borrow(), true);
5936
5937 // Extend the pending selection so that it surrounds another selection
5938 leader.update(cx, |leader, cx| {
5939 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5940 });
5941 follower
5942 .update(cx, |follower, cx| {
5943 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5944 })
5945 .await
5946 .unwrap();
5947 follower.read_with(cx, |follower, cx| {
5948 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5949 });
5950
5951 // Scrolling locally breaks the follow
5952 follower.update(cx, |follower, cx| {
5953 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5954 follower.set_scroll_anchor(
5955 ScrollAnchor {
5956 anchor: top_anchor,
5957 offset: vec2f(0.0, 0.5),
5958 },
5959 cx,
5960 );
5961 });
5962 assert_eq!(*is_still_following.borrow(), false);
5963}
5964
5965#[gpui::test]
5966async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5967 init_test(cx, |_| {});
5968
5969 let fs = FakeFs::new(cx.background());
5970 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5971 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5972 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5973
5974 let leader = pane.update(cx, |_, cx| {
5975 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5976 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5977 });
5978
5979 // Start following the editor when it has no excerpts.
5980 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5981 let follower_1 = cx
5982 .update(|cx| {
5983 Editor::from_state_proto(
5984 pane.clone(),
5985 project.clone(),
5986 ViewId {
5987 creator: Default::default(),
5988 id: 0,
5989 },
5990 &mut state_message,
5991 cx,
5992 )
5993 })
5994 .unwrap()
5995 .await
5996 .unwrap();
5997
5998 let update_message = Rc::new(RefCell::new(None));
5999 follower_1.update(cx, {
6000 let update = update_message.clone();
6001 |_, cx| {
6002 cx.subscribe(&leader, move |_, leader, event, cx| {
6003 leader
6004 .read(cx)
6005 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6006 })
6007 .detach();
6008 }
6009 });
6010
6011 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6012 (
6013 project
6014 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6015 .unwrap(),
6016 project
6017 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6018 .unwrap(),
6019 )
6020 });
6021
6022 // Insert some excerpts.
6023 leader.update(cx, |leader, cx| {
6024 leader.buffer.update(cx, |multibuffer, cx| {
6025 let excerpt_ids = multibuffer.push_excerpts(
6026 buffer_1.clone(),
6027 [
6028 ExcerptRange {
6029 context: 1..6,
6030 primary: None,
6031 },
6032 ExcerptRange {
6033 context: 12..15,
6034 primary: None,
6035 },
6036 ExcerptRange {
6037 context: 0..3,
6038 primary: None,
6039 },
6040 ],
6041 cx,
6042 );
6043 multibuffer.insert_excerpts_after(
6044 excerpt_ids[0],
6045 buffer_2.clone(),
6046 [
6047 ExcerptRange {
6048 context: 8..12,
6049 primary: None,
6050 },
6051 ExcerptRange {
6052 context: 0..6,
6053 primary: None,
6054 },
6055 ],
6056 cx,
6057 );
6058 });
6059 });
6060
6061 // Apply the update of adding the excerpts.
6062 follower_1
6063 .update(cx, |follower, cx| {
6064 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6065 })
6066 .await
6067 .unwrap();
6068 assert_eq!(
6069 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6070 leader.read_with(cx, |editor, cx| editor.text(cx))
6071 );
6072 update_message.borrow_mut().take();
6073
6074 // Start following separately after it already has excerpts.
6075 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6076 let follower_2 = cx
6077 .update(|cx| {
6078 Editor::from_state_proto(
6079 pane.clone(),
6080 project.clone(),
6081 ViewId {
6082 creator: Default::default(),
6083 id: 0,
6084 },
6085 &mut state_message,
6086 cx,
6087 )
6088 })
6089 .unwrap()
6090 .await
6091 .unwrap();
6092 assert_eq!(
6093 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6094 leader.read_with(cx, |editor, cx| editor.text(cx))
6095 );
6096
6097 // Remove some excerpts.
6098 leader.update(cx, |leader, cx| {
6099 leader.buffer.update(cx, |multibuffer, cx| {
6100 let excerpt_ids = multibuffer.excerpt_ids();
6101 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6102 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6103 });
6104 });
6105
6106 // Apply the update of removing the excerpts.
6107 follower_1
6108 .update(cx, |follower, cx| {
6109 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6110 })
6111 .await
6112 .unwrap();
6113 follower_2
6114 .update(cx, |follower, cx| {
6115 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6116 })
6117 .await
6118 .unwrap();
6119 update_message.borrow_mut().take();
6120 assert_eq!(
6121 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6122 leader.read_with(cx, |editor, cx| editor.text(cx))
6123 );
6124}
6125
6126#[test]
6127fn test_combine_syntax_and_fuzzy_match_highlights() {
6128 let string = "abcdefghijklmnop";
6129 let syntax_ranges = [
6130 (
6131 0..3,
6132 HighlightStyle {
6133 color: Some(Color::red()),
6134 ..Default::default()
6135 },
6136 ),
6137 (
6138 4..8,
6139 HighlightStyle {
6140 color: Some(Color::green()),
6141 ..Default::default()
6142 },
6143 ),
6144 ];
6145 let match_indices = [4, 6, 7, 8];
6146 assert_eq!(
6147 combine_syntax_and_fuzzy_match_highlights(
6148 string,
6149 Default::default(),
6150 syntax_ranges.into_iter(),
6151 &match_indices,
6152 ),
6153 &[
6154 (
6155 0..3,
6156 HighlightStyle {
6157 color: Some(Color::red()),
6158 ..Default::default()
6159 },
6160 ),
6161 (
6162 4..5,
6163 HighlightStyle {
6164 color: Some(Color::green()),
6165 weight: Some(fonts::Weight::BOLD),
6166 ..Default::default()
6167 },
6168 ),
6169 (
6170 5..6,
6171 HighlightStyle {
6172 color: Some(Color::green()),
6173 ..Default::default()
6174 },
6175 ),
6176 (
6177 6..8,
6178 HighlightStyle {
6179 color: Some(Color::green()),
6180 weight: Some(fonts::Weight::BOLD),
6181 ..Default::default()
6182 },
6183 ),
6184 (
6185 8..9,
6186 HighlightStyle {
6187 weight: Some(fonts::Weight::BOLD),
6188 ..Default::default()
6189 },
6190 ),
6191 ]
6192 );
6193}
6194
6195#[gpui::test]
6196async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6197 init_test(cx, |_| {});
6198
6199 let mut cx = EditorTestContext::new(cx).await;
6200
6201 let diff_base = r#"
6202 use some::mod;
6203
6204 const A: u32 = 42;
6205
6206 fn main() {
6207 println!("hello");
6208
6209 println!("world");
6210 }
6211 "#
6212 .unindent();
6213
6214 // Edits are modified, removed, modified, added
6215 cx.set_state(
6216 &r#"
6217 use some::modified;
6218
6219 ˇ
6220 fn main() {
6221 println!("hello there");
6222
6223 println!("around the");
6224 println!("world");
6225 }
6226 "#
6227 .unindent(),
6228 );
6229
6230 cx.set_diff_base(Some(&diff_base));
6231 deterministic.run_until_parked();
6232
6233 cx.update_editor(|editor, cx| {
6234 //Wrap around the bottom of the buffer
6235 for _ in 0..3 {
6236 editor.go_to_hunk(&GoToHunk, cx);
6237 }
6238 });
6239
6240 cx.assert_editor_state(
6241 &r#"
6242 ˇuse some::modified;
6243
6244
6245 fn main() {
6246 println!("hello there");
6247
6248 println!("around the");
6249 println!("world");
6250 }
6251 "#
6252 .unindent(),
6253 );
6254
6255 cx.update_editor(|editor, cx| {
6256 //Wrap around the top of the buffer
6257 for _ in 0..2 {
6258 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6259 }
6260 });
6261
6262 cx.assert_editor_state(
6263 &r#"
6264 use some::modified;
6265
6266
6267 fn main() {
6268 ˇ println!("hello there");
6269
6270 println!("around the");
6271 println!("world");
6272 }
6273 "#
6274 .unindent(),
6275 );
6276
6277 cx.update_editor(|editor, cx| {
6278 editor.fold(&Fold, cx);
6279
6280 //Make sure that the fold only gets one hunk
6281 for _ in 0..4 {
6282 editor.go_to_hunk(&GoToHunk, cx);
6283 }
6284 });
6285
6286 cx.assert_editor_state(
6287 &r#"
6288 ˇuse some::modified;
6289
6290
6291 fn main() {
6292 println!("hello there");
6293
6294 println!("around the");
6295 println!("world");
6296 }
6297 "#
6298 .unindent(),
6299 );
6300}
6301
6302#[test]
6303fn test_split_words() {
6304 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6305 split_words(text).collect()
6306 }
6307
6308 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6309 assert_eq!(split("hello_world"), &["hello_", "world"]);
6310 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6311 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6312 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6313 assert_eq!(split("helloworld"), &["helloworld"]);
6314}
6315
6316#[gpui::test]
6317async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6318 init_test(cx, |_| {});
6319
6320 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6321 let mut assert = |before, after| {
6322 let _state_context = cx.set_state(before);
6323 cx.update_editor(|editor, cx| {
6324 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6325 });
6326 cx.assert_editor_state(after);
6327 };
6328
6329 // Outside bracket jumps to outside of matching bracket
6330 assert("console.logˇ(var);", "console.log(var)ˇ;");
6331 assert("console.log(var)ˇ;", "console.logˇ(var);");
6332
6333 // Inside bracket jumps to inside of matching bracket
6334 assert("console.log(ˇvar);", "console.log(varˇ);");
6335 assert("console.log(varˇ);", "console.log(ˇvar);");
6336
6337 // When outside a bracket and inside, favor jumping to the inside bracket
6338 assert(
6339 "console.log('foo', [1, 2, 3]ˇ);",
6340 "console.log(ˇ'foo', [1, 2, 3]);",
6341 );
6342 assert(
6343 "console.log(ˇ'foo', [1, 2, 3]);",
6344 "console.log('foo', [1, 2, 3]ˇ);",
6345 );
6346
6347 // Bias forward if two options are equally likely
6348 assert(
6349 "let result = curried_fun()ˇ();",
6350 "let result = curried_fun()()ˇ;",
6351 );
6352
6353 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6354 assert(
6355 indoc! {"
6356 function test() {
6357 console.log('test')ˇ
6358 }"},
6359 indoc! {"
6360 function test() {
6361 console.logˇ('test')
6362 }"},
6363 );
6364}
6365
6366#[gpui::test(iterations = 10)]
6367async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6368 init_test(cx, |_| {});
6369
6370 let (copilot, copilot_lsp) = Copilot::fake(cx);
6371 cx.update(|cx| cx.set_global(copilot));
6372 let mut cx = EditorLspTestContext::new_rust(
6373 lsp::ServerCapabilities {
6374 completion_provider: Some(lsp::CompletionOptions {
6375 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6376 ..Default::default()
6377 }),
6378 ..Default::default()
6379 },
6380 cx,
6381 )
6382 .await;
6383
6384 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6385 cx.set_state(indoc! {"
6386 oneˇ
6387 two
6388 three
6389 "});
6390 cx.simulate_keystroke(".");
6391 let _ = handle_completion_request(
6392 &mut cx,
6393 indoc! {"
6394 one.|<>
6395 two
6396 three
6397 "},
6398 vec!["completion_a", "completion_b"],
6399 );
6400 handle_copilot_completion_request(
6401 &copilot_lsp,
6402 vec![copilot::request::Completion {
6403 text: "one.copilot1".into(),
6404 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6405 ..Default::default()
6406 }],
6407 vec![],
6408 );
6409 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6410 cx.update_editor(|editor, cx| {
6411 assert!(editor.context_menu_visible());
6412 assert!(!editor.has_active_copilot_suggestion(cx));
6413
6414 // Confirming a completion inserts it and hides the context menu, without showing
6415 // the copilot suggestion afterwards.
6416 editor
6417 .confirm_completion(&Default::default(), cx)
6418 .unwrap()
6419 .detach();
6420 assert!(!editor.context_menu_visible());
6421 assert!(!editor.has_active_copilot_suggestion(cx));
6422 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6423 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6424 });
6425
6426 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6427 cx.set_state(indoc! {"
6428 oneˇ
6429 two
6430 three
6431 "});
6432 cx.simulate_keystroke(".");
6433 let _ = handle_completion_request(
6434 &mut cx,
6435 indoc! {"
6436 one.|<>
6437 two
6438 three
6439 "},
6440 vec![],
6441 );
6442 handle_copilot_completion_request(
6443 &copilot_lsp,
6444 vec![copilot::request::Completion {
6445 text: "one.copilot1".into(),
6446 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6447 ..Default::default()
6448 }],
6449 vec![],
6450 );
6451 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6452 cx.update_editor(|editor, cx| {
6453 assert!(!editor.context_menu_visible());
6454 assert!(editor.has_active_copilot_suggestion(cx));
6455 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6456 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6457 });
6458
6459 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6460 cx.set_state(indoc! {"
6461 oneˇ
6462 two
6463 three
6464 "});
6465 cx.simulate_keystroke(".");
6466 let _ = handle_completion_request(
6467 &mut cx,
6468 indoc! {"
6469 one.|<>
6470 two
6471 three
6472 "},
6473 vec!["completion_a", "completion_b"],
6474 );
6475 handle_copilot_completion_request(
6476 &copilot_lsp,
6477 vec![copilot::request::Completion {
6478 text: "one.copilot1".into(),
6479 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6480 ..Default::default()
6481 }],
6482 vec![],
6483 );
6484 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6485 cx.update_editor(|editor, cx| {
6486 assert!(editor.context_menu_visible());
6487 assert!(!editor.has_active_copilot_suggestion(cx));
6488
6489 // When hiding the context menu, the Copilot suggestion becomes visible.
6490 editor.hide_context_menu(cx);
6491 assert!(!editor.context_menu_visible());
6492 assert!(editor.has_active_copilot_suggestion(cx));
6493 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6494 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6495 });
6496
6497 // Ensure existing completion is interpolated when inserting again.
6498 cx.simulate_keystroke("c");
6499 deterministic.run_until_parked();
6500 cx.update_editor(|editor, cx| {
6501 assert!(!editor.context_menu_visible());
6502 assert!(editor.has_active_copilot_suggestion(cx));
6503 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6504 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6505 });
6506
6507 // After debouncing, new Copilot completions should be requested.
6508 handle_copilot_completion_request(
6509 &copilot_lsp,
6510 vec![copilot::request::Completion {
6511 text: "one.copilot2".into(),
6512 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6513 ..Default::default()
6514 }],
6515 vec![],
6516 );
6517 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6518 cx.update_editor(|editor, cx| {
6519 assert!(!editor.context_menu_visible());
6520 assert!(editor.has_active_copilot_suggestion(cx));
6521 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6522 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6523
6524 // Canceling should remove the active Copilot suggestion.
6525 editor.cancel(&Default::default(), cx);
6526 assert!(!editor.has_active_copilot_suggestion(cx));
6527 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6528 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6529
6530 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6531 editor.tab(&Default::default(), cx);
6532 assert!(!editor.has_active_copilot_suggestion(cx));
6533 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6534 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6535
6536 // When undoing the previously active suggestion is shown again.
6537 editor.undo(&Default::default(), cx);
6538 assert!(editor.has_active_copilot_suggestion(cx));
6539 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6540 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6541 });
6542
6543 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6544 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6545 cx.update_editor(|editor, cx| {
6546 assert!(editor.has_active_copilot_suggestion(cx));
6547 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6548 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6549
6550 // Tabbing when there is an active suggestion inserts it.
6551 editor.tab(&Default::default(), cx);
6552 assert!(!editor.has_active_copilot_suggestion(cx));
6553 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6554 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6555
6556 // When undoing the previously active suggestion is shown again.
6557 editor.undo(&Default::default(), cx);
6558 assert!(editor.has_active_copilot_suggestion(cx));
6559 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6560 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6561
6562 // Hide suggestion.
6563 editor.cancel(&Default::default(), cx);
6564 assert!(!editor.has_active_copilot_suggestion(cx));
6565 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6566 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6567 });
6568
6569 // If an edit occurs outside of this editor but no suggestion is being shown,
6570 // we won't make it visible.
6571 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6572 cx.update_editor(|editor, cx| {
6573 assert!(!editor.has_active_copilot_suggestion(cx));
6574 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6575 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6576 });
6577
6578 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6579 cx.update_editor(|editor, cx| {
6580 editor.set_text("fn foo() {\n \n}", cx);
6581 editor.change_selections(None, cx, |s| {
6582 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6583 });
6584 });
6585 handle_copilot_completion_request(
6586 &copilot_lsp,
6587 vec![copilot::request::Completion {
6588 text: " let x = 4;".into(),
6589 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6590 ..Default::default()
6591 }],
6592 vec![],
6593 );
6594
6595 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6596 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6597 cx.update_editor(|editor, cx| {
6598 assert!(editor.has_active_copilot_suggestion(cx));
6599 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6600 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6601
6602 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6603 editor.tab(&Default::default(), cx);
6604 assert!(editor.has_active_copilot_suggestion(cx));
6605 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6606 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6607
6608 // Tabbing again accepts the suggestion.
6609 editor.tab(&Default::default(), cx);
6610 assert!(!editor.has_active_copilot_suggestion(cx));
6611 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6612 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6613 });
6614}
6615
6616#[gpui::test]
6617async fn test_copilot_completion_invalidation(
6618 deterministic: Arc<Deterministic>,
6619 cx: &mut gpui::TestAppContext,
6620) {
6621 init_test(cx, |_| {});
6622
6623 let (copilot, copilot_lsp) = Copilot::fake(cx);
6624 cx.update(|cx| cx.set_global(copilot));
6625 let mut cx = EditorLspTestContext::new_rust(
6626 lsp::ServerCapabilities {
6627 completion_provider: Some(lsp::CompletionOptions {
6628 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6629 ..Default::default()
6630 }),
6631 ..Default::default()
6632 },
6633 cx,
6634 )
6635 .await;
6636
6637 cx.set_state(indoc! {"
6638 one
6639 twˇ
6640 three
6641 "});
6642
6643 handle_copilot_completion_request(
6644 &copilot_lsp,
6645 vec![copilot::request::Completion {
6646 text: "two.foo()".into(),
6647 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6648 ..Default::default()
6649 }],
6650 vec![],
6651 );
6652 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6653 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6654 cx.update_editor(|editor, cx| {
6655 assert!(editor.has_active_copilot_suggestion(cx));
6656 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6657 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6658
6659 editor.backspace(&Default::default(), cx);
6660 assert!(editor.has_active_copilot_suggestion(cx));
6661 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6662 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6663
6664 editor.backspace(&Default::default(), cx);
6665 assert!(editor.has_active_copilot_suggestion(cx));
6666 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6667 assert_eq!(editor.text(cx), "one\n\nthree\n");
6668
6669 // Deleting across the original suggestion range invalidates it.
6670 editor.backspace(&Default::default(), cx);
6671 assert!(!editor.has_active_copilot_suggestion(cx));
6672 assert_eq!(editor.display_text(cx), "one\nthree\n");
6673 assert_eq!(editor.text(cx), "one\nthree\n");
6674
6675 // Undoing the deletion restores the suggestion.
6676 editor.undo(&Default::default(), cx);
6677 assert!(editor.has_active_copilot_suggestion(cx));
6678 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6679 assert_eq!(editor.text(cx), "one\n\nthree\n");
6680 });
6681}
6682
6683#[gpui::test]
6684async fn test_copilot_multibuffer(
6685 deterministic: Arc<Deterministic>,
6686 cx: &mut gpui::TestAppContext,
6687) {
6688 init_test(cx, |_| {});
6689
6690 let (copilot, copilot_lsp) = Copilot::fake(cx);
6691 cx.update(|cx| cx.set_global(copilot));
6692
6693 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6694 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6695 let multibuffer = cx.add_model(|cx| {
6696 let mut multibuffer = MultiBuffer::new(0);
6697 multibuffer.push_excerpts(
6698 buffer_1.clone(),
6699 [ExcerptRange {
6700 context: Point::new(0, 0)..Point::new(2, 0),
6701 primary: None,
6702 }],
6703 cx,
6704 );
6705 multibuffer.push_excerpts(
6706 buffer_2.clone(),
6707 [ExcerptRange {
6708 context: Point::new(0, 0)..Point::new(2, 0),
6709 primary: None,
6710 }],
6711 cx,
6712 );
6713 multibuffer
6714 });
6715 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6716
6717 handle_copilot_completion_request(
6718 &copilot_lsp,
6719 vec![copilot::request::Completion {
6720 text: "b = 2 + a".into(),
6721 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6722 ..Default::default()
6723 }],
6724 vec![],
6725 );
6726 editor.update(cx, |editor, cx| {
6727 // Ensure copilot suggestions are shown for the first excerpt.
6728 editor.change_selections(None, cx, |s| {
6729 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6730 });
6731 editor.next_copilot_suggestion(&Default::default(), cx);
6732 });
6733 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6734 editor.update(cx, |editor, cx| {
6735 assert!(editor.has_active_copilot_suggestion(cx));
6736 assert_eq!(
6737 editor.display_text(cx),
6738 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6739 );
6740 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6741 });
6742
6743 handle_copilot_completion_request(
6744 &copilot_lsp,
6745 vec![copilot::request::Completion {
6746 text: "d = 4 + c".into(),
6747 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
6748 ..Default::default()
6749 }],
6750 vec![],
6751 );
6752 editor.update(cx, |editor, cx| {
6753 // Move to another excerpt, ensuring the suggestion gets cleared.
6754 editor.change_selections(None, cx, |s| {
6755 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
6756 });
6757 assert!(!editor.has_active_copilot_suggestion(cx));
6758 assert_eq!(
6759 editor.display_text(cx),
6760 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
6761 );
6762 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6763
6764 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
6765 editor.handle_input(" ", cx);
6766 assert!(!editor.has_active_copilot_suggestion(cx));
6767 assert_eq!(
6768 editor.display_text(cx),
6769 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
6770 );
6771 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6772 });
6773
6774 // Ensure the new suggestion is displayed when the debounce timeout expires.
6775 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6776 editor.update(cx, |editor, cx| {
6777 assert!(editor.has_active_copilot_suggestion(cx));
6778 assert_eq!(
6779 editor.display_text(cx),
6780 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
6781 );
6782 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6783 });
6784}
6785
6786#[gpui::test]
6787async fn test_copilot_disabled_globs(
6788 deterministic: Arc<Deterministic>,
6789 cx: &mut gpui::TestAppContext,
6790) {
6791 init_test(cx, |settings| {
6792 settings
6793 .copilot
6794 .get_or_insert(Default::default())
6795 .disabled_globs = Some(vec![".env*".to_string()]);
6796 });
6797
6798 let (copilot, copilot_lsp) = Copilot::fake(cx);
6799 cx.update(|cx| cx.set_global(copilot));
6800
6801 let fs = FakeFs::new(cx.background());
6802 fs.insert_tree(
6803 "/test",
6804 json!({
6805 ".env": "SECRET=something\n",
6806 "README.md": "hello\n"
6807 }),
6808 )
6809 .await;
6810 let project = Project::test(fs, ["/test".as_ref()], cx).await;
6811
6812 let private_buffer = project
6813 .update(cx, |project, cx| {
6814 project.open_local_buffer("/test/.env", cx)
6815 })
6816 .await
6817 .unwrap();
6818 let public_buffer = project
6819 .update(cx, |project, cx| {
6820 project.open_local_buffer("/test/README.md", cx)
6821 })
6822 .await
6823 .unwrap();
6824
6825 let multibuffer = cx.add_model(|cx| {
6826 let mut multibuffer = MultiBuffer::new(0);
6827 multibuffer.push_excerpts(
6828 private_buffer.clone(),
6829 [ExcerptRange {
6830 context: Point::new(0, 0)..Point::new(1, 0),
6831 primary: None,
6832 }],
6833 cx,
6834 );
6835 multibuffer.push_excerpts(
6836 public_buffer.clone(),
6837 [ExcerptRange {
6838 context: Point::new(0, 0)..Point::new(1, 0),
6839 primary: None,
6840 }],
6841 cx,
6842 );
6843 multibuffer
6844 });
6845 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6846
6847 let mut copilot_requests = copilot_lsp
6848 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
6849 Ok(copilot::request::GetCompletionsResult {
6850 completions: vec![copilot::request::Completion {
6851 text: "next line".into(),
6852 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6853 ..Default::default()
6854 }],
6855 })
6856 });
6857
6858 editor.update(cx, |editor, cx| {
6859 editor.change_selections(None, cx, |selections| {
6860 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
6861 });
6862 editor.next_copilot_suggestion(&Default::default(), cx);
6863 });
6864
6865 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6866 assert!(copilot_requests.try_next().is_err());
6867
6868 editor.update(cx, |editor, cx| {
6869 editor.change_selections(None, cx, |s| {
6870 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
6871 });
6872 editor.next_copilot_suggestion(&Default::default(), cx);
6873 });
6874
6875 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6876 assert!(copilot_requests.try_next().is_ok());
6877}
6878
6879fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
6880 let point = DisplayPoint::new(row as u32, column as u32);
6881 point..point
6882}
6883
6884fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
6885 let (text, ranges) = marked_text_ranges(marked_text, true);
6886 assert_eq!(view.text(cx), text);
6887 assert_eq!(
6888 view.selections.ranges(cx),
6889 ranges,
6890 "Assert selections are {}",
6891 marked_text
6892 );
6893}
6894
6895/// Handle completion request passing a marked string specifying where the completion
6896/// should be triggered from using '|' character, what range should be replaced, and what completions
6897/// should be returned using '<' and '>' to delimit the range
6898fn handle_completion_request<'a>(
6899 cx: &mut EditorLspTestContext<'a>,
6900 marked_string: &str,
6901 completions: Vec<&'static str>,
6902) -> impl Future<Output = ()> {
6903 let complete_from_marker: TextRangeMarker = '|'.into();
6904 let replace_range_marker: TextRangeMarker = ('<', '>').into();
6905 let (_, mut marked_ranges) = marked_text_ranges_by(
6906 marked_string,
6907 vec![complete_from_marker.clone(), replace_range_marker.clone()],
6908 );
6909
6910 let complete_from_position =
6911 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
6912 let replace_range =
6913 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
6914
6915 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
6916 let completions = completions.clone();
6917 async move {
6918 assert_eq!(params.text_document_position.text_document.uri, url.clone());
6919 assert_eq!(
6920 params.text_document_position.position,
6921 complete_from_position
6922 );
6923 Ok(Some(lsp::CompletionResponse::Array(
6924 completions
6925 .iter()
6926 .map(|completion_text| lsp::CompletionItem {
6927 label: completion_text.to_string(),
6928 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6929 range: replace_range,
6930 new_text: completion_text.to_string(),
6931 })),
6932 ..Default::default()
6933 })
6934 .collect(),
6935 )))
6936 }
6937 });
6938
6939 async move {
6940 request.next().await;
6941 }
6942}
6943
6944fn handle_resolve_completion_request<'a>(
6945 cx: &mut EditorLspTestContext<'a>,
6946 edits: Option<Vec<(&'static str, &'static str)>>,
6947) -> impl Future<Output = ()> {
6948 let edits = edits.map(|edits| {
6949 edits
6950 .iter()
6951 .map(|(marked_string, new_text)| {
6952 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
6953 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
6954 lsp::TextEdit::new(replace_range, new_text.to_string())
6955 })
6956 .collect::<Vec<_>>()
6957 });
6958
6959 let mut request =
6960 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
6961 let edits = edits.clone();
6962 async move {
6963 Ok(lsp::CompletionItem {
6964 additional_text_edits: edits,
6965 ..Default::default()
6966 })
6967 }
6968 });
6969
6970 async move {
6971 request.next().await;
6972 }
6973}
6974
6975fn handle_copilot_completion_request(
6976 lsp: &lsp::FakeLanguageServer,
6977 completions: Vec<copilot::request::Completion>,
6978 completions_cycling: Vec<copilot::request::Completion>,
6979) {
6980 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
6981 let completions = completions.clone();
6982 async move {
6983 Ok(copilot::request::GetCompletionsResult {
6984 completions: completions.clone(),
6985 })
6986 }
6987 });
6988 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
6989 let completions_cycling = completions_cycling.clone();
6990 async move {
6991 Ok(copilot::request::GetCompletionsResult {
6992 completions: completions_cycling.clone(),
6993 })
6994 }
6995 });
6996}
6997
6998pub(crate) fn update_test_settings(
6999 cx: &mut TestAppContext,
7000 f: impl Fn(&mut AllLanguageSettingsContent),
7001) {
7002 cx.update(|cx| {
7003 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7004 store.update_user_settings::<AllLanguageSettings>(cx, f);
7005 });
7006 });
7007}
7008
7009pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
7010 cx.foreground().forbid_parking();
7011
7012 cx.update(|cx| {
7013 cx.set_global(SettingsStore::test(cx));
7014 theme::init((), cx);
7015 client::init_settings(cx);
7016 language::init(cx);
7017 Project::init_settings(cx);
7018 workspace::init_settings(cx);
7019 crate::init(cx);
7020 });
7021
7022 update_test_settings(cx, f);
7023}