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_all_matches(cx: &mut gpui::TestAppContext) {
3825 init_test(cx, |_| {});
3826
3827 let mut cx = EditorTestContext::new(cx).await;
3828 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3829
3830 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches::default(), cx))
3831 .unwrap();
3832 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3833}
3834
3835#[gpui::test]
3836async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
3837 init_test(cx, |_| {});
3838
3839 let mut cx = EditorTestContext::new(cx).await;
3840 cx.set_state(
3841 r#"let foo = 2;
3842lˇet foo = 2;
3843let fooˇ = 2;
3844let foo = 2;
3845let foo = ˇ2;"#,
3846 );
3847
3848 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3849 .unwrap();
3850 cx.assert_editor_state(
3851 r#"let foo = 2;
3852«letˇ» foo = 2;
3853let «fooˇ» = 2;
3854let foo = 2;
3855let foo = «2ˇ»;"#,
3856 );
3857
3858 // noop for multiple selections with different contents
3859 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3860 .unwrap();
3861 cx.assert_editor_state(
3862 r#"let foo = 2;
3863«letˇ» foo = 2;
3864let «fooˇ» = 2;
3865let foo = 2;
3866let foo = «2ˇ»;"#,
3867 );
3868}
3869
3870#[gpui::test]
3871async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
3872 init_test(cx, |_| {});
3873
3874 let mut cx = EditorTestContext::new(cx).await;
3875 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3876
3877 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3878 .unwrap();
3879 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3880
3881 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3882 .unwrap();
3883 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3884
3885 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3886 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3887
3888 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3889 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3890
3891 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3892 .unwrap();
3893 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3894
3895 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3896 .unwrap();
3897 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
3898
3899 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3900 .unwrap();
3901 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3902}
3903
3904#[gpui::test]
3905async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
3906 init_test(cx, |_| {});
3907
3908 let mut cx = EditorTestContext::new(cx).await;
3909 cx.set_state(
3910 r#"let foo = 2;
3911lˇet foo = 2;
3912let fooˇ = 2;
3913let foo = 2;
3914let foo = ˇ2;"#,
3915 );
3916
3917 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3918 .unwrap();
3919 cx.assert_editor_state(
3920 r#"let foo = 2;
3921«letˇ» foo = 2;
3922let «fooˇ» = 2;
3923let foo = 2;
3924let foo = «2ˇ»;"#,
3925 );
3926
3927 // noop for multiple selections with different contents
3928 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3929 .unwrap();
3930 cx.assert_editor_state(
3931 r#"let foo = 2;
3932«letˇ» foo = 2;
3933let «fooˇ» = 2;
3934let foo = 2;
3935let foo = «2ˇ»;"#,
3936 );
3937}
3938
3939#[gpui::test]
3940async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
3941 init_test(cx, |_| {});
3942
3943 let mut cx = EditorTestContext::new(cx).await;
3944 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3945
3946 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3947 .unwrap();
3948 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3949
3950 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3951 .unwrap();
3952 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3953
3954 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3955 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3956
3957 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3958 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3959
3960 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3961 .unwrap();
3962 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3963
3964 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3965 .unwrap();
3966 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3967}
3968
3969#[gpui::test]
3970async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3971 init_test(cx, |_| {});
3972
3973 let language = Arc::new(Language::new(
3974 LanguageConfig::default(),
3975 Some(tree_sitter_rust::language()),
3976 ));
3977
3978 let text = r#"
3979 use mod1::mod2::{mod3, mod4};
3980
3981 fn fn_1(param1: bool, param2: &str) {
3982 let var1 = "text";
3983 }
3984 "#
3985 .unindent();
3986
3987 let buffer = cx
3988 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
3989 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3990 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
3991
3992 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3993 .await;
3994
3995 _ = view.update(cx, |view, cx| {
3996 view.change_selections(None, cx, |s| {
3997 s.select_display_ranges([
3998 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3999 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4000 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4001 ]);
4002 });
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, 23)..DisplayPoint::new(0, 27),
4009 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4010 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4011 ]
4012 );
4013
4014 _ = view.update(cx, |view, cx| {
4015 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4016 });
4017 assert_eq!(
4018 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4019 &[
4020 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4021 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4022 ]
4023 );
4024
4025 _ = view.update(cx, |view, cx| {
4026 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4027 });
4028 assert_eq!(
4029 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4030 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4031 );
4032
4033 // Trying to expand the selected syntax node one more time has no effect.
4034 _ = view.update(cx, |view, cx| {
4035 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4036 });
4037 assert_eq!(
4038 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4039 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4040 );
4041
4042 _ = view.update(cx, |view, cx| {
4043 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4044 });
4045 assert_eq!(
4046 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4047 &[
4048 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4049 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
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, 23)..DisplayPoint::new(0, 27),
4060 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4061 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4062 ]
4063 );
4064
4065 _ = view.update(cx, |view, cx| {
4066 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4067 });
4068 assert_eq!(
4069 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4070 &[
4071 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4072 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4073 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4074 ]
4075 );
4076
4077 // Trying to shrink the selected syntax node one more time has no effect.
4078 _ = view.update(cx, |view, cx| {
4079 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4080 });
4081 assert_eq!(
4082 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4083 &[
4084 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4085 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4086 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4087 ]
4088 );
4089
4090 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4091 // a fold.
4092 _ = view.update(cx, |view, cx| {
4093 view.fold_ranges(
4094 vec![
4095 Point::new(0, 21)..Point::new(0, 24),
4096 Point::new(3, 20)..Point::new(3, 22),
4097 ],
4098 true,
4099 cx,
4100 );
4101 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4102 });
4103 assert_eq!(
4104 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4105 &[
4106 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4107 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4108 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4109 ]
4110 );
4111}
4112
4113#[gpui::test]
4114async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4115 init_test(cx, |_| {});
4116
4117 let language = Arc::new(
4118 Language::new(
4119 LanguageConfig {
4120 brackets: BracketPairConfig {
4121 pairs: vec![
4122 BracketPair {
4123 start: "{".to_string(),
4124 end: "}".to_string(),
4125 close: false,
4126 newline: true,
4127 },
4128 BracketPair {
4129 start: "(".to_string(),
4130 end: ")".to_string(),
4131 close: false,
4132 newline: true,
4133 },
4134 ],
4135 ..Default::default()
4136 },
4137 ..Default::default()
4138 },
4139 Some(tree_sitter_rust::language()),
4140 )
4141 .with_indents_query(
4142 r#"
4143 (_ "(" ")" @end) @indent
4144 (_ "{" "}" @end) @indent
4145 "#,
4146 )
4147 .unwrap(),
4148 );
4149
4150 let text = "fn a() {}";
4151
4152 let buffer = cx
4153 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4154 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4155 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4156 editor
4157 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4158 .await;
4159
4160 _ = editor.update(cx, |editor, cx| {
4161 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4162 editor.newline(&Newline, cx);
4163 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4164 assert_eq!(
4165 editor.selections.ranges(cx),
4166 &[
4167 Point::new(1, 4)..Point::new(1, 4),
4168 Point::new(3, 4)..Point::new(3, 4),
4169 Point::new(5, 0)..Point::new(5, 0)
4170 ]
4171 );
4172 });
4173}
4174
4175#[gpui::test]
4176async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4177 init_test(cx, |_| {});
4178
4179 let mut cx = EditorTestContext::new(cx).await;
4180
4181 let language = Arc::new(Language::new(
4182 LanguageConfig {
4183 brackets: BracketPairConfig {
4184 pairs: vec![
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: true,
4195 newline: true,
4196 },
4197 BracketPair {
4198 start: "/*".to_string(),
4199 end: " */".to_string(),
4200 close: true,
4201 newline: true,
4202 },
4203 BracketPair {
4204 start: "[".to_string(),
4205 end: "]".to_string(),
4206 close: false,
4207 newline: true,
4208 },
4209 BracketPair {
4210 start: "\"".to_string(),
4211 end: "\"".to_string(),
4212 close: true,
4213 newline: false,
4214 },
4215 ],
4216 ..Default::default()
4217 },
4218 autoclose_before: "})]".to_string(),
4219 ..Default::default()
4220 },
4221 Some(tree_sitter_rust::language()),
4222 ));
4223
4224 let registry = Arc::new(LanguageRegistry::test());
4225 registry.add(language.clone());
4226 cx.update_buffer(|buffer, cx| {
4227 buffer.set_language_registry(registry);
4228 buffer.set_language(Some(language), cx);
4229 });
4230
4231 cx.set_state(
4232 &r#"
4233 🏀ˇ
4234 εˇ
4235 ❤️ˇ
4236 "#
4237 .unindent(),
4238 );
4239
4240 // autoclose multiple nested brackets at multiple cursors
4241 cx.update_editor(|view, cx| {
4242 view.handle_input("{", cx);
4243 view.handle_input("{", cx);
4244 view.handle_input("{", cx);
4245 });
4246 cx.assert_editor_state(
4247 &"
4248 🏀{{{ˇ}}}
4249 ε{{{ˇ}}}
4250 ❤️{{{ˇ}}}
4251 "
4252 .unindent(),
4253 );
4254
4255 // insert a different closing bracket
4256 cx.update_editor(|view, cx| {
4257 view.handle_input(")", cx);
4258 });
4259 cx.assert_editor_state(
4260 &"
4261 🏀{{{)ˇ}}}
4262 ε{{{)ˇ}}}
4263 ❤️{{{)ˇ}}}
4264 "
4265 .unindent(),
4266 );
4267
4268 // skip over the auto-closed brackets when typing a closing bracket
4269 cx.update_editor(|view, cx| {
4270 view.move_right(&MoveRight, cx);
4271 view.handle_input("}", cx);
4272 view.handle_input("}", cx);
4273 view.handle_input("}", cx);
4274 });
4275 cx.assert_editor_state(
4276 &"
4277 🏀{{{)}}}}ˇ
4278 ε{{{)}}}}ˇ
4279 ❤️{{{)}}}}ˇ
4280 "
4281 .unindent(),
4282 );
4283
4284 // autoclose multi-character pairs
4285 cx.set_state(
4286 &"
4287 ˇ
4288 ˇ
4289 "
4290 .unindent(),
4291 );
4292 cx.update_editor(|view, cx| {
4293 view.handle_input("/", cx);
4294 view.handle_input("*", cx);
4295 });
4296 cx.assert_editor_state(
4297 &"
4298 /*ˇ */
4299 /*ˇ */
4300 "
4301 .unindent(),
4302 );
4303
4304 // one cursor autocloses a multi-character pair, one cursor
4305 // does not autoclose.
4306 cx.set_state(
4307 &"
4308 /ˇ
4309 ˇ
4310 "
4311 .unindent(),
4312 );
4313 cx.update_editor(|view, cx| view.handle_input("*", cx));
4314 cx.assert_editor_state(
4315 &"
4316 /*ˇ */
4317 *ˇ
4318 "
4319 .unindent(),
4320 );
4321
4322 // Don't autoclose if the next character isn't whitespace and isn't
4323 // listed in the language's "autoclose_before" section.
4324 cx.set_state("ˇa b");
4325 cx.update_editor(|view, cx| view.handle_input("{", cx));
4326 cx.assert_editor_state("{ˇa b");
4327
4328 // Don't autoclose if `close` is false for the bracket pair
4329 cx.set_state("ˇ");
4330 cx.update_editor(|view, cx| view.handle_input("[", cx));
4331 cx.assert_editor_state("[ˇ");
4332
4333 // Surround with brackets if text is selected
4334 cx.set_state("«aˇ» b");
4335 cx.update_editor(|view, cx| view.handle_input("{", cx));
4336 cx.assert_editor_state("{«aˇ»} b");
4337
4338 // Autclose pair where the start and end characters are the same
4339 cx.set_state("aˇ");
4340 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4341 cx.assert_editor_state("a\"ˇ\"");
4342 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4343 cx.assert_editor_state("a\"\"ˇ");
4344}
4345
4346#[gpui::test]
4347async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4348 init_test(cx, |_| {});
4349
4350 let mut cx = EditorTestContext::new(cx).await;
4351
4352 let html_language = Arc::new(
4353 Language::new(
4354 LanguageConfig {
4355 name: "HTML".into(),
4356 brackets: BracketPairConfig {
4357 pairs: vec![
4358 BracketPair {
4359 start: "<".into(),
4360 end: ">".into(),
4361 close: true,
4362 ..Default::default()
4363 },
4364 BracketPair {
4365 start: "{".into(),
4366 end: "}".into(),
4367 close: true,
4368 ..Default::default()
4369 },
4370 BracketPair {
4371 start: "(".into(),
4372 end: ")".into(),
4373 close: true,
4374 ..Default::default()
4375 },
4376 ],
4377 ..Default::default()
4378 },
4379 autoclose_before: "})]>".into(),
4380 ..Default::default()
4381 },
4382 Some(tree_sitter_html::language()),
4383 )
4384 .with_injection_query(
4385 r#"
4386 (script_element
4387 (raw_text) @content
4388 (#set! "language" "javascript"))
4389 "#,
4390 )
4391 .unwrap(),
4392 );
4393
4394 let javascript_language = Arc::new(Language::new(
4395 LanguageConfig {
4396 name: "JavaScript".into(),
4397 brackets: BracketPairConfig {
4398 pairs: vec![
4399 BracketPair {
4400 start: "/*".into(),
4401 end: " */".into(),
4402 close: true,
4403 ..Default::default()
4404 },
4405 BracketPair {
4406 start: "{".into(),
4407 end: "}".into(),
4408 close: true,
4409 ..Default::default()
4410 },
4411 BracketPair {
4412 start: "(".into(),
4413 end: ")".into(),
4414 close: true,
4415 ..Default::default()
4416 },
4417 ],
4418 ..Default::default()
4419 },
4420 autoclose_before: "})]>".into(),
4421 ..Default::default()
4422 },
4423 Some(tree_sitter_typescript::language_tsx()),
4424 ));
4425
4426 let registry = Arc::new(LanguageRegistry::test());
4427 registry.add(html_language.clone());
4428 registry.add(javascript_language.clone());
4429
4430 cx.update_buffer(|buffer, cx| {
4431 buffer.set_language_registry(registry);
4432 buffer.set_language(Some(html_language), cx);
4433 });
4434
4435 cx.set_state(
4436 &r#"
4437 <body>ˇ
4438 <script>
4439 var x = 1;ˇ
4440 </script>
4441 </body>ˇ
4442 "#
4443 .unindent(),
4444 );
4445
4446 // Precondition: different languages are active at different locations.
4447 cx.update_editor(|editor, cx| {
4448 let snapshot = editor.snapshot(cx);
4449 let cursors = editor.selections.ranges::<usize>(cx);
4450 let languages = cursors
4451 .iter()
4452 .map(|c| snapshot.language_at(c.start).unwrap().name())
4453 .collect::<Vec<_>>();
4454 assert_eq!(
4455 languages,
4456 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4457 );
4458 });
4459
4460 // Angle brackets autoclose in HTML, but not JavaScript.
4461 cx.update_editor(|editor, cx| {
4462 editor.handle_input("<", cx);
4463 editor.handle_input("a", cx);
4464 });
4465 cx.assert_editor_state(
4466 &r#"
4467 <body><aˇ>
4468 <script>
4469 var x = 1;<aˇ
4470 </script>
4471 </body><aˇ>
4472 "#
4473 .unindent(),
4474 );
4475
4476 // Curly braces and parens autoclose in both HTML and JavaScript.
4477 cx.update_editor(|editor, cx| {
4478 editor.handle_input(" b=", cx);
4479 editor.handle_input("{", cx);
4480 editor.handle_input("c", cx);
4481 editor.handle_input("(", cx);
4482 });
4483 cx.assert_editor_state(
4484 &r#"
4485 <body><a b={c(ˇ)}>
4486 <script>
4487 var x = 1;<a b={c(ˇ)}
4488 </script>
4489 </body><a b={c(ˇ)}>
4490 "#
4491 .unindent(),
4492 );
4493
4494 // Brackets that were already autoclosed are skipped.
4495 cx.update_editor(|editor, cx| {
4496 editor.handle_input(")", cx);
4497 editor.handle_input("d", cx);
4498 editor.handle_input("}", cx);
4499 });
4500 cx.assert_editor_state(
4501 &r#"
4502 <body><a b={c()d}ˇ>
4503 <script>
4504 var x = 1;<a b={c()d}ˇ
4505 </script>
4506 </body><a b={c()d}ˇ>
4507 "#
4508 .unindent(),
4509 );
4510 cx.update_editor(|editor, cx| {
4511 editor.handle_input(">", cx);
4512 });
4513 cx.assert_editor_state(
4514 &r#"
4515 <body><a b={c()d}>ˇ
4516 <script>
4517 var x = 1;<a b={c()d}>ˇ
4518 </script>
4519 </body><a b={c()d}>ˇ
4520 "#
4521 .unindent(),
4522 );
4523
4524 // Reset
4525 cx.set_state(
4526 &r#"
4527 <body>ˇ
4528 <script>
4529 var x = 1;ˇ
4530 </script>
4531 </body>ˇ
4532 "#
4533 .unindent(),
4534 );
4535
4536 cx.update_editor(|editor, cx| {
4537 editor.handle_input("<", cx);
4538 });
4539 cx.assert_editor_state(
4540 &r#"
4541 <body><ˇ>
4542 <script>
4543 var x = 1;<ˇ
4544 </script>
4545 </body><ˇ>
4546 "#
4547 .unindent(),
4548 );
4549
4550 // When backspacing, the closing angle brackets are removed.
4551 cx.update_editor(|editor, cx| {
4552 editor.backspace(&Backspace, cx);
4553 });
4554 cx.assert_editor_state(
4555 &r#"
4556 <body>ˇ
4557 <script>
4558 var x = 1;ˇ
4559 </script>
4560 </body>ˇ
4561 "#
4562 .unindent(),
4563 );
4564
4565 // Block comments autoclose in JavaScript, but not HTML.
4566 cx.update_editor(|editor, cx| {
4567 editor.handle_input("/", cx);
4568 editor.handle_input("*", cx);
4569 });
4570 cx.assert_editor_state(
4571 &r#"
4572 <body>/*ˇ
4573 <script>
4574 var x = 1;/*ˇ */
4575 </script>
4576 </body>/*ˇ
4577 "#
4578 .unindent(),
4579 );
4580}
4581
4582#[gpui::test]
4583async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4584 init_test(cx, |_| {});
4585
4586 let mut cx = EditorTestContext::new(cx).await;
4587
4588 let rust_language = Arc::new(
4589 Language::new(
4590 LanguageConfig {
4591 name: "Rust".into(),
4592 brackets: serde_json::from_value(json!([
4593 { "start": "{", "end": "}", "close": true, "newline": true },
4594 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4595 ]))
4596 .unwrap(),
4597 autoclose_before: "})]>".into(),
4598 ..Default::default()
4599 },
4600 Some(tree_sitter_rust::language()),
4601 )
4602 .with_override_query("(string_literal) @string")
4603 .unwrap(),
4604 );
4605
4606 let registry = Arc::new(LanguageRegistry::test());
4607 registry.add(rust_language.clone());
4608
4609 cx.update_buffer(|buffer, cx| {
4610 buffer.set_language_registry(registry);
4611 buffer.set_language(Some(rust_language), cx);
4612 });
4613
4614 cx.set_state(
4615 &r#"
4616 let x = ˇ
4617 "#
4618 .unindent(),
4619 );
4620
4621 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
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 // Inserting another quotation mark. The cursor moves across the existing
4633 // automatically-inserted quotation mark.
4634 cx.update_editor(|editor, cx| {
4635 editor.handle_input("\"", cx);
4636 });
4637 cx.assert_editor_state(
4638 &r#"
4639 let x = ""ˇ
4640 "#
4641 .unindent(),
4642 );
4643
4644 // Reset
4645 cx.set_state(
4646 &r#"
4647 let x = ˇ
4648 "#
4649 .unindent(),
4650 );
4651
4652 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4653 cx.update_editor(|editor, cx| {
4654 editor.handle_input("\"", cx);
4655 editor.handle_input(" ", cx);
4656 editor.move_left(&Default::default(), cx);
4657 editor.handle_input("\\", cx);
4658 editor.handle_input("\"", cx);
4659 });
4660 cx.assert_editor_state(
4661 &r#"
4662 let x = "\"ˇ "
4663 "#
4664 .unindent(),
4665 );
4666
4667 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4668 // mark. Nothing is inserted.
4669 cx.update_editor(|editor, cx| {
4670 editor.move_right(&Default::default(), cx);
4671 editor.handle_input("\"", cx);
4672 });
4673 cx.assert_editor_state(
4674 &r#"
4675 let x = "\" "ˇ
4676 "#
4677 .unindent(),
4678 );
4679}
4680
4681#[gpui::test]
4682async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4683 init_test(cx, |_| {});
4684
4685 let language = Arc::new(Language::new(
4686 LanguageConfig {
4687 brackets: BracketPairConfig {
4688 pairs: vec![
4689 BracketPair {
4690 start: "{".to_string(),
4691 end: "}".to_string(),
4692 close: true,
4693 newline: true,
4694 },
4695 BracketPair {
4696 start: "/* ".to_string(),
4697 end: "*/".to_string(),
4698 close: true,
4699 ..Default::default()
4700 },
4701 ],
4702 ..Default::default()
4703 },
4704 ..Default::default()
4705 },
4706 Some(tree_sitter_rust::language()),
4707 ));
4708
4709 let text = r#"
4710 a
4711 b
4712 c
4713 "#
4714 .unindent();
4715
4716 let buffer = cx
4717 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4718 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4719 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4720 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4721 .await;
4722
4723 _ = view.update(cx, |view, cx| {
4724 view.change_selections(None, cx, |s| {
4725 s.select_display_ranges([
4726 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4727 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4728 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4729 ])
4730 });
4731
4732 view.handle_input("{", cx);
4733 view.handle_input("{", cx);
4734 view.handle_input("{", cx);
4735 assert_eq!(
4736 view.text(cx),
4737 "
4738 {{{a}}}
4739 {{{b}}}
4740 {{{c}}}
4741 "
4742 .unindent()
4743 );
4744 assert_eq!(
4745 view.selections.display_ranges(cx),
4746 [
4747 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4748 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4749 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4750 ]
4751 );
4752
4753 view.undo(&Undo, cx);
4754 view.undo(&Undo, cx);
4755 view.undo(&Undo, cx);
4756 assert_eq!(
4757 view.text(cx),
4758 "
4759 a
4760 b
4761 c
4762 "
4763 .unindent()
4764 );
4765 assert_eq!(
4766 view.selections.display_ranges(cx),
4767 [
4768 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4769 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4770 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4771 ]
4772 );
4773
4774 // Ensure inserting the first character of a multi-byte bracket pair
4775 // doesn't surround the selections with the bracket.
4776 view.handle_input("/", cx);
4777 assert_eq!(
4778 view.text(cx),
4779 "
4780 /
4781 /
4782 /
4783 "
4784 .unindent()
4785 );
4786 assert_eq!(
4787 view.selections.display_ranges(cx),
4788 [
4789 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4790 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4791 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4792 ]
4793 );
4794
4795 view.undo(&Undo, cx);
4796 assert_eq!(
4797 view.text(cx),
4798 "
4799 a
4800 b
4801 c
4802 "
4803 .unindent()
4804 );
4805 assert_eq!(
4806 view.selections.display_ranges(cx),
4807 [
4808 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4809 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4810 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4811 ]
4812 );
4813
4814 // Ensure inserting the last character of a multi-byte bracket pair
4815 // doesn't surround the selections with the bracket.
4816 view.handle_input("*", cx);
4817 assert_eq!(
4818 view.text(cx),
4819 "
4820 *
4821 *
4822 *
4823 "
4824 .unindent()
4825 );
4826 assert_eq!(
4827 view.selections.display_ranges(cx),
4828 [
4829 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4830 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4831 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4832 ]
4833 );
4834 });
4835}
4836
4837#[gpui::test]
4838async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4839 init_test(cx, |_| {});
4840
4841 let language = Arc::new(Language::new(
4842 LanguageConfig {
4843 brackets: BracketPairConfig {
4844 pairs: vec![BracketPair {
4845 start: "{".to_string(),
4846 end: "}".to_string(),
4847 close: true,
4848 newline: true,
4849 }],
4850 ..Default::default()
4851 },
4852 autoclose_before: "}".to_string(),
4853 ..Default::default()
4854 },
4855 Some(tree_sitter_rust::language()),
4856 ));
4857
4858 let text = r#"
4859 a
4860 b
4861 c
4862 "#
4863 .unindent();
4864
4865 let buffer = cx
4866 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
4867 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4868 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4869 editor
4870 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4871 .await;
4872
4873 _ = editor.update(cx, |editor, cx| {
4874 editor.change_selections(None, cx, |s| {
4875 s.select_ranges([
4876 Point::new(0, 1)..Point::new(0, 1),
4877 Point::new(1, 1)..Point::new(1, 1),
4878 Point::new(2, 1)..Point::new(2, 1),
4879 ])
4880 });
4881
4882 editor.handle_input("{", cx);
4883 editor.handle_input("{", cx);
4884 editor.handle_input("_", cx);
4885 assert_eq!(
4886 editor.text(cx),
4887 "
4888 a{{_}}
4889 b{{_}}
4890 c{{_}}
4891 "
4892 .unindent()
4893 );
4894 assert_eq!(
4895 editor.selections.ranges::<Point>(cx),
4896 [
4897 Point::new(0, 4)..Point::new(0, 4),
4898 Point::new(1, 4)..Point::new(1, 4),
4899 Point::new(2, 4)..Point::new(2, 4)
4900 ]
4901 );
4902
4903 editor.backspace(&Default::default(), cx);
4904 editor.backspace(&Default::default(), cx);
4905 assert_eq!(
4906 editor.text(cx),
4907 "
4908 a{}
4909 b{}
4910 c{}
4911 "
4912 .unindent()
4913 );
4914 assert_eq!(
4915 editor.selections.ranges::<Point>(cx),
4916 [
4917 Point::new(0, 2)..Point::new(0, 2),
4918 Point::new(1, 2)..Point::new(1, 2),
4919 Point::new(2, 2)..Point::new(2, 2)
4920 ]
4921 );
4922
4923 editor.delete_to_previous_word_start(&Default::default(), cx);
4924 assert_eq!(
4925 editor.text(cx),
4926 "
4927 a
4928 b
4929 c
4930 "
4931 .unindent()
4932 );
4933 assert_eq!(
4934 editor.selections.ranges::<Point>(cx),
4935 [
4936 Point::new(0, 1)..Point::new(0, 1),
4937 Point::new(1, 1)..Point::new(1, 1),
4938 Point::new(2, 1)..Point::new(2, 1)
4939 ]
4940 );
4941 });
4942}
4943
4944#[gpui::test]
4945async fn test_snippets(cx: &mut gpui::TestAppContext) {
4946 init_test(cx, |_| {});
4947
4948 let (text, insertion_ranges) = marked_text_ranges(
4949 indoc! {"
4950 a.ˇ b
4951 a.ˇ b
4952 a.ˇ b
4953 "},
4954 false,
4955 );
4956
4957 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4958 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4959
4960 _ = editor.update(cx, |editor, cx| {
4961 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4962
4963 editor
4964 .insert_snippet(&insertion_ranges, snippet, cx)
4965 .unwrap();
4966
4967 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4968 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4969 assert_eq!(editor.text(cx), expected_text);
4970 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4971 }
4972
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 // Can't move earlier than the first tab stop
4984 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4985 assert(
4986 editor,
4987 cx,
4988 indoc! {"
4989 a.f(«one», two, «three») b
4990 a.f(«one», two, «three») b
4991 a.f(«one», two, «three») b
4992 "},
4993 );
4994
4995 assert!(editor.move_to_next_snippet_tabstop(cx));
4996 assert(
4997 editor,
4998 cx,
4999 indoc! {"
5000 a.f(one, «two», three) b
5001 a.f(one, «two», three) b
5002 a.f(one, «two», three) b
5003 "},
5004 );
5005
5006 editor.move_to_prev_snippet_tabstop(cx);
5007 assert(
5008 editor,
5009 cx,
5010 indoc! {"
5011 a.f(«one», two, «three») b
5012 a.f(«one», two, «three») b
5013 a.f(«one», two, «three») b
5014 "},
5015 );
5016
5017 assert!(editor.move_to_next_snippet_tabstop(cx));
5018 assert(
5019 editor,
5020 cx,
5021 indoc! {"
5022 a.f(one, «two», three) b
5023 a.f(one, «two», three) b
5024 a.f(one, «two», three) b
5025 "},
5026 );
5027 assert!(editor.move_to_next_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 // As soon as the last tab stop is reached, snippet state is gone
5039 editor.move_to_prev_snippet_tabstop(cx);
5040 assert(
5041 editor,
5042 cx,
5043 indoc! {"
5044 a.f(one, two, three)ˇ b
5045 a.f(one, two, three)ˇ b
5046 a.f(one, two, three)ˇ b
5047 "},
5048 );
5049 });
5050}
5051
5052#[gpui::test]
5053async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
5054 init_test(cx, |_| {});
5055
5056 let mut language = Language::new(
5057 LanguageConfig {
5058 name: "Rust".into(),
5059 path_suffixes: vec!["rs".to_string()],
5060 ..Default::default()
5061 },
5062 Some(tree_sitter_rust::language()),
5063 );
5064 let mut fake_servers = language
5065 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5066 capabilities: lsp::ServerCapabilities {
5067 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5068 ..Default::default()
5069 },
5070 ..Default::default()
5071 }))
5072 .await;
5073
5074 let fs = FakeFs::new(cx.executor());
5075 fs.insert_file("/file.rs", Default::default()).await;
5076
5077 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5078 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5079 let buffer = project
5080 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5081 .await
5082 .unwrap();
5083
5084 cx.executor().start_waiting();
5085 let fake_server = fake_servers.next().await.unwrap();
5086
5087 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5088 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5089 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5090 assert!(cx.read(|cx| editor.is_dirty(cx)));
5091
5092 let save = editor
5093 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5094 .unwrap();
5095 fake_server
5096 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5097 assert_eq!(
5098 params.text_document.uri,
5099 lsp::Url::from_file_path("/file.rs").unwrap()
5100 );
5101 assert_eq!(params.options.tab_size, 4);
5102 Ok(Some(vec![lsp::TextEdit::new(
5103 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5104 ", ".to_string(),
5105 )]))
5106 })
5107 .next()
5108 .await;
5109 cx.executor().start_waiting();
5110 let _x = save.await;
5111
5112 assert_eq!(
5113 editor.update(cx, |editor, cx| editor.text(cx)),
5114 "one, two\nthree\n"
5115 );
5116 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5117
5118 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5119 assert!(cx.read(|cx| editor.is_dirty(cx)));
5120
5121 // Ensure we can still save even if formatting hangs.
5122 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5123 assert_eq!(
5124 params.text_document.uri,
5125 lsp::Url::from_file_path("/file.rs").unwrap()
5126 );
5127 futures::future::pending::<()>().await;
5128 unreachable!()
5129 });
5130 let save = editor
5131 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5132 .unwrap();
5133 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5134 cx.executor().start_waiting();
5135 save.await;
5136 assert_eq!(
5137 editor.update(cx, |editor, cx| editor.text(cx)),
5138 "one\ntwo\nthree\n"
5139 );
5140 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5141
5142 // Set rust language override and assert overridden tabsize is sent to language server
5143 update_test_language_settings(cx, |settings| {
5144 settings.languages.insert(
5145 "Rust".into(),
5146 LanguageSettingsContent {
5147 tab_size: NonZeroU32::new(8),
5148 ..Default::default()
5149 },
5150 );
5151 });
5152
5153 let save = editor
5154 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5155 .unwrap();
5156 fake_server
5157 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5158 assert_eq!(
5159 params.text_document.uri,
5160 lsp::Url::from_file_path("/file.rs").unwrap()
5161 );
5162 assert_eq!(params.options.tab_size, 8);
5163 Ok(Some(vec![]))
5164 })
5165 .next()
5166 .await;
5167 cx.executor().start_waiting();
5168 save.await;
5169}
5170
5171#[gpui::test]
5172async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5173 init_test(cx, |_| {});
5174
5175 let mut language = Language::new(
5176 LanguageConfig {
5177 name: "Rust".into(),
5178 path_suffixes: vec!["rs".to_string()],
5179 ..Default::default()
5180 },
5181 Some(tree_sitter_rust::language()),
5182 );
5183 let mut fake_servers = language
5184 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5185 capabilities: lsp::ServerCapabilities {
5186 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5187 ..Default::default()
5188 },
5189 ..Default::default()
5190 }))
5191 .await;
5192
5193 let fs = FakeFs::new(cx.executor());
5194 fs.insert_file("/file.rs", Default::default()).await;
5195
5196 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5197 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5198 let buffer = project
5199 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5200 .await
5201 .unwrap();
5202
5203 cx.executor().start_waiting();
5204 let fake_server = fake_servers.next().await.unwrap();
5205
5206 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5207 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5208 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5209 assert!(cx.read(|cx| editor.is_dirty(cx)));
5210
5211 let save = editor
5212 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5213 .unwrap();
5214 fake_server
5215 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5216 assert_eq!(
5217 params.text_document.uri,
5218 lsp::Url::from_file_path("/file.rs").unwrap()
5219 );
5220 assert_eq!(params.options.tab_size, 4);
5221 Ok(Some(vec![lsp::TextEdit::new(
5222 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5223 ", ".to_string(),
5224 )]))
5225 })
5226 .next()
5227 .await;
5228 cx.executor().start_waiting();
5229 save.await;
5230 assert_eq!(
5231 editor.update(cx, |editor, cx| editor.text(cx)),
5232 "one, two\nthree\n"
5233 );
5234 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5235
5236 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5237 assert!(cx.read(|cx| editor.is_dirty(cx)));
5238
5239 // Ensure we can still save even if formatting hangs.
5240 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5241 move |params, _| async move {
5242 assert_eq!(
5243 params.text_document.uri,
5244 lsp::Url::from_file_path("/file.rs").unwrap()
5245 );
5246 futures::future::pending::<()>().await;
5247 unreachable!()
5248 },
5249 );
5250 let save = editor
5251 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5252 .unwrap();
5253 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5254 cx.executor().start_waiting();
5255 save.await;
5256 assert_eq!(
5257 editor.update(cx, |editor, cx| editor.text(cx)),
5258 "one\ntwo\nthree\n"
5259 );
5260 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5261
5262 // Set rust language override and assert overridden tabsize is sent to language server
5263 update_test_language_settings(cx, |settings| {
5264 settings.languages.insert(
5265 "Rust".into(),
5266 LanguageSettingsContent {
5267 tab_size: NonZeroU32::new(8),
5268 ..Default::default()
5269 },
5270 );
5271 });
5272
5273 let save = editor
5274 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5275 .unwrap();
5276 fake_server
5277 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5278 assert_eq!(
5279 params.text_document.uri,
5280 lsp::Url::from_file_path("/file.rs").unwrap()
5281 );
5282 assert_eq!(params.options.tab_size, 8);
5283 Ok(Some(vec![]))
5284 })
5285 .next()
5286 .await;
5287 cx.executor().start_waiting();
5288 save.await;
5289}
5290
5291#[gpui::test]
5292async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5293 init_test(cx, |settings| {
5294 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5295 });
5296
5297 let mut language = Language::new(
5298 LanguageConfig {
5299 name: "Rust".into(),
5300 path_suffixes: vec!["rs".to_string()],
5301 // Enable Prettier formatting for the same buffer, and ensure
5302 // LSP is called instead of Prettier.
5303 prettier_parser_name: Some("test_parser".to_string()),
5304 ..Default::default()
5305 },
5306 Some(tree_sitter_rust::language()),
5307 );
5308 let mut fake_servers = language
5309 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5310 capabilities: lsp::ServerCapabilities {
5311 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5312 ..Default::default()
5313 },
5314 ..Default::default()
5315 }))
5316 .await;
5317
5318 let fs = FakeFs::new(cx.executor());
5319 fs.insert_file("/file.rs", Default::default()).await;
5320
5321 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5322 _ = project.update(cx, |project, _| {
5323 project.languages().add(Arc::new(language));
5324 });
5325 let buffer = project
5326 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5327 .await
5328 .unwrap();
5329
5330 cx.executor().start_waiting();
5331 let fake_server = fake_servers.next().await.unwrap();
5332
5333 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5334 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5335 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5336
5337 let format = editor
5338 .update(cx, |editor, cx| {
5339 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5340 })
5341 .unwrap();
5342 fake_server
5343 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5344 assert_eq!(
5345 params.text_document.uri,
5346 lsp::Url::from_file_path("/file.rs").unwrap()
5347 );
5348 assert_eq!(params.options.tab_size, 4);
5349 Ok(Some(vec![lsp::TextEdit::new(
5350 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5351 ", ".to_string(),
5352 )]))
5353 })
5354 .next()
5355 .await;
5356 cx.executor().start_waiting();
5357 format.await;
5358 assert_eq!(
5359 editor.update(cx, |editor, cx| editor.text(cx)),
5360 "one, two\nthree\n"
5361 );
5362
5363 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5364 // Ensure we don't lock if formatting hangs.
5365 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5366 assert_eq!(
5367 params.text_document.uri,
5368 lsp::Url::from_file_path("/file.rs").unwrap()
5369 );
5370 futures::future::pending::<()>().await;
5371 unreachable!()
5372 });
5373 let format = editor
5374 .update(cx, |editor, cx| {
5375 editor.perform_format(project, FormatTrigger::Manual, cx)
5376 })
5377 .unwrap();
5378 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5379 cx.executor().start_waiting();
5380 format.await;
5381 assert_eq!(
5382 editor.update(cx, |editor, cx| editor.text(cx)),
5383 "one\ntwo\nthree\n"
5384 );
5385}
5386
5387#[gpui::test]
5388async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5389 init_test(cx, |_| {});
5390
5391 let mut cx = EditorLspTestContext::new_rust(
5392 lsp::ServerCapabilities {
5393 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5394 ..Default::default()
5395 },
5396 cx,
5397 )
5398 .await;
5399
5400 cx.set_state(indoc! {"
5401 one.twoˇ
5402 "});
5403
5404 // The format request takes a long time. When it completes, it inserts
5405 // a newline and an indent before the `.`
5406 cx.lsp
5407 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5408 let executor = cx.background_executor().clone();
5409 async move {
5410 executor.timer(Duration::from_millis(100)).await;
5411 Ok(Some(vec![lsp::TextEdit {
5412 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5413 new_text: "\n ".into(),
5414 }]))
5415 }
5416 });
5417
5418 // Submit a format request.
5419 let format_1 = cx
5420 .update_editor(|editor, cx| editor.format(&Format, cx))
5421 .unwrap();
5422 cx.executor().run_until_parked();
5423
5424 // Submit a second format request.
5425 let format_2 = cx
5426 .update_editor(|editor, cx| editor.format(&Format, cx))
5427 .unwrap();
5428 cx.executor().run_until_parked();
5429
5430 // Wait for both format requests to complete
5431 cx.executor().advance_clock(Duration::from_millis(200));
5432 cx.executor().start_waiting();
5433 format_1.await.unwrap();
5434 cx.executor().start_waiting();
5435 format_2.await.unwrap();
5436
5437 // The formatting edits only happens once.
5438 cx.assert_editor_state(indoc! {"
5439 one
5440 .twoˇ
5441 "});
5442}
5443
5444#[gpui::test]
5445async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5446 init_test(cx, |settings| {
5447 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5448 });
5449
5450 let mut cx = EditorLspTestContext::new_rust(
5451 lsp::ServerCapabilities {
5452 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5453 ..Default::default()
5454 },
5455 cx,
5456 )
5457 .await;
5458
5459 // Set up a buffer white some trailing whitespace and no trailing newline.
5460 cx.set_state(
5461 &[
5462 "one ", //
5463 "twoˇ", //
5464 "three ", //
5465 "four", //
5466 ]
5467 .join("\n"),
5468 );
5469
5470 // Submit a format request.
5471 let format = cx
5472 .update_editor(|editor, cx| editor.format(&Format, cx))
5473 .unwrap();
5474
5475 // Record which buffer changes have been sent to the language server
5476 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5477 cx.lsp
5478 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5479 let buffer_changes = buffer_changes.clone();
5480 move |params, _| {
5481 buffer_changes.lock().extend(
5482 params
5483 .content_changes
5484 .into_iter()
5485 .map(|e| (e.range.unwrap(), e.text)),
5486 );
5487 }
5488 });
5489
5490 // Handle formatting requests to the language server.
5491 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5492 let buffer_changes = buffer_changes.clone();
5493 move |_, _| {
5494 // When formatting is requested, trailing whitespace has already been stripped,
5495 // and the trailing newline has already been added.
5496 assert_eq!(
5497 &buffer_changes.lock()[1..],
5498 &[
5499 (
5500 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5501 "".into()
5502 ),
5503 (
5504 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5505 "".into()
5506 ),
5507 (
5508 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5509 "\n".into()
5510 ),
5511 ]
5512 );
5513
5514 // Insert blank lines between each line of the buffer.
5515 async move {
5516 Ok(Some(vec![
5517 lsp::TextEdit {
5518 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5519 new_text: "\n".into(),
5520 },
5521 lsp::TextEdit {
5522 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5523 new_text: "\n".into(),
5524 },
5525 ]))
5526 }
5527 }
5528 });
5529
5530 // After formatting the buffer, the trailing whitespace is stripped,
5531 // a newline is appended, and the edits provided by the language server
5532 // have been applied.
5533 format.await.unwrap();
5534 cx.assert_editor_state(
5535 &[
5536 "one", //
5537 "", //
5538 "twoˇ", //
5539 "", //
5540 "three", //
5541 "four", //
5542 "", //
5543 ]
5544 .join("\n"),
5545 );
5546
5547 // Undoing the formatting undoes the trailing whitespace removal, the
5548 // trailing newline, and the LSP edits.
5549 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5550 cx.assert_editor_state(
5551 &[
5552 "one ", //
5553 "twoˇ", //
5554 "three ", //
5555 "four", //
5556 ]
5557 .join("\n"),
5558 );
5559}
5560
5561#[gpui::test]
5562async fn test_completion(cx: &mut gpui::TestAppContext) {
5563 init_test(cx, |_| {});
5564
5565 let mut cx = EditorLspTestContext::new_rust(
5566 lsp::ServerCapabilities {
5567 completion_provider: Some(lsp::CompletionOptions {
5568 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5569 resolve_provider: Some(true),
5570 ..Default::default()
5571 }),
5572 ..Default::default()
5573 },
5574 cx,
5575 )
5576 .await;
5577
5578 cx.set_state(indoc! {"
5579 oneˇ
5580 two
5581 three
5582 "});
5583 cx.simulate_keystroke(".");
5584 handle_completion_request(
5585 &mut cx,
5586 indoc! {"
5587 one.|<>
5588 two
5589 three
5590 "},
5591 vec!["first_completion", "second_completion"],
5592 )
5593 .await;
5594 cx.condition(|editor, _| editor.context_menu_visible())
5595 .await;
5596 let apply_additional_edits = cx.update_editor(|editor, cx| {
5597 editor.context_menu_next(&Default::default(), cx);
5598 editor
5599 .confirm_completion(&ConfirmCompletion::default(), cx)
5600 .unwrap()
5601 });
5602 cx.assert_editor_state(indoc! {"
5603 one.second_completionˇ
5604 two
5605 three
5606 "});
5607
5608 handle_resolve_completion_request(
5609 &mut cx,
5610 Some(vec![
5611 (
5612 //This overlaps with the primary completion edit which is
5613 //misbehavior from the LSP spec, test that we filter it out
5614 indoc! {"
5615 one.second_ˇcompletion
5616 two
5617 threeˇ
5618 "},
5619 "overlapping additional edit",
5620 ),
5621 (
5622 indoc! {"
5623 one.second_completion
5624 two
5625 threeˇ
5626 "},
5627 "\nadditional edit",
5628 ),
5629 ]),
5630 )
5631 .await;
5632 apply_additional_edits.await.unwrap();
5633 cx.assert_editor_state(indoc! {"
5634 one.second_completionˇ
5635 two
5636 three
5637 additional edit
5638 "});
5639
5640 cx.set_state(indoc! {"
5641 one.second_completion
5642 twoˇ
5643 threeˇ
5644 additional edit
5645 "});
5646 cx.simulate_keystroke(" ");
5647 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5648 cx.simulate_keystroke("s");
5649 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5650
5651 cx.assert_editor_state(indoc! {"
5652 one.second_completion
5653 two sˇ
5654 three sˇ
5655 additional edit
5656 "});
5657 handle_completion_request(
5658 &mut cx,
5659 indoc! {"
5660 one.second_completion
5661 two s
5662 three <s|>
5663 additional edit
5664 "},
5665 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5666 )
5667 .await;
5668 cx.condition(|editor, _| editor.context_menu_visible())
5669 .await;
5670
5671 cx.simulate_keystroke("i");
5672
5673 handle_completion_request(
5674 &mut cx,
5675 indoc! {"
5676 one.second_completion
5677 two si
5678 three <si|>
5679 additional edit
5680 "},
5681 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5682 )
5683 .await;
5684 cx.condition(|editor, _| editor.context_menu_visible())
5685 .await;
5686
5687 let apply_additional_edits = cx.update_editor(|editor, cx| {
5688 editor
5689 .confirm_completion(&ConfirmCompletion::default(), cx)
5690 .unwrap()
5691 });
5692 cx.assert_editor_state(indoc! {"
5693 one.second_completion
5694 two sixth_completionˇ
5695 three sixth_completionˇ
5696 additional edit
5697 "});
5698
5699 handle_resolve_completion_request(&mut cx, None).await;
5700 apply_additional_edits.await.unwrap();
5701
5702 _ = cx.update(|cx| {
5703 cx.update_global::<SettingsStore, _>(|settings, cx| {
5704 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5705 settings.show_completions_on_input = Some(false);
5706 });
5707 })
5708 });
5709 cx.set_state("editorˇ");
5710 cx.simulate_keystroke(".");
5711 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5712 cx.simulate_keystroke("c");
5713 cx.simulate_keystroke("l");
5714 cx.simulate_keystroke("o");
5715 cx.assert_editor_state("editor.cloˇ");
5716 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5717 cx.update_editor(|editor, cx| {
5718 editor.show_completions(&ShowCompletions, cx);
5719 });
5720 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5721 cx.condition(|editor, _| editor.context_menu_visible())
5722 .await;
5723 let apply_additional_edits = cx.update_editor(|editor, cx| {
5724 editor
5725 .confirm_completion(&ConfirmCompletion::default(), cx)
5726 .unwrap()
5727 });
5728 cx.assert_editor_state("editor.closeˇ");
5729 handle_resolve_completion_request(&mut cx, None).await;
5730 apply_additional_edits.await.unwrap();
5731}
5732
5733#[gpui::test]
5734async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5735 init_test(cx, |_| {});
5736 let mut cx = EditorTestContext::new(cx).await;
5737 let language = Arc::new(Language::new(
5738 LanguageConfig {
5739 line_comment: Some("// ".into()),
5740 ..Default::default()
5741 },
5742 Some(tree_sitter_rust::language()),
5743 ));
5744 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5745
5746 // If multiple selections intersect a line, the line is only toggled once.
5747 cx.set_state(indoc! {"
5748 fn a() {
5749 «//b();
5750 ˇ»// «c();
5751 //ˇ» d();
5752 }
5753 "});
5754
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 // The comment prefix is inserted at the same column for every line in a
5766 // selection.
5767 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5768
5769 cx.assert_editor_state(indoc! {"
5770 fn a() {
5771 // «b();
5772 // c();
5773 ˇ»// d();
5774 }
5775 "});
5776
5777 // If a selection ends at the beginning of a line, that line is not toggled.
5778 cx.set_selections_state(indoc! {"
5779 fn a() {
5780 // b();
5781 «// c();
5782 ˇ» // d();
5783 }
5784 "});
5785
5786 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5787
5788 cx.assert_editor_state(indoc! {"
5789 fn a() {
5790 // b();
5791 «c();
5792 ˇ» // d();
5793 }
5794 "});
5795
5796 // If a selection span a single line and is empty, the line is toggled.
5797 cx.set_state(indoc! {"
5798 fn a() {
5799 a();
5800 b();
5801 ˇ
5802 }
5803 "});
5804
5805 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5806
5807 cx.assert_editor_state(indoc! {"
5808 fn a() {
5809 a();
5810 b();
5811 //•ˇ
5812 }
5813 "});
5814
5815 // If a selection span multiple lines, empty lines are not toggled.
5816 cx.set_state(indoc! {"
5817 fn a() {
5818 «a();
5819
5820 c();ˇ»
5821 }
5822 "});
5823
5824 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5825
5826 cx.assert_editor_state(indoc! {"
5827 fn a() {
5828 // «a();
5829
5830 // c();ˇ»
5831 }
5832 "});
5833}
5834
5835#[gpui::test]
5836async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5837 init_test(cx, |_| {});
5838
5839 let language = Arc::new(Language::new(
5840 LanguageConfig {
5841 line_comment: Some("// ".into()),
5842 ..Default::default()
5843 },
5844 Some(tree_sitter_rust::language()),
5845 ));
5846
5847 let registry = Arc::new(LanguageRegistry::test());
5848 registry.add(language.clone());
5849
5850 let mut cx = EditorTestContext::new(cx).await;
5851 cx.update_buffer(|buffer, cx| {
5852 buffer.set_language_registry(registry);
5853 buffer.set_language(Some(language), cx);
5854 });
5855
5856 let toggle_comments = &ToggleComments {
5857 advance_downwards: true,
5858 };
5859
5860 // Single cursor on one line -> advance
5861 // Cursor moves horizontally 3 characters as well on non-blank line
5862 cx.set_state(indoc!(
5863 "fn a() {
5864 ˇdog();
5865 cat();
5866 }"
5867 ));
5868 cx.update_editor(|editor, cx| {
5869 editor.toggle_comments(toggle_comments, cx);
5870 });
5871 cx.assert_editor_state(indoc!(
5872 "fn a() {
5873 // dog();
5874 catˇ();
5875 }"
5876 ));
5877
5878 // Single selection on one line -> don't advance
5879 cx.set_state(indoc!(
5880 "fn a() {
5881 «dog()ˇ»;
5882 cat();
5883 }"
5884 ));
5885 cx.update_editor(|editor, cx| {
5886 editor.toggle_comments(toggle_comments, cx);
5887 });
5888 cx.assert_editor_state(indoc!(
5889 "fn a() {
5890 // «dog()ˇ»;
5891 cat();
5892 }"
5893 ));
5894
5895 // Multiple cursors on one line -> advance
5896 cx.set_state(indoc!(
5897 "fn a() {
5898 ˇdˇog();
5899 cat();
5900 }"
5901 ));
5902 cx.update_editor(|editor, cx| {
5903 editor.toggle_comments(toggle_comments, cx);
5904 });
5905 cx.assert_editor_state(indoc!(
5906 "fn a() {
5907 // dog();
5908 catˇ(ˇ);
5909 }"
5910 ));
5911
5912 // Multiple cursors on one line, with selection -> don't advance
5913 cx.set_state(indoc!(
5914 "fn a() {
5915 ˇdˇog«()ˇ»;
5916 cat();
5917 }"
5918 ));
5919 cx.update_editor(|editor, cx| {
5920 editor.toggle_comments(toggle_comments, cx);
5921 });
5922 cx.assert_editor_state(indoc!(
5923 "fn a() {
5924 // ˇdˇog«()ˇ»;
5925 cat();
5926 }"
5927 ));
5928
5929 // Single cursor on one line -> advance
5930 // Cursor moves to column 0 on blank line
5931 cx.set_state(indoc!(
5932 "fn a() {
5933 ˇdog();
5934
5935 cat();
5936 }"
5937 ));
5938 cx.update_editor(|editor, cx| {
5939 editor.toggle_comments(toggle_comments, cx);
5940 });
5941 cx.assert_editor_state(indoc!(
5942 "fn a() {
5943 // dog();
5944 ˇ
5945 cat();
5946 }"
5947 ));
5948
5949 // Single cursor on one line -> advance
5950 // Cursor starts and ends at column 0
5951 cx.set_state(indoc!(
5952 "fn a() {
5953 ˇ dog();
5954 cat();
5955 }"
5956 ));
5957 cx.update_editor(|editor, cx| {
5958 editor.toggle_comments(toggle_comments, cx);
5959 });
5960 cx.assert_editor_state(indoc!(
5961 "fn a() {
5962 // dog();
5963 ˇ cat();
5964 }"
5965 ));
5966}
5967
5968#[gpui::test]
5969async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5970 init_test(cx, |_| {});
5971
5972 let mut cx = EditorTestContext::new(cx).await;
5973
5974 let html_language = Arc::new(
5975 Language::new(
5976 LanguageConfig {
5977 name: "HTML".into(),
5978 block_comment: Some(("<!-- ".into(), " -->".into())),
5979 ..Default::default()
5980 },
5981 Some(tree_sitter_html::language()),
5982 )
5983 .with_injection_query(
5984 r#"
5985 (script_element
5986 (raw_text) @content
5987 (#set! "language" "javascript"))
5988 "#,
5989 )
5990 .unwrap(),
5991 );
5992
5993 let javascript_language = Arc::new(Language::new(
5994 LanguageConfig {
5995 name: "JavaScript".into(),
5996 line_comment: Some("// ".into()),
5997 ..Default::default()
5998 },
5999 Some(tree_sitter_typescript::language_tsx()),
6000 ));
6001
6002 let registry = Arc::new(LanguageRegistry::test());
6003 registry.add(html_language.clone());
6004 registry.add(javascript_language.clone());
6005
6006 cx.update_buffer(|buffer, cx| {
6007 buffer.set_language_registry(registry);
6008 buffer.set_language(Some(html_language), cx);
6009 });
6010
6011 // Toggle comments for empty selections
6012 cx.set_state(
6013 &r#"
6014 <p>A</p>ˇ
6015 <p>B</p>ˇ
6016 <p>C</p>ˇ
6017 "#
6018 .unindent(),
6019 );
6020 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6021 cx.assert_editor_state(
6022 &r#"
6023 <!-- <p>A</p>ˇ -->
6024 <!-- <p>B</p>ˇ -->
6025 <!-- <p>C</p>ˇ -->
6026 "#
6027 .unindent(),
6028 );
6029 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6030 cx.assert_editor_state(
6031 &r#"
6032 <p>A</p>ˇ
6033 <p>B</p>ˇ
6034 <p>C</p>ˇ
6035 "#
6036 .unindent(),
6037 );
6038
6039 // Toggle comments for mixture of empty and non-empty selections, where
6040 // multiple selections occupy a given line.
6041 cx.set_state(
6042 &r#"
6043 <p>A«</p>
6044 <p>ˇ»B</p>ˇ
6045 <p>C«</p>
6046 <p>ˇ»D</p>ˇ
6047 "#
6048 .unindent(),
6049 );
6050
6051 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6052 cx.assert_editor_state(
6053 &r#"
6054 <!-- <p>A«</p>
6055 <p>ˇ»B</p>ˇ -->
6056 <!-- <p>C«</p>
6057 <p>ˇ»D</p>ˇ -->
6058 "#
6059 .unindent(),
6060 );
6061 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6062 cx.assert_editor_state(
6063 &r#"
6064 <p>A«</p>
6065 <p>ˇ»B</p>ˇ
6066 <p>C«</p>
6067 <p>ˇ»D</p>ˇ
6068 "#
6069 .unindent(),
6070 );
6071
6072 // Toggle comments when different languages are active for different
6073 // selections.
6074 cx.set_state(
6075 &r#"
6076 ˇ<script>
6077 ˇvar x = new Y();
6078 ˇ</script>
6079 "#
6080 .unindent(),
6081 );
6082 cx.executor().run_until_parked();
6083 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6084 cx.assert_editor_state(
6085 &r#"
6086 <!-- ˇ<script> -->
6087 // ˇvar x = new Y();
6088 <!-- ˇ</script> -->
6089 "#
6090 .unindent(),
6091 );
6092}
6093
6094#[gpui::test]
6095fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6096 init_test(cx, |_| {});
6097
6098 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6099 let multibuffer = cx.new_model(|cx| {
6100 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6101 multibuffer.push_excerpts(
6102 buffer.clone(),
6103 [
6104 ExcerptRange {
6105 context: Point::new(0, 0)..Point::new(0, 4),
6106 primary: None,
6107 },
6108 ExcerptRange {
6109 context: Point::new(1, 0)..Point::new(1, 4),
6110 primary: None,
6111 },
6112 ],
6113 cx,
6114 );
6115 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6116 multibuffer
6117 });
6118
6119 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6120 _ = view.update(cx, |view, cx| {
6121 assert_eq!(view.text(cx), "aaaa\nbbbb");
6122 view.change_selections(None, cx, |s| {
6123 s.select_ranges([
6124 Point::new(0, 0)..Point::new(0, 0),
6125 Point::new(1, 0)..Point::new(1, 0),
6126 ])
6127 });
6128
6129 view.handle_input("X", cx);
6130 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6131 assert_eq!(
6132 view.selections.ranges(cx),
6133 [
6134 Point::new(0, 1)..Point::new(0, 1),
6135 Point::new(1, 1)..Point::new(1, 1),
6136 ]
6137 );
6138
6139 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6140 view.change_selections(None, cx, |s| {
6141 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6142 });
6143 view.backspace(&Default::default(), cx);
6144 assert_eq!(view.text(cx), "Xa\nbbb");
6145 assert_eq!(
6146 view.selections.ranges(cx),
6147 [Point::new(1, 0)..Point::new(1, 0)]
6148 );
6149
6150 view.change_selections(None, cx, |s| {
6151 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6152 });
6153 view.backspace(&Default::default(), cx);
6154 assert_eq!(view.text(cx), "X\nbb");
6155 assert_eq!(
6156 view.selections.ranges(cx),
6157 [Point::new(0, 1)..Point::new(0, 1)]
6158 );
6159 });
6160}
6161
6162#[gpui::test]
6163fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6164 init_test(cx, |_| {});
6165
6166 let markers = vec![('[', ']').into(), ('(', ')').into()];
6167 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6168 indoc! {"
6169 [aaaa
6170 (bbbb]
6171 cccc)",
6172 },
6173 markers.clone(),
6174 );
6175 let excerpt_ranges = markers.into_iter().map(|marker| {
6176 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6177 ExcerptRange {
6178 context,
6179 primary: None,
6180 }
6181 });
6182 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6183 let multibuffer = cx.new_model(|cx| {
6184 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6185 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6186 multibuffer
6187 });
6188
6189 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6190 _ = view.update(cx, |view, cx| {
6191 let (expected_text, selection_ranges) = marked_text_ranges(
6192 indoc! {"
6193 aaaa
6194 bˇbbb
6195 bˇbbˇb
6196 cccc"
6197 },
6198 true,
6199 );
6200 assert_eq!(view.text(cx), expected_text);
6201 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6202
6203 view.handle_input("X", cx);
6204
6205 let (expected_text, expected_selections) = marked_text_ranges(
6206 indoc! {"
6207 aaaa
6208 bXˇbbXb
6209 bXˇbbXˇb
6210 cccc"
6211 },
6212 false,
6213 );
6214 assert_eq!(view.text(cx), expected_text);
6215 assert_eq!(view.selections.ranges(cx), expected_selections);
6216
6217 view.newline(&Newline, cx);
6218 let (expected_text, expected_selections) = marked_text_ranges(
6219 indoc! {"
6220 aaaa
6221 bX
6222 ˇbbX
6223 b
6224 bX
6225 ˇbbX
6226 ˇb
6227 cccc"
6228 },
6229 false,
6230 );
6231 assert_eq!(view.text(cx), expected_text);
6232 assert_eq!(view.selections.ranges(cx), expected_selections);
6233 });
6234}
6235
6236#[gpui::test]
6237fn test_refresh_selections(cx: &mut TestAppContext) {
6238 init_test(cx, |_| {});
6239
6240 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6241 let mut excerpt1_id = None;
6242 let multibuffer = cx.new_model(|cx| {
6243 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6244 excerpt1_id = multibuffer
6245 .push_excerpts(
6246 buffer.clone(),
6247 [
6248 ExcerptRange {
6249 context: Point::new(0, 0)..Point::new(1, 4),
6250 primary: None,
6251 },
6252 ExcerptRange {
6253 context: Point::new(1, 0)..Point::new(2, 4),
6254 primary: None,
6255 },
6256 ],
6257 cx,
6258 )
6259 .into_iter()
6260 .next();
6261 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6262 multibuffer
6263 });
6264
6265 let editor = cx.add_window(|cx| {
6266 let mut editor = build_editor(multibuffer.clone(), cx);
6267 let snapshot = editor.snapshot(cx);
6268 editor.change_selections(None, cx, |s| {
6269 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6270 });
6271 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6272 assert_eq!(
6273 editor.selections.ranges(cx),
6274 [
6275 Point::new(1, 3)..Point::new(1, 3),
6276 Point::new(2, 1)..Point::new(2, 1),
6277 ]
6278 );
6279 editor
6280 });
6281
6282 // Refreshing selections is a no-op when excerpts haven't changed.
6283 _ = editor.update(cx, |editor, cx| {
6284 editor.change_selections(None, cx, |s| s.refresh());
6285 assert_eq!(
6286 editor.selections.ranges(cx),
6287 [
6288 Point::new(1, 3)..Point::new(1, 3),
6289 Point::new(2, 1)..Point::new(2, 1),
6290 ]
6291 );
6292 });
6293
6294 _ = multibuffer.update(cx, |multibuffer, cx| {
6295 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6296 });
6297 _ = editor.update(cx, |editor, cx| {
6298 // Removing an excerpt causes the first selection to become degenerate.
6299 assert_eq!(
6300 editor.selections.ranges(cx),
6301 [
6302 Point::new(0, 0)..Point::new(0, 0),
6303 Point::new(0, 1)..Point::new(0, 1)
6304 ]
6305 );
6306
6307 // Refreshing selections will relocate the first selection to the original buffer
6308 // location.
6309 editor.change_selections(None, cx, |s| s.refresh());
6310 assert_eq!(
6311 editor.selections.ranges(cx),
6312 [
6313 Point::new(0, 1)..Point::new(0, 1),
6314 Point::new(0, 3)..Point::new(0, 3)
6315 ]
6316 );
6317 assert!(editor.selections.pending_anchor().is_some());
6318 });
6319}
6320
6321#[gpui::test]
6322fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6323 init_test(cx, |_| {});
6324
6325 let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6326 let mut excerpt1_id = None;
6327 let multibuffer = cx.new_model(|cx| {
6328 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6329 excerpt1_id = multibuffer
6330 .push_excerpts(
6331 buffer.clone(),
6332 [
6333 ExcerptRange {
6334 context: Point::new(0, 0)..Point::new(1, 4),
6335 primary: None,
6336 },
6337 ExcerptRange {
6338 context: Point::new(1, 0)..Point::new(2, 4),
6339 primary: None,
6340 },
6341 ],
6342 cx,
6343 )
6344 .into_iter()
6345 .next();
6346 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6347 multibuffer
6348 });
6349
6350 let editor = cx.add_window(|cx| {
6351 let mut editor = build_editor(multibuffer.clone(), cx);
6352 let snapshot = editor.snapshot(cx);
6353 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6354 assert_eq!(
6355 editor.selections.ranges(cx),
6356 [Point::new(1, 3)..Point::new(1, 3)]
6357 );
6358 editor
6359 });
6360
6361 _ = multibuffer.update(cx, |multibuffer, cx| {
6362 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6363 });
6364 _ = editor.update(cx, |editor, cx| {
6365 assert_eq!(
6366 editor.selections.ranges(cx),
6367 [Point::new(0, 0)..Point::new(0, 0)]
6368 );
6369
6370 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6371 editor.change_selections(None, cx, |s| s.refresh());
6372 assert_eq!(
6373 editor.selections.ranges(cx),
6374 [Point::new(0, 3)..Point::new(0, 3)]
6375 );
6376 assert!(editor.selections.pending_anchor().is_some());
6377 });
6378}
6379
6380#[gpui::test]
6381async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6382 init_test(cx, |_| {});
6383
6384 let language = Arc::new(
6385 Language::new(
6386 LanguageConfig {
6387 brackets: BracketPairConfig {
6388 pairs: vec![
6389 BracketPair {
6390 start: "{".to_string(),
6391 end: "}".to_string(),
6392 close: true,
6393 newline: true,
6394 },
6395 BracketPair {
6396 start: "/* ".to_string(),
6397 end: " */".to_string(),
6398 close: true,
6399 newline: true,
6400 },
6401 ],
6402 ..Default::default()
6403 },
6404 ..Default::default()
6405 },
6406 Some(tree_sitter_rust::language()),
6407 )
6408 .with_indents_query("")
6409 .unwrap(),
6410 );
6411
6412 let text = concat!(
6413 "{ }\n", //
6414 " x\n", //
6415 " /* */\n", //
6416 "x\n", //
6417 "{{} }\n", //
6418 );
6419
6420 let buffer = cx
6421 .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
6422 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6423 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6424 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6425 .await;
6426
6427 _ = view.update(cx, |view, cx| {
6428 view.change_selections(None, cx, |s| {
6429 s.select_display_ranges([
6430 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6431 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6432 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6433 ])
6434 });
6435 view.newline(&Newline, cx);
6436
6437 assert_eq!(
6438 view.buffer().read(cx).read(cx).text(),
6439 concat!(
6440 "{ \n", // Suppress rustfmt
6441 "\n", //
6442 "}\n", //
6443 " x\n", //
6444 " /* \n", //
6445 " \n", //
6446 " */\n", //
6447 "x\n", //
6448 "{{} \n", //
6449 "}\n", //
6450 )
6451 );
6452 });
6453}
6454
6455#[gpui::test]
6456fn test_highlighted_ranges(cx: &mut TestAppContext) {
6457 init_test(cx, |_| {});
6458
6459 let editor = cx.add_window(|cx| {
6460 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6461 build_editor(buffer.clone(), cx)
6462 });
6463
6464 _ = editor.update(cx, |editor, cx| {
6465 struct Type1;
6466 struct Type2;
6467
6468 let buffer = editor.buffer.read(cx).snapshot(cx);
6469
6470 let anchor_range =
6471 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6472
6473 editor.highlight_background::<Type1>(
6474 vec![
6475 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6476 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6477 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6478 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6479 ],
6480 |_| Hsla::red(),
6481 cx,
6482 );
6483 editor.highlight_background::<Type2>(
6484 vec![
6485 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6486 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6487 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6488 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6489 ],
6490 |_| Hsla::green(),
6491 cx,
6492 );
6493
6494 let snapshot = editor.snapshot(cx);
6495 let mut highlighted_ranges = editor.background_highlights_in_range(
6496 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6497 &snapshot,
6498 cx.theme().colors(),
6499 );
6500 // Enforce a consistent ordering based on color without relying on the ordering of the
6501 // highlight's `TypeId` which is non-executor.
6502 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6503 assert_eq!(
6504 highlighted_ranges,
6505 &[
6506 (
6507 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6508 Hsla::red(),
6509 ),
6510 (
6511 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6512 Hsla::red(),
6513 ),
6514 (
6515 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6516 Hsla::green(),
6517 ),
6518 (
6519 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6520 Hsla::green(),
6521 ),
6522 ]
6523 );
6524 assert_eq!(
6525 editor.background_highlights_in_range(
6526 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6527 &snapshot,
6528 cx.theme().colors(),
6529 ),
6530 &[(
6531 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6532 Hsla::red(),
6533 )]
6534 );
6535 });
6536}
6537
6538#[gpui::test]
6539async fn test_following(cx: &mut gpui::TestAppContext) {
6540 init_test(cx, |_| {});
6541
6542 let fs = FakeFs::new(cx.executor());
6543 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6544
6545 let buffer = project.update(cx, |project, cx| {
6546 let buffer = project
6547 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6548 .unwrap();
6549 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
6550 });
6551 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6552 let follower = cx.update(|cx| {
6553 cx.open_window(
6554 WindowOptions {
6555 bounds: WindowBounds::Fixed(Bounds::from_corners(
6556 gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6557 gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6558 )),
6559 ..Default::default()
6560 },
6561 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
6562 )
6563 });
6564
6565 let is_still_following = Rc::new(RefCell::new(true));
6566 let follower_edit_event_count = Rc::new(RefCell::new(0));
6567 let pending_update = Rc::new(RefCell::new(None));
6568 _ = follower.update(cx, {
6569 let update = pending_update.clone();
6570 let is_still_following = is_still_following.clone();
6571 let follower_edit_event_count = follower_edit_event_count.clone();
6572 |_, cx| {
6573 cx.subscribe(
6574 &leader.root_view(cx).unwrap(),
6575 move |_, leader, event, cx| {
6576 leader
6577 .read(cx)
6578 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6579 },
6580 )
6581 .detach();
6582
6583 cx.subscribe(
6584 &follower.root_view(cx).unwrap(),
6585 move |_, _, event: &EditorEvent, _cx| {
6586 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
6587 *is_still_following.borrow_mut() = false;
6588 }
6589
6590 if let EditorEvent::BufferEdited = event {
6591 *follower_edit_event_count.borrow_mut() += 1;
6592 }
6593 },
6594 )
6595 .detach();
6596 }
6597 });
6598
6599 // Update the selections only
6600 _ = leader.update(cx, |leader, cx| {
6601 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6602 });
6603 follower
6604 .update(cx, |follower, cx| {
6605 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6606 })
6607 .unwrap()
6608 .await
6609 .unwrap();
6610 _ = follower.update(cx, |follower, cx| {
6611 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6612 });
6613 assert_eq!(*is_still_following.borrow(), true);
6614 assert_eq!(*follower_edit_event_count.borrow(), 0);
6615
6616 // Update the scroll position only
6617 _ = leader.update(cx, |leader, cx| {
6618 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6619 });
6620 follower
6621 .update(cx, |follower, cx| {
6622 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6623 })
6624 .unwrap()
6625 .await
6626 .unwrap();
6627 assert_eq!(
6628 follower
6629 .update(cx, |follower, cx| follower.scroll_position(cx))
6630 .unwrap(),
6631 gpui::Point::new(1.5, 3.5)
6632 );
6633 assert_eq!(*is_still_following.borrow(), true);
6634 assert_eq!(*follower_edit_event_count.borrow(), 0);
6635
6636 // Update the selections and scroll position. The follower's scroll position is updated
6637 // via autoscroll, not via the leader's exact scroll position.
6638 _ = leader.update(cx, |leader, cx| {
6639 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6640 leader.request_autoscroll(Autoscroll::newest(), cx);
6641 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6642 });
6643 follower
6644 .update(cx, |follower, cx| {
6645 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6646 })
6647 .unwrap()
6648 .await
6649 .unwrap();
6650 _ = follower.update(cx, |follower, cx| {
6651 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6652 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6653 });
6654 assert_eq!(*is_still_following.borrow(), true);
6655
6656 // Creating a pending selection that precedes another selection
6657 _ = leader.update(cx, |leader, cx| {
6658 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6659 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6660 });
6661 follower
6662 .update(cx, |follower, cx| {
6663 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6664 })
6665 .unwrap()
6666 .await
6667 .unwrap();
6668 _ = follower.update(cx, |follower, cx| {
6669 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6670 });
6671 assert_eq!(*is_still_following.borrow(), true);
6672
6673 // Extend the pending selection so that it surrounds another selection
6674 _ = leader.update(cx, |leader, cx| {
6675 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6676 });
6677 follower
6678 .update(cx, |follower, cx| {
6679 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6680 })
6681 .unwrap()
6682 .await
6683 .unwrap();
6684 _ = follower.update(cx, |follower, cx| {
6685 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6686 });
6687
6688 // Scrolling locally breaks the follow
6689 _ = follower.update(cx, |follower, cx| {
6690 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6691 follower.set_scroll_anchor(
6692 ScrollAnchor {
6693 anchor: top_anchor,
6694 offset: gpui::Point::new(0.0, 0.5),
6695 },
6696 cx,
6697 );
6698 });
6699 assert_eq!(*is_still_following.borrow(), false);
6700}
6701
6702#[gpui::test]
6703async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6704 init_test(cx, |_| {});
6705
6706 let fs = FakeFs::new(cx.executor());
6707 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6708 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6709 let pane = workspace
6710 .update(cx, |workspace, _| workspace.active_pane().clone())
6711 .unwrap();
6712
6713 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6714
6715 let leader = pane.update(cx, |_, cx| {
6716 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
6717 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
6718 });
6719
6720 // Start following the editor when it has no excerpts.
6721 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6722 let follower_1 = cx
6723 .update_window(*workspace.deref(), |_, cx| {
6724 Editor::from_state_proto(
6725 pane.clone(),
6726 workspace.root_view(cx).unwrap(),
6727 ViewId {
6728 creator: Default::default(),
6729 id: 0,
6730 },
6731 &mut state_message,
6732 cx,
6733 )
6734 })
6735 .unwrap()
6736 .unwrap()
6737 .await
6738 .unwrap();
6739
6740 let update_message = Rc::new(RefCell::new(None));
6741 follower_1.update(cx, {
6742 let update = update_message.clone();
6743 |_, cx| {
6744 cx.subscribe(&leader, move |_, leader, event, cx| {
6745 leader
6746 .read(cx)
6747 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6748 })
6749 .detach();
6750 }
6751 });
6752
6753 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6754 (
6755 project
6756 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6757 .unwrap(),
6758 project
6759 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6760 .unwrap(),
6761 )
6762 });
6763
6764 // Insert some excerpts.
6765 _ = leader.update(cx, |leader, cx| {
6766 leader.buffer.update(cx, |multibuffer, cx| {
6767 let excerpt_ids = multibuffer.push_excerpts(
6768 buffer_1.clone(),
6769 [
6770 ExcerptRange {
6771 context: 1..6,
6772 primary: None,
6773 },
6774 ExcerptRange {
6775 context: 12..15,
6776 primary: None,
6777 },
6778 ExcerptRange {
6779 context: 0..3,
6780 primary: None,
6781 },
6782 ],
6783 cx,
6784 );
6785 multibuffer.insert_excerpts_after(
6786 excerpt_ids[0],
6787 buffer_2.clone(),
6788 [
6789 ExcerptRange {
6790 context: 8..12,
6791 primary: None,
6792 },
6793 ExcerptRange {
6794 context: 0..6,
6795 primary: None,
6796 },
6797 ],
6798 cx,
6799 );
6800 });
6801 });
6802
6803 // Apply the update of adding the excerpts.
6804 follower_1
6805 .update(cx, |follower, cx| {
6806 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6807 })
6808 .await
6809 .unwrap();
6810 assert_eq!(
6811 follower_1.update(cx, |editor, cx| editor.text(cx)),
6812 leader.update(cx, |editor, cx| editor.text(cx))
6813 );
6814 update_message.borrow_mut().take();
6815
6816 // Start following separately after it already has excerpts.
6817 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6818 let follower_2 = cx
6819 .update_window(*workspace.deref(), |_, cx| {
6820 Editor::from_state_proto(
6821 pane.clone(),
6822 workspace.root_view(cx).unwrap().clone(),
6823 ViewId {
6824 creator: Default::default(),
6825 id: 0,
6826 },
6827 &mut state_message,
6828 cx,
6829 )
6830 })
6831 .unwrap()
6832 .unwrap()
6833 .await
6834 .unwrap();
6835 assert_eq!(
6836 follower_2.update(cx, |editor, cx| editor.text(cx)),
6837 leader.update(cx, |editor, cx| editor.text(cx))
6838 );
6839
6840 // Remove some excerpts.
6841 _ = leader.update(cx, |leader, cx| {
6842 leader.buffer.update(cx, |multibuffer, cx| {
6843 let excerpt_ids = multibuffer.excerpt_ids();
6844 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6845 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6846 });
6847 });
6848
6849 // Apply the update of removing the excerpts.
6850 follower_1
6851 .update(cx, |follower, cx| {
6852 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6853 })
6854 .await
6855 .unwrap();
6856 follower_2
6857 .update(cx, |follower, cx| {
6858 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6859 })
6860 .await
6861 .unwrap();
6862 update_message.borrow_mut().take();
6863 assert_eq!(
6864 follower_1.update(cx, |editor, cx| editor.text(cx)),
6865 leader.update(cx, |editor, cx| editor.text(cx))
6866 );
6867}
6868
6869#[gpui::test]
6870async fn go_to_prev_overlapping_diagnostic(
6871 executor: BackgroundExecutor,
6872 cx: &mut gpui::TestAppContext,
6873) {
6874 init_test(cx, |_| {});
6875
6876 let mut cx = EditorTestContext::new(cx).await;
6877 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6878
6879 cx.set_state(indoc! {"
6880 ˇfn func(abc def: i32) -> u32 {
6881 }
6882 "});
6883
6884 _ = cx.update(|cx| {
6885 _ = project.update(cx, |project, cx| {
6886 project
6887 .update_diagnostics(
6888 LanguageServerId(0),
6889 lsp::PublishDiagnosticsParams {
6890 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6891 version: None,
6892 diagnostics: vec![
6893 lsp::Diagnostic {
6894 range: lsp::Range::new(
6895 lsp::Position::new(0, 11),
6896 lsp::Position::new(0, 12),
6897 ),
6898 severity: Some(lsp::DiagnosticSeverity::ERROR),
6899 ..Default::default()
6900 },
6901 lsp::Diagnostic {
6902 range: lsp::Range::new(
6903 lsp::Position::new(0, 12),
6904 lsp::Position::new(0, 15),
6905 ),
6906 severity: Some(lsp::DiagnosticSeverity::ERROR),
6907 ..Default::default()
6908 },
6909 lsp::Diagnostic {
6910 range: lsp::Range::new(
6911 lsp::Position::new(0, 25),
6912 lsp::Position::new(0, 28),
6913 ),
6914 severity: Some(lsp::DiagnosticSeverity::ERROR),
6915 ..Default::default()
6916 },
6917 ],
6918 },
6919 &[],
6920 cx,
6921 )
6922 .unwrap()
6923 });
6924 });
6925
6926 executor.run_until_parked();
6927
6928 cx.update_editor(|editor, cx| {
6929 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6930 });
6931
6932 cx.assert_editor_state(indoc! {"
6933 fn func(abc def: i32) -> ˇu32 {
6934 }
6935 "});
6936
6937 cx.update_editor(|editor, cx| {
6938 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6939 });
6940
6941 cx.assert_editor_state(indoc! {"
6942 fn func(abc ˇdef: i32) -> u32 {
6943 }
6944 "});
6945
6946 cx.update_editor(|editor, cx| {
6947 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6948 });
6949
6950 cx.assert_editor_state(indoc! {"
6951 fn func(abcˇ def: i32) -> u32 {
6952 }
6953 "});
6954
6955 cx.update_editor(|editor, cx| {
6956 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6957 });
6958
6959 cx.assert_editor_state(indoc! {"
6960 fn func(abc def: i32) -> ˇu32 {
6961 }
6962 "});
6963}
6964
6965#[gpui::test]
6966async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6967 init_test(cx, |_| {});
6968
6969 let mut cx = EditorTestContext::new(cx).await;
6970
6971 let diff_base = r#"
6972 use some::mod;
6973
6974 const A: u32 = 42;
6975
6976 fn main() {
6977 println!("hello");
6978
6979 println!("world");
6980 }
6981 "#
6982 .unindent();
6983
6984 // Edits are modified, removed, modified, added
6985 cx.set_state(
6986 &r#"
6987 use some::modified;
6988
6989 ˇ
6990 fn main() {
6991 println!("hello there");
6992
6993 println!("around the");
6994 println!("world");
6995 }
6996 "#
6997 .unindent(),
6998 );
6999
7000 cx.set_diff_base(Some(&diff_base));
7001 executor.run_until_parked();
7002
7003 cx.update_editor(|editor, cx| {
7004 //Wrap around the bottom of the buffer
7005 for _ in 0..3 {
7006 editor.go_to_hunk(&GoToHunk, cx);
7007 }
7008 });
7009
7010 cx.assert_editor_state(
7011 &r#"
7012 ˇuse some::modified;
7013
7014
7015 fn main() {
7016 println!("hello there");
7017
7018 println!("around the");
7019 println!("world");
7020 }
7021 "#
7022 .unindent(),
7023 );
7024
7025 cx.update_editor(|editor, cx| {
7026 //Wrap around the top of the buffer
7027 for _ in 0..2 {
7028 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7029 }
7030 });
7031
7032 cx.assert_editor_state(
7033 &r#"
7034 use some::modified;
7035
7036
7037 fn main() {
7038 ˇ println!("hello there");
7039
7040 println!("around the");
7041 println!("world");
7042 }
7043 "#
7044 .unindent(),
7045 );
7046
7047 cx.update_editor(|editor, cx| {
7048 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7049 });
7050
7051 cx.assert_editor_state(
7052 &r#"
7053 use some::modified;
7054
7055 ˇ
7056 fn main() {
7057 println!("hello there");
7058
7059 println!("around the");
7060 println!("world");
7061 }
7062 "#
7063 .unindent(),
7064 );
7065
7066 cx.update_editor(|editor, cx| {
7067 for _ in 0..3 {
7068 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7069 }
7070 });
7071
7072 cx.assert_editor_state(
7073 &r#"
7074 use some::modified;
7075
7076
7077 fn main() {
7078 ˇ println!("hello there");
7079
7080 println!("around the");
7081 println!("world");
7082 }
7083 "#
7084 .unindent(),
7085 );
7086
7087 cx.update_editor(|editor, cx| {
7088 editor.fold(&Fold, cx);
7089
7090 //Make sure that the fold only gets one hunk
7091 for _ in 0..4 {
7092 editor.go_to_hunk(&GoToHunk, cx);
7093 }
7094 });
7095
7096 cx.assert_editor_state(
7097 &r#"
7098 ˇuse some::modified;
7099
7100
7101 fn main() {
7102 println!("hello there");
7103
7104 println!("around the");
7105 println!("world");
7106 }
7107 "#
7108 .unindent(),
7109 );
7110}
7111
7112#[test]
7113fn test_split_words() {
7114 fn split<'a>(text: &'a str) -> Vec<&'a str> {
7115 split_words(text).collect()
7116 }
7117
7118 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7119 assert_eq!(split("hello_world"), &["hello_", "world"]);
7120 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7121 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7122 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7123 assert_eq!(split("helloworld"), &["helloworld"]);
7124}
7125
7126#[gpui::test]
7127async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7128 init_test(cx, |_| {});
7129
7130 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7131 let mut assert = |before, after| {
7132 let _state_context = cx.set_state(before);
7133 cx.update_editor(|editor, cx| {
7134 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7135 });
7136 cx.assert_editor_state(after);
7137 };
7138
7139 // Outside bracket jumps to outside of matching bracket
7140 assert("console.logˇ(var);", "console.log(var)ˇ;");
7141 assert("console.log(var)ˇ;", "console.logˇ(var);");
7142
7143 // Inside bracket jumps to inside of matching bracket
7144 assert("console.log(ˇvar);", "console.log(varˇ);");
7145 assert("console.log(varˇ);", "console.log(ˇvar);");
7146
7147 // When outside a bracket and inside, favor jumping to the inside bracket
7148 assert(
7149 "console.log('foo', [1, 2, 3]ˇ);",
7150 "console.log(ˇ'foo', [1, 2, 3]);",
7151 );
7152 assert(
7153 "console.log(ˇ'foo', [1, 2, 3]);",
7154 "console.log('foo', [1, 2, 3]ˇ);",
7155 );
7156
7157 // Bias forward if two options are equally likely
7158 assert(
7159 "let result = curried_fun()ˇ();",
7160 "let result = curried_fun()()ˇ;",
7161 );
7162
7163 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7164 assert(
7165 indoc! {"
7166 function test() {
7167 console.log('test')ˇ
7168 }"},
7169 indoc! {"
7170 function test() {
7171 console.logˇ('test')
7172 }"},
7173 );
7174}
7175
7176#[gpui::test(iterations = 10)]
7177async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7178 // flaky
7179 init_test(cx, |_| {});
7180
7181 let (copilot, copilot_lsp) = Copilot::fake(cx);
7182 _ = cx.update(|cx| cx.set_global(copilot));
7183 let mut cx = EditorLspTestContext::new_rust(
7184 lsp::ServerCapabilities {
7185 completion_provider: Some(lsp::CompletionOptions {
7186 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7187 ..Default::default()
7188 }),
7189 ..Default::default()
7190 },
7191 cx,
7192 )
7193 .await;
7194
7195 // When inserting, ensure autocompletion is favored over Copilot suggestions.
7196 cx.set_state(indoc! {"
7197 oneˇ
7198 two
7199 three
7200 "});
7201 cx.simulate_keystroke(".");
7202 let _ = handle_completion_request(
7203 &mut cx,
7204 indoc! {"
7205 one.|<>
7206 two
7207 three
7208 "},
7209 vec!["completion_a", "completion_b"],
7210 );
7211 handle_copilot_completion_request(
7212 &copilot_lsp,
7213 vec![copilot::request::Completion {
7214 text: "one.copilot1".into(),
7215 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7216 ..Default::default()
7217 }],
7218 vec![],
7219 );
7220 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7221 cx.update_editor(|editor, cx| {
7222 assert!(editor.context_menu_visible());
7223 assert!(!editor.has_active_copilot_suggestion(cx));
7224
7225 // Confirming a completion inserts it and hides the context menu, without showing
7226 // the copilot suggestion afterwards.
7227 editor
7228 .confirm_completion(&Default::default(), cx)
7229 .unwrap()
7230 .detach();
7231 assert!(!editor.context_menu_visible());
7232 assert!(!editor.has_active_copilot_suggestion(cx));
7233 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7234 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7235 });
7236
7237 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7238 cx.set_state(indoc! {"
7239 oneˇ
7240 two
7241 three
7242 "});
7243 cx.simulate_keystroke(".");
7244 let _ = handle_completion_request(
7245 &mut cx,
7246 indoc! {"
7247 one.|<>
7248 two
7249 three
7250 "},
7251 vec![],
7252 );
7253 handle_copilot_completion_request(
7254 &copilot_lsp,
7255 vec![copilot::request::Completion {
7256 text: "one.copilot1".into(),
7257 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7258 ..Default::default()
7259 }],
7260 vec![],
7261 );
7262 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7263 cx.update_editor(|editor, cx| {
7264 assert!(!editor.context_menu_visible());
7265 assert!(editor.has_active_copilot_suggestion(cx));
7266 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7267 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7268 });
7269
7270 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7271 cx.set_state(indoc! {"
7272 oneˇ
7273 two
7274 three
7275 "});
7276 cx.simulate_keystroke(".");
7277 let _ = handle_completion_request(
7278 &mut cx,
7279 indoc! {"
7280 one.|<>
7281 two
7282 three
7283 "},
7284 vec!["completion_a", "completion_b"],
7285 );
7286 handle_copilot_completion_request(
7287 &copilot_lsp,
7288 vec![copilot::request::Completion {
7289 text: "one.copilot1".into(),
7290 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7291 ..Default::default()
7292 }],
7293 vec![],
7294 );
7295 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7296 cx.update_editor(|editor, cx| {
7297 assert!(editor.context_menu_visible());
7298 assert!(!editor.has_active_copilot_suggestion(cx));
7299
7300 // When hiding the context menu, the Copilot suggestion becomes visible.
7301 editor.hide_context_menu(cx);
7302 assert!(!editor.context_menu_visible());
7303 assert!(editor.has_active_copilot_suggestion(cx));
7304 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7305 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7306 });
7307
7308 // Ensure existing completion is interpolated when inserting again.
7309 cx.simulate_keystroke("c");
7310 executor.run_until_parked();
7311 cx.update_editor(|editor, cx| {
7312 assert!(!editor.context_menu_visible());
7313 assert!(editor.has_active_copilot_suggestion(cx));
7314 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7315 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7316 });
7317
7318 // After debouncing, new Copilot completions should be requested.
7319 handle_copilot_completion_request(
7320 &copilot_lsp,
7321 vec![copilot::request::Completion {
7322 text: "one.copilot2".into(),
7323 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7324 ..Default::default()
7325 }],
7326 vec![],
7327 );
7328 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7329 cx.update_editor(|editor, cx| {
7330 assert!(!editor.context_menu_visible());
7331 assert!(editor.has_active_copilot_suggestion(cx));
7332 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7333 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7334
7335 // Canceling should remove the active Copilot suggestion.
7336 editor.cancel(&Default::default(), cx);
7337 assert!(!editor.has_active_copilot_suggestion(cx));
7338 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7339 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7340
7341 // After canceling, tabbing shouldn't insert the previously shown suggestion.
7342 editor.tab(&Default::default(), cx);
7343 assert!(!editor.has_active_copilot_suggestion(cx));
7344 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7345 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7346
7347 // When undoing the previously active suggestion is shown again.
7348 editor.undo(&Default::default(), cx);
7349 assert!(editor.has_active_copilot_suggestion(cx));
7350 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7351 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7352 });
7353
7354 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7355 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7356 cx.update_editor(|editor, 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 // Tabbing when there is an active suggestion inserts it.
7362 editor.tab(&Default::default(), cx);
7363 assert!(!editor.has_active_copilot_suggestion(cx));
7364 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7365 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7366
7367 // When undoing the previously active suggestion is shown again.
7368 editor.undo(&Default::default(), cx);
7369 assert!(editor.has_active_copilot_suggestion(cx));
7370 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7371 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7372
7373 // Hide suggestion.
7374 editor.cancel(&Default::default(), cx);
7375 assert!(!editor.has_active_copilot_suggestion(cx));
7376 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7377 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7378 });
7379
7380 // If an edit occurs outside of this editor but no suggestion is being shown,
7381 // we won't make it visible.
7382 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7383 cx.update_editor(|editor, cx| {
7384 assert!(!editor.has_active_copilot_suggestion(cx));
7385 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7386 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7387 });
7388
7389 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7390 cx.update_editor(|editor, cx| {
7391 editor.set_text("fn foo() {\n \n}", cx);
7392 editor.change_selections(None, cx, |s| {
7393 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7394 });
7395 });
7396 handle_copilot_completion_request(
7397 &copilot_lsp,
7398 vec![copilot::request::Completion {
7399 text: " let x = 4;".into(),
7400 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7401 ..Default::default()
7402 }],
7403 vec![],
7404 );
7405
7406 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7407 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7408 cx.update_editor(|editor, cx| {
7409 assert!(editor.has_active_copilot_suggestion(cx));
7410 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7411 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7412
7413 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7414 editor.tab(&Default::default(), cx);
7415 assert!(editor.has_active_copilot_suggestion(cx));
7416 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7417 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7418
7419 // Tabbing again accepts the suggestion.
7420 editor.tab(&Default::default(), cx);
7421 assert!(!editor.has_active_copilot_suggestion(cx));
7422 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7423 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7424 });
7425}
7426
7427#[gpui::test]
7428async fn test_copilot_completion_invalidation(
7429 executor: BackgroundExecutor,
7430 cx: &mut gpui::TestAppContext,
7431) {
7432 init_test(cx, |_| {});
7433
7434 let (copilot, copilot_lsp) = Copilot::fake(cx);
7435 _ = cx.update(|cx| cx.set_global(copilot));
7436 let mut cx = EditorLspTestContext::new_rust(
7437 lsp::ServerCapabilities {
7438 completion_provider: Some(lsp::CompletionOptions {
7439 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7440 ..Default::default()
7441 }),
7442 ..Default::default()
7443 },
7444 cx,
7445 )
7446 .await;
7447
7448 cx.set_state(indoc! {"
7449 one
7450 twˇ
7451 three
7452 "});
7453
7454 handle_copilot_completion_request(
7455 &copilot_lsp,
7456 vec![copilot::request::Completion {
7457 text: "two.foo()".into(),
7458 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7459 ..Default::default()
7460 }],
7461 vec![],
7462 );
7463 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7464 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7465 cx.update_editor(|editor, cx| {
7466 assert!(editor.has_active_copilot_suggestion(cx));
7467 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7468 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7469
7470 editor.backspace(&Default::default(), cx);
7471 assert!(editor.has_active_copilot_suggestion(cx));
7472 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7473 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7474
7475 editor.backspace(&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 // Deleting across the original suggestion range invalidates it.
7481 editor.backspace(&Default::default(), cx);
7482 assert!(!editor.has_active_copilot_suggestion(cx));
7483 assert_eq!(editor.display_text(cx), "one\nthree\n");
7484 assert_eq!(editor.text(cx), "one\nthree\n");
7485
7486 // Undoing the deletion restores the suggestion.
7487 editor.undo(&Default::default(), cx);
7488 assert!(editor.has_active_copilot_suggestion(cx));
7489 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7490 assert_eq!(editor.text(cx), "one\n\nthree\n");
7491 });
7492}
7493
7494#[gpui::test]
7495async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7496 init_test(cx, |_| {});
7497
7498 let (copilot, copilot_lsp) = Copilot::fake(cx);
7499 _ = cx.update(|cx| cx.set_global(copilot));
7500
7501 let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7502 let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7503 let multibuffer = cx.new_model(|cx| {
7504 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7505 multibuffer.push_excerpts(
7506 buffer_1.clone(),
7507 [ExcerptRange {
7508 context: Point::new(0, 0)..Point::new(2, 0),
7509 primary: None,
7510 }],
7511 cx,
7512 );
7513 multibuffer.push_excerpts(
7514 buffer_2.clone(),
7515 [ExcerptRange {
7516 context: Point::new(0, 0)..Point::new(2, 0),
7517 primary: None,
7518 }],
7519 cx,
7520 );
7521 multibuffer
7522 });
7523 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7524
7525 handle_copilot_completion_request(
7526 &copilot_lsp,
7527 vec![copilot::request::Completion {
7528 text: "b = 2 + a".into(),
7529 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7530 ..Default::default()
7531 }],
7532 vec![],
7533 );
7534 _ = editor.update(cx, |editor, cx| {
7535 // Ensure copilot suggestions are shown for the first excerpt.
7536 editor.change_selections(None, cx, |s| {
7537 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7538 });
7539 editor.next_copilot_suggestion(&Default::default(), cx);
7540 });
7541 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7542 _ = editor.update(cx, |editor, cx| {
7543 assert!(editor.has_active_copilot_suggestion(cx));
7544 assert_eq!(
7545 editor.display_text(cx),
7546 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7547 );
7548 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7549 });
7550
7551 handle_copilot_completion_request(
7552 &copilot_lsp,
7553 vec![copilot::request::Completion {
7554 text: "d = 4 + c".into(),
7555 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7556 ..Default::default()
7557 }],
7558 vec![],
7559 );
7560 _ = editor.update(cx, |editor, cx| {
7561 // Move to another excerpt, ensuring the suggestion gets cleared.
7562 editor.change_selections(None, cx, |s| {
7563 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7564 });
7565 assert!(!editor.has_active_copilot_suggestion(cx));
7566 assert_eq!(
7567 editor.display_text(cx),
7568 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7569 );
7570 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7571
7572 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7573 editor.handle_input(" ", cx);
7574 assert!(!editor.has_active_copilot_suggestion(cx));
7575 assert_eq!(
7576 editor.display_text(cx),
7577 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7578 );
7579 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7580 });
7581
7582 // Ensure the new suggestion is displayed when the debounce timeout expires.
7583 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7584 _ = editor.update(cx, |editor, cx| {
7585 assert!(editor.has_active_copilot_suggestion(cx));
7586 assert_eq!(
7587 editor.display_text(cx),
7588 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7589 );
7590 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7591 });
7592}
7593
7594#[gpui::test]
7595async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7596 init_test(cx, |settings| {
7597 settings
7598 .copilot
7599 .get_or_insert(Default::default())
7600 .disabled_globs = Some(vec![".env*".to_string()]);
7601 });
7602
7603 let (copilot, copilot_lsp) = Copilot::fake(cx);
7604 _ = cx.update(|cx| cx.set_global(copilot));
7605
7606 let fs = FakeFs::new(cx.executor());
7607 fs.insert_tree(
7608 "/test",
7609 json!({
7610 ".env": "SECRET=something\n",
7611 "README.md": "hello\n"
7612 }),
7613 )
7614 .await;
7615 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7616
7617 let private_buffer = project
7618 .update(cx, |project, cx| {
7619 project.open_local_buffer("/test/.env", cx)
7620 })
7621 .await
7622 .unwrap();
7623 let public_buffer = project
7624 .update(cx, |project, cx| {
7625 project.open_local_buffer("/test/README.md", cx)
7626 })
7627 .await
7628 .unwrap();
7629
7630 let multibuffer = cx.new_model(|cx| {
7631 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7632 multibuffer.push_excerpts(
7633 private_buffer.clone(),
7634 [ExcerptRange {
7635 context: Point::new(0, 0)..Point::new(1, 0),
7636 primary: None,
7637 }],
7638 cx,
7639 );
7640 multibuffer.push_excerpts(
7641 public_buffer.clone(),
7642 [ExcerptRange {
7643 context: Point::new(0, 0)..Point::new(1, 0),
7644 primary: None,
7645 }],
7646 cx,
7647 );
7648 multibuffer
7649 });
7650 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7651
7652 let mut copilot_requests = copilot_lsp
7653 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7654 Ok(copilot::request::GetCompletionsResult {
7655 completions: vec![copilot::request::Completion {
7656 text: "next line".into(),
7657 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7658 ..Default::default()
7659 }],
7660 })
7661 });
7662
7663 _ = editor.update(cx, |editor, cx| {
7664 editor.change_selections(None, cx, |selections| {
7665 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7666 });
7667 editor.next_copilot_suggestion(&Default::default(), cx);
7668 });
7669
7670 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7671 assert!(copilot_requests.try_next().is_err());
7672
7673 _ = editor.update(cx, |editor, cx| {
7674 editor.change_selections(None, cx, |s| {
7675 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7676 });
7677 editor.next_copilot_suggestion(&Default::default(), cx);
7678 });
7679
7680 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7681 assert!(copilot_requests.try_next().is_ok());
7682}
7683
7684#[gpui::test]
7685async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7686 init_test(cx, |_| {});
7687
7688 let mut language = Language::new(
7689 LanguageConfig {
7690 name: "Rust".into(),
7691 path_suffixes: vec!["rs".to_string()],
7692 brackets: BracketPairConfig {
7693 pairs: vec![BracketPair {
7694 start: "{".to_string(),
7695 end: "}".to_string(),
7696 close: true,
7697 newline: true,
7698 }],
7699 disabled_scopes_by_bracket_ix: Vec::new(),
7700 },
7701 ..Default::default()
7702 },
7703 Some(tree_sitter_rust::language()),
7704 );
7705 let mut fake_servers = language
7706 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7707 capabilities: lsp::ServerCapabilities {
7708 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7709 first_trigger_character: "{".to_string(),
7710 more_trigger_character: None,
7711 }),
7712 ..Default::default()
7713 },
7714 ..Default::default()
7715 }))
7716 .await;
7717
7718 let fs = FakeFs::new(cx.executor());
7719 fs.insert_tree(
7720 "/a",
7721 json!({
7722 "main.rs": "fn main() { let a = 5; }",
7723 "other.rs": "// Test file",
7724 }),
7725 )
7726 .await;
7727 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7728 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7729 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7730
7731 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7732
7733 let worktree_id = workspace
7734 .update(cx, |workspace, cx| {
7735 workspace.project().update(cx, |project, cx| {
7736 project.worktrees().next().unwrap().read(cx).id()
7737 })
7738 })
7739 .unwrap();
7740
7741 let buffer = project
7742 .update(cx, |project, cx| {
7743 project.open_local_buffer("/a/main.rs", cx)
7744 })
7745 .await
7746 .unwrap();
7747 cx.executor().run_until_parked();
7748 cx.executor().start_waiting();
7749 let fake_server = fake_servers.next().await.unwrap();
7750 let editor_handle = workspace
7751 .update(cx, |workspace, cx| {
7752 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7753 })
7754 .unwrap()
7755 .await
7756 .unwrap()
7757 .downcast::<Editor>()
7758 .unwrap();
7759
7760 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7761 assert_eq!(
7762 params.text_document_position.text_document.uri,
7763 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7764 );
7765 assert_eq!(
7766 params.text_document_position.position,
7767 lsp::Position::new(0, 21),
7768 );
7769
7770 Ok(Some(vec![lsp::TextEdit {
7771 new_text: "]".to_string(),
7772 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7773 }]))
7774 });
7775
7776 editor_handle.update(cx, |editor, cx| {
7777 editor.focus(cx);
7778 editor.change_selections(None, cx, |s| {
7779 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7780 });
7781 editor.handle_input("{", cx);
7782 });
7783
7784 cx.executor().run_until_parked();
7785
7786 _ = buffer.update(cx, |buffer, _| {
7787 assert_eq!(
7788 buffer.text(),
7789 "fn main() { let a = {5}; }",
7790 "No extra braces from on type formatting should appear in the buffer"
7791 )
7792 });
7793}
7794
7795#[gpui::test]
7796async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7797 init_test(cx, |_| {});
7798
7799 let language_name: Arc<str> = "Rust".into();
7800 let mut language = Language::new(
7801 LanguageConfig {
7802 name: Arc::clone(&language_name),
7803 path_suffixes: vec!["rs".to_string()],
7804 ..Default::default()
7805 },
7806 Some(tree_sitter_rust::language()),
7807 );
7808
7809 let server_restarts = Arc::new(AtomicUsize::new(0));
7810 let closure_restarts = Arc::clone(&server_restarts);
7811 let language_server_name = "test language server";
7812 let mut fake_servers = language
7813 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7814 name: language_server_name,
7815 initialization_options: Some(json!({
7816 "testOptionValue": true
7817 })),
7818 initializer: Some(Box::new(move |fake_server| {
7819 let task_restarts = Arc::clone(&closure_restarts);
7820 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7821 task_restarts.fetch_add(1, atomic::Ordering::Release);
7822 futures::future::ready(Ok(()))
7823 });
7824 })),
7825 ..Default::default()
7826 }))
7827 .await;
7828
7829 let fs = FakeFs::new(cx.executor());
7830 fs.insert_tree(
7831 "/a",
7832 json!({
7833 "main.rs": "fn main() { let a = 5; }",
7834 "other.rs": "// Test file",
7835 }),
7836 )
7837 .await;
7838 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7839 _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7840 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7841 let _buffer = project
7842 .update(cx, |project, cx| {
7843 project.open_local_buffer("/a/main.rs", cx)
7844 })
7845 .await
7846 .unwrap();
7847 let _fake_server = fake_servers.next().await.unwrap();
7848 update_test_language_settings(cx, |language_settings| {
7849 language_settings.languages.insert(
7850 Arc::clone(&language_name),
7851 LanguageSettingsContent {
7852 tab_size: NonZeroU32::new(8),
7853 ..Default::default()
7854 },
7855 );
7856 });
7857 cx.executor().run_until_parked();
7858 assert_eq!(
7859 server_restarts.load(atomic::Ordering::Acquire),
7860 0,
7861 "Should not restart LSP server on an unrelated change"
7862 );
7863
7864 update_test_project_settings(cx, |project_settings| {
7865 project_settings.lsp.insert(
7866 "Some other server name".into(),
7867 LspSettings {
7868 initialization_options: Some(json!({
7869 "some other init value": false
7870 })),
7871 },
7872 );
7873 });
7874 cx.executor().run_until_parked();
7875 assert_eq!(
7876 server_restarts.load(atomic::Ordering::Acquire),
7877 0,
7878 "Should not restart LSP server on an unrelated LSP settings change"
7879 );
7880
7881 update_test_project_settings(cx, |project_settings| {
7882 project_settings.lsp.insert(
7883 language_server_name.into(),
7884 LspSettings {
7885 initialization_options: Some(json!({
7886 "anotherInitValue": false
7887 })),
7888 },
7889 );
7890 });
7891 cx.executor().run_until_parked();
7892 assert_eq!(
7893 server_restarts.load(atomic::Ordering::Acquire),
7894 1,
7895 "Should restart LSP server on a related LSP settings change"
7896 );
7897
7898 update_test_project_settings(cx, |project_settings| {
7899 project_settings.lsp.insert(
7900 language_server_name.into(),
7901 LspSettings {
7902 initialization_options: Some(json!({
7903 "anotherInitValue": false
7904 })),
7905 },
7906 );
7907 });
7908 cx.executor().run_until_parked();
7909 assert_eq!(
7910 server_restarts.load(atomic::Ordering::Acquire),
7911 1,
7912 "Should not restart LSP server on a related LSP settings change that is the same"
7913 );
7914
7915 update_test_project_settings(cx, |project_settings| {
7916 project_settings.lsp.insert(
7917 language_server_name.into(),
7918 LspSettings {
7919 initialization_options: None,
7920 },
7921 );
7922 });
7923 cx.executor().run_until_parked();
7924 assert_eq!(
7925 server_restarts.load(atomic::Ordering::Acquire),
7926 2,
7927 "Should restart LSP server on another related LSP settings change"
7928 );
7929}
7930
7931#[gpui::test]
7932async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7933 init_test(cx, |_| {});
7934
7935 let mut cx = EditorLspTestContext::new_rust(
7936 lsp::ServerCapabilities {
7937 completion_provider: Some(lsp::CompletionOptions {
7938 trigger_characters: Some(vec![".".to_string()]),
7939 resolve_provider: Some(true),
7940 ..Default::default()
7941 }),
7942 ..Default::default()
7943 },
7944 cx,
7945 )
7946 .await;
7947
7948 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7949 cx.simulate_keystroke(".");
7950 let completion_item = lsp::CompletionItem {
7951 label: "some".into(),
7952 kind: Some(lsp::CompletionItemKind::SNIPPET),
7953 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7954 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7955 kind: lsp::MarkupKind::Markdown,
7956 value: "```rust\nSome(2)\n```".to_string(),
7957 })),
7958 deprecated: Some(false),
7959 sort_text: Some("fffffff2".to_string()),
7960 filter_text: Some("some".to_string()),
7961 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7962 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7963 range: lsp::Range {
7964 start: lsp::Position {
7965 line: 0,
7966 character: 22,
7967 },
7968 end: lsp::Position {
7969 line: 0,
7970 character: 22,
7971 },
7972 },
7973 new_text: "Some(2)".to_string(),
7974 })),
7975 additional_text_edits: Some(vec![lsp::TextEdit {
7976 range: lsp::Range {
7977 start: lsp::Position {
7978 line: 0,
7979 character: 20,
7980 },
7981 end: lsp::Position {
7982 line: 0,
7983 character: 22,
7984 },
7985 },
7986 new_text: "".to_string(),
7987 }]),
7988 ..Default::default()
7989 };
7990
7991 let closure_completion_item = completion_item.clone();
7992 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7993 let task_completion_item = closure_completion_item.clone();
7994 async move {
7995 Ok(Some(lsp::CompletionResponse::Array(vec![
7996 task_completion_item,
7997 ])))
7998 }
7999 });
8000
8001 request.next().await;
8002
8003 cx.condition(|editor, _| editor.context_menu_visible())
8004 .await;
8005 let apply_additional_edits = cx.update_editor(|editor, cx| {
8006 editor
8007 .confirm_completion(&ConfirmCompletion::default(), cx)
8008 .unwrap()
8009 });
8010 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8011
8012 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8013 let task_completion_item = completion_item.clone();
8014 async move { Ok(task_completion_item) }
8015 })
8016 .next()
8017 .await
8018 .unwrap();
8019 apply_additional_edits.await.unwrap();
8020 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8021}
8022
8023#[gpui::test]
8024async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8025 init_test(cx, |_| {});
8026
8027 let mut cx = EditorLspTestContext::new(
8028 Language::new(
8029 LanguageConfig {
8030 path_suffixes: vec!["jsx".into()],
8031 overrides: [(
8032 "element".into(),
8033 LanguageConfigOverride {
8034 word_characters: Override::Set(['-'].into_iter().collect()),
8035 ..Default::default()
8036 },
8037 )]
8038 .into_iter()
8039 .collect(),
8040 ..Default::default()
8041 },
8042 Some(tree_sitter_typescript::language_tsx()),
8043 )
8044 .with_override_query("(jsx_self_closing_element) @element")
8045 .unwrap(),
8046 lsp::ServerCapabilities {
8047 completion_provider: Some(lsp::CompletionOptions {
8048 trigger_characters: Some(vec![":".to_string()]),
8049 ..Default::default()
8050 }),
8051 ..Default::default()
8052 },
8053 cx,
8054 )
8055 .await;
8056
8057 cx.lsp
8058 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8059 Ok(Some(lsp::CompletionResponse::Array(vec![
8060 lsp::CompletionItem {
8061 label: "bg-blue".into(),
8062 ..Default::default()
8063 },
8064 lsp::CompletionItem {
8065 label: "bg-red".into(),
8066 ..Default::default()
8067 },
8068 lsp::CompletionItem {
8069 label: "bg-yellow".into(),
8070 ..Default::default()
8071 },
8072 ])))
8073 });
8074
8075 cx.set_state(r#"<p class="bgˇ" />"#);
8076
8077 // Trigger completion when typing a dash, because the dash is an extra
8078 // word character in the 'element' scope, which contains the cursor.
8079 cx.simulate_keystroke("-");
8080 cx.executor().run_until_parked();
8081 cx.update_editor(|editor, _| {
8082 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8083 assert_eq!(
8084 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8085 &["bg-red", "bg-blue", "bg-yellow"]
8086 );
8087 } else {
8088 panic!("expected completion menu to be open");
8089 }
8090 });
8091
8092 cx.simulate_keystroke("l");
8093 cx.executor().run_until_parked();
8094 cx.update_editor(|editor, _| {
8095 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8096 assert_eq!(
8097 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8098 &["bg-blue", "bg-yellow"]
8099 );
8100 } else {
8101 panic!("expected completion menu to be open");
8102 }
8103 });
8104
8105 // When filtering completions, consider the character after the '-' to
8106 // be the start of a subword.
8107 cx.set_state(r#"<p class="yelˇ" />"#);
8108 cx.simulate_keystroke("l");
8109 cx.executor().run_until_parked();
8110 cx.update_editor(|editor, _| {
8111 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8112 assert_eq!(
8113 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8114 &["bg-yellow"]
8115 );
8116 } else {
8117 panic!("expected completion menu to be open");
8118 }
8119 });
8120}
8121
8122#[gpui::test]
8123async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8124 init_test(cx, |settings| {
8125 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8126 });
8127
8128 let mut language = Language::new(
8129 LanguageConfig {
8130 name: "Rust".into(),
8131 path_suffixes: vec!["rs".to_string()],
8132 prettier_parser_name: Some("test_parser".to_string()),
8133 ..Default::default()
8134 },
8135 Some(tree_sitter_rust::language()),
8136 );
8137
8138 let test_plugin = "test_plugin";
8139 let _ = language
8140 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8141 prettier_plugins: vec![test_plugin],
8142 ..Default::default()
8143 }))
8144 .await;
8145
8146 let fs = FakeFs::new(cx.executor());
8147 fs.insert_file("/file.rs", Default::default()).await;
8148
8149 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8150 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8151 _ = project.update(cx, |project, _| {
8152 project.languages().add(Arc::new(language));
8153 });
8154 let buffer = project
8155 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8156 .await
8157 .unwrap();
8158
8159 let buffer_text = "one\ntwo\nthree\n";
8160 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8161 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8162 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8163
8164 editor
8165 .update(cx, |editor, cx| {
8166 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8167 })
8168 .unwrap()
8169 .await;
8170 assert_eq!(
8171 editor.update(cx, |editor, cx| editor.text(cx)),
8172 buffer_text.to_string() + prettier_format_suffix,
8173 "Test prettier formatting was not applied to the original buffer text",
8174 );
8175
8176 update_test_language_settings(cx, |settings| {
8177 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8178 });
8179 let format = editor.update(cx, |editor, cx| {
8180 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8181 });
8182 format.await.unwrap();
8183 assert_eq!(
8184 editor.update(cx, |editor, cx| editor.text(cx)),
8185 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8186 "Autoformatting (via test prettier) was not applied to the original buffer text",
8187 );
8188}
8189
8190fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8191 let point = DisplayPoint::new(row as u32, column as u32);
8192 point..point
8193}
8194
8195fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8196 let (text, ranges) = marked_text_ranges(marked_text, true);
8197 assert_eq!(view.text(cx), text);
8198 assert_eq!(
8199 view.selections.ranges(cx),
8200 ranges,
8201 "Assert selections are {}",
8202 marked_text
8203 );
8204}
8205
8206/// Handle completion request passing a marked string specifying where the completion
8207/// should be triggered from using '|' character, what range should be replaced, and what completions
8208/// should be returned using '<' and '>' to delimit the range
8209pub fn handle_completion_request(
8210 cx: &mut EditorLspTestContext,
8211 marked_string: &str,
8212 completions: Vec<&'static str>,
8213) -> impl Future<Output = ()> {
8214 let complete_from_marker: TextRangeMarker = '|'.into();
8215 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8216 let (_, mut marked_ranges) = marked_text_ranges_by(
8217 marked_string,
8218 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8219 );
8220
8221 let complete_from_position =
8222 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8223 let replace_range =
8224 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8225
8226 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8227 let completions = completions.clone();
8228 async move {
8229 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8230 assert_eq!(
8231 params.text_document_position.position,
8232 complete_from_position
8233 );
8234 Ok(Some(lsp::CompletionResponse::Array(
8235 completions
8236 .iter()
8237 .map(|completion_text| lsp::CompletionItem {
8238 label: completion_text.to_string(),
8239 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8240 range: replace_range,
8241 new_text: completion_text.to_string(),
8242 })),
8243 ..Default::default()
8244 })
8245 .collect(),
8246 )))
8247 }
8248 });
8249
8250 async move {
8251 request.next().await;
8252 }
8253}
8254
8255fn handle_resolve_completion_request(
8256 cx: &mut EditorLspTestContext,
8257 edits: Option<Vec<(&'static str, &'static str)>>,
8258) -> impl Future<Output = ()> {
8259 let edits = edits.map(|edits| {
8260 edits
8261 .iter()
8262 .map(|(marked_string, new_text)| {
8263 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8264 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8265 lsp::TextEdit::new(replace_range, new_text.to_string())
8266 })
8267 .collect::<Vec<_>>()
8268 });
8269
8270 let mut request =
8271 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8272 let edits = edits.clone();
8273 async move {
8274 Ok(lsp::CompletionItem {
8275 additional_text_edits: edits,
8276 ..Default::default()
8277 })
8278 }
8279 });
8280
8281 async move {
8282 request.next().await;
8283 }
8284}
8285
8286fn handle_copilot_completion_request(
8287 lsp: &lsp::FakeLanguageServer,
8288 completions: Vec<copilot::request::Completion>,
8289 completions_cycling: Vec<copilot::request::Completion>,
8290) {
8291 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8292 let completions = completions.clone();
8293 async move {
8294 Ok(copilot::request::GetCompletionsResult {
8295 completions: completions.clone(),
8296 })
8297 }
8298 });
8299 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8300 let completions_cycling = completions_cycling.clone();
8301 async move {
8302 Ok(copilot::request::GetCompletionsResult {
8303 completions: completions_cycling.clone(),
8304 })
8305 }
8306 });
8307}
8308
8309pub(crate) fn update_test_language_settings(
8310 cx: &mut TestAppContext,
8311 f: impl Fn(&mut AllLanguageSettingsContent),
8312) {
8313 _ = cx.update(|cx| {
8314 cx.update_global(|store: &mut SettingsStore, cx| {
8315 store.update_user_settings::<AllLanguageSettings>(cx, f);
8316 });
8317 });
8318}
8319
8320pub(crate) fn update_test_project_settings(
8321 cx: &mut TestAppContext,
8322 f: impl Fn(&mut ProjectSettings),
8323) {
8324 _ = cx.update(|cx| {
8325 cx.update_global(|store: &mut SettingsStore, cx| {
8326 store.update_user_settings::<ProjectSettings>(cx, f);
8327 });
8328 });
8329}
8330
8331pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8332 _ = cx.update(|cx| {
8333 let store = SettingsStore::test(cx);
8334 cx.set_global(store);
8335 theme::init(theme::LoadThemes::JustBase, cx);
8336 client::init_settings(cx);
8337 language::init(cx);
8338 Project::init_settings(cx);
8339 workspace::init_settings(cx);
8340 crate::init(cx);
8341 });
8342
8343 update_test_language_settings(cx, f);
8344}