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