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