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