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