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