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