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