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