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