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