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 ..Default::default()
5241 }),
5242 ..Default::default()
5243 },
5244 cx,
5245 )
5246 .await;
5247
5248 cx.set_state(indoc! {"
5249 oneˇ
5250 two
5251 three
5252 "});
5253 cx.simulate_keystroke(".");
5254 handle_completion_request(
5255 &mut cx,
5256 indoc! {"
5257 one.|<>
5258 two
5259 three
5260 "},
5261 vec!["first_completion", "second_completion"],
5262 )
5263 .await;
5264 cx.condition(|editor, _| editor.context_menu_visible())
5265 .await;
5266 let apply_additional_edits = cx.update_editor(|editor, cx| {
5267 editor.move_down(&MoveDown, cx);
5268 editor
5269 .confirm_completion(&ConfirmCompletion::default(), cx)
5270 .unwrap()
5271 });
5272 cx.assert_editor_state(indoc! {"
5273 one.second_completionˇ
5274 two
5275 three
5276 "});
5277
5278 handle_resolve_completion_request(
5279 &mut cx,
5280 Some(vec![
5281 (
5282 //This overlaps with the primary completion edit which is
5283 //misbehavior from the LSP spec, test that we filter it out
5284 indoc! {"
5285 one.second_ˇcompletion
5286 two
5287 threeˇ
5288 "},
5289 "overlapping additional edit",
5290 ),
5291 (
5292 indoc! {"
5293 one.second_completion
5294 two
5295 threeˇ
5296 "},
5297 "\nadditional edit",
5298 ),
5299 ]),
5300 )
5301 .await;
5302 apply_additional_edits.await.unwrap();
5303 cx.assert_editor_state(indoc! {"
5304 one.second_completionˇ
5305 two
5306 three
5307 additional edit
5308 "});
5309
5310 cx.set_state(indoc! {"
5311 one.second_completion
5312 twoˇ
5313 threeˇ
5314 additional edit
5315 "});
5316 cx.simulate_keystroke(" ");
5317 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5318 cx.simulate_keystroke("s");
5319 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5320
5321 cx.assert_editor_state(indoc! {"
5322 one.second_completion
5323 two sˇ
5324 three sˇ
5325 additional edit
5326 "});
5327 handle_completion_request(
5328 &mut cx,
5329 indoc! {"
5330 one.second_completion
5331 two s
5332 three <s|>
5333 additional edit
5334 "},
5335 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5336 )
5337 .await;
5338 cx.condition(|editor, _| editor.context_menu_visible())
5339 .await;
5340
5341 cx.simulate_keystroke("i");
5342
5343 handle_completion_request(
5344 &mut cx,
5345 indoc! {"
5346 one.second_completion
5347 two si
5348 three <si|>
5349 additional edit
5350 "},
5351 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5352 )
5353 .await;
5354 cx.condition(|editor, _| editor.context_menu_visible())
5355 .await;
5356
5357 let apply_additional_edits = cx.update_editor(|editor, cx| {
5358 editor
5359 .confirm_completion(&ConfirmCompletion::default(), cx)
5360 .unwrap()
5361 });
5362 cx.assert_editor_state(indoc! {"
5363 one.second_completion
5364 two sixth_completionˇ
5365 three sixth_completionˇ
5366 additional edit
5367 "});
5368
5369 handle_resolve_completion_request(&mut cx, None).await;
5370 apply_additional_edits.await.unwrap();
5371
5372 cx.update(|cx| {
5373 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5374 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5375 settings.show_completions_on_input = Some(false);
5376 });
5377 })
5378 });
5379 cx.set_state("editorˇ");
5380 cx.simulate_keystroke(".");
5381 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5382 cx.simulate_keystroke("c");
5383 cx.simulate_keystroke("l");
5384 cx.simulate_keystroke("o");
5385 cx.assert_editor_state("editor.cloˇ");
5386 assert!(cx.editor(|e, _| e.context_menu.is_none()));
5387 cx.update_editor(|editor, cx| {
5388 editor.show_completions(&ShowCompletions, cx);
5389 });
5390 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5391 cx.condition(|editor, _| editor.context_menu_visible())
5392 .await;
5393 let apply_additional_edits = cx.update_editor(|editor, cx| {
5394 editor
5395 .confirm_completion(&ConfirmCompletion::default(), cx)
5396 .unwrap()
5397 });
5398 cx.assert_editor_state("editor.closeˇ");
5399 handle_resolve_completion_request(&mut cx, None).await;
5400 apply_additional_edits.await.unwrap();
5401}
5402
5403#[gpui::test]
5404async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5405 init_test(cx, |_| {});
5406 let mut cx = EditorTestContext::new(cx).await;
5407 let language = Arc::new(Language::new(
5408 LanguageConfig {
5409 line_comment: Some("// ".into()),
5410 ..Default::default()
5411 },
5412 Some(tree_sitter_rust::language()),
5413 ));
5414 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5415
5416 // If multiple selections intersect a line, the line is only toggled once.
5417 cx.set_state(indoc! {"
5418 fn a() {
5419 «//b();
5420 ˇ»// «c();
5421 //ˇ» d();
5422 }
5423 "});
5424
5425 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5426
5427 cx.assert_editor_state(indoc! {"
5428 fn a() {
5429 «b();
5430 c();
5431 ˇ» d();
5432 }
5433 "});
5434
5435 // The comment prefix is inserted at the same column for every line in a
5436 // selection.
5437 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5438
5439 cx.assert_editor_state(indoc! {"
5440 fn a() {
5441 // «b();
5442 // c();
5443 ˇ»// d();
5444 }
5445 "});
5446
5447 // If a selection ends at the beginning of a line, that line is not toggled.
5448 cx.set_selections_state(indoc! {"
5449 fn a() {
5450 // b();
5451 «// c();
5452 ˇ» // d();
5453 }
5454 "});
5455
5456 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5457
5458 cx.assert_editor_state(indoc! {"
5459 fn a() {
5460 // b();
5461 «c();
5462 ˇ» // d();
5463 }
5464 "});
5465
5466 // If a selection span a single line and is empty, the line is toggled.
5467 cx.set_state(indoc! {"
5468 fn a() {
5469 a();
5470 b();
5471 ˇ
5472 }
5473 "});
5474
5475 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5476
5477 cx.assert_editor_state(indoc! {"
5478 fn a() {
5479 a();
5480 b();
5481 //•ˇ
5482 }
5483 "});
5484
5485 // If a selection span multiple lines, empty lines are not toggled.
5486 cx.set_state(indoc! {"
5487 fn a() {
5488 «a();
5489
5490 c();ˇ»
5491 }
5492 "});
5493
5494 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5495
5496 cx.assert_editor_state(indoc! {"
5497 fn a() {
5498 // «a();
5499
5500 // c();ˇ»
5501 }
5502 "});
5503}
5504
5505#[gpui::test]
5506async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5507 init_test(cx, |_| {});
5508
5509 let language = Arc::new(Language::new(
5510 LanguageConfig {
5511 line_comment: Some("// ".into()),
5512 ..Default::default()
5513 },
5514 Some(tree_sitter_rust::language()),
5515 ));
5516
5517 let registry = Arc::new(LanguageRegistry::test());
5518 registry.add(language.clone());
5519
5520 let mut cx = EditorTestContext::new(cx).await;
5521 cx.update_buffer(|buffer, cx| {
5522 buffer.set_language_registry(registry);
5523 buffer.set_language(Some(language), cx);
5524 });
5525
5526 let toggle_comments = &ToggleComments {
5527 advance_downwards: true,
5528 };
5529
5530 // Single cursor on one line -> advance
5531 // Cursor moves horizontally 3 characters as well on non-blank line
5532 cx.set_state(indoc!(
5533 "fn a() {
5534 ˇdog();
5535 cat();
5536 }"
5537 ));
5538 cx.update_editor(|editor, cx| {
5539 editor.toggle_comments(toggle_comments, cx);
5540 });
5541 cx.assert_editor_state(indoc!(
5542 "fn a() {
5543 // dog();
5544 catˇ();
5545 }"
5546 ));
5547
5548 // Single selection on one line -> don't advance
5549 cx.set_state(indoc!(
5550 "fn a() {
5551 «dog()ˇ»;
5552 cat();
5553 }"
5554 ));
5555 cx.update_editor(|editor, cx| {
5556 editor.toggle_comments(toggle_comments, cx);
5557 });
5558 cx.assert_editor_state(indoc!(
5559 "fn a() {
5560 // «dog()ˇ»;
5561 cat();
5562 }"
5563 ));
5564
5565 // Multiple cursors on one line -> advance
5566 cx.set_state(indoc!(
5567 "fn a() {
5568 ˇdˇog();
5569 cat();
5570 }"
5571 ));
5572 cx.update_editor(|editor, cx| {
5573 editor.toggle_comments(toggle_comments, cx);
5574 });
5575 cx.assert_editor_state(indoc!(
5576 "fn a() {
5577 // dog();
5578 catˇ(ˇ);
5579 }"
5580 ));
5581
5582 // Multiple cursors on one line, with selection -> don't advance
5583 cx.set_state(indoc!(
5584 "fn a() {
5585 ˇdˇog«()ˇ»;
5586 cat();
5587 }"
5588 ));
5589 cx.update_editor(|editor, cx| {
5590 editor.toggle_comments(toggle_comments, cx);
5591 });
5592 cx.assert_editor_state(indoc!(
5593 "fn a() {
5594 // ˇdˇog«()ˇ»;
5595 cat();
5596 }"
5597 ));
5598
5599 // Single cursor on one line -> advance
5600 // Cursor moves to column 0 on blank line
5601 cx.set_state(indoc!(
5602 "fn a() {
5603 ˇdog();
5604
5605 cat();
5606 }"
5607 ));
5608 cx.update_editor(|editor, cx| {
5609 editor.toggle_comments(toggle_comments, cx);
5610 });
5611 cx.assert_editor_state(indoc!(
5612 "fn a() {
5613 // dog();
5614 ˇ
5615 cat();
5616 }"
5617 ));
5618
5619 // Single cursor on one line -> advance
5620 // Cursor starts and ends at column 0
5621 cx.set_state(indoc!(
5622 "fn a() {
5623 ˇ dog();
5624 cat();
5625 }"
5626 ));
5627 cx.update_editor(|editor, cx| {
5628 editor.toggle_comments(toggle_comments, cx);
5629 });
5630 cx.assert_editor_state(indoc!(
5631 "fn a() {
5632 // dog();
5633 ˇ cat();
5634 }"
5635 ));
5636}
5637
5638#[gpui::test]
5639async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5640 init_test(cx, |_| {});
5641
5642 let mut cx = EditorTestContext::new(cx).await;
5643
5644 let html_language = Arc::new(
5645 Language::new(
5646 LanguageConfig {
5647 name: "HTML".into(),
5648 block_comment: Some(("<!-- ".into(), " -->".into())),
5649 ..Default::default()
5650 },
5651 Some(tree_sitter_html::language()),
5652 )
5653 .with_injection_query(
5654 r#"
5655 (script_element
5656 (raw_text) @content
5657 (#set! "language" "javascript"))
5658 "#,
5659 )
5660 .unwrap(),
5661 );
5662
5663 let javascript_language = Arc::new(Language::new(
5664 LanguageConfig {
5665 name: "JavaScript".into(),
5666 line_comment: Some("// ".into()),
5667 ..Default::default()
5668 },
5669 Some(tree_sitter_typescript::language_tsx()),
5670 ));
5671
5672 let registry = Arc::new(LanguageRegistry::test());
5673 registry.add(html_language.clone());
5674 registry.add(javascript_language.clone());
5675
5676 cx.update_buffer(|buffer, cx| {
5677 buffer.set_language_registry(registry);
5678 buffer.set_language(Some(html_language), cx);
5679 });
5680
5681 // Toggle comments for empty selections
5682 cx.set_state(
5683 &r#"
5684 <p>A</p>ˇ
5685 <p>B</p>ˇ
5686 <p>C</p>ˇ
5687 "#
5688 .unindent(),
5689 );
5690 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5691 cx.assert_editor_state(
5692 &r#"
5693 <!-- <p>A</p>ˇ -->
5694 <!-- <p>B</p>ˇ -->
5695 <!-- <p>C</p>ˇ -->
5696 "#
5697 .unindent(),
5698 );
5699 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5700 cx.assert_editor_state(
5701 &r#"
5702 <p>A</p>ˇ
5703 <p>B</p>ˇ
5704 <p>C</p>ˇ
5705 "#
5706 .unindent(),
5707 );
5708
5709 // Toggle comments for mixture of empty and non-empty selections, where
5710 // multiple selections occupy a given line.
5711 cx.set_state(
5712 &r#"
5713 <p>A«</p>
5714 <p>ˇ»B</p>ˇ
5715 <p>C«</p>
5716 <p>ˇ»D</p>ˇ
5717 "#
5718 .unindent(),
5719 );
5720
5721 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5722 cx.assert_editor_state(
5723 &r#"
5724 <!-- <p>A«</p>
5725 <p>ˇ»B</p>ˇ -->
5726 <!-- <p>C«</p>
5727 <p>ˇ»D</p>ˇ -->
5728 "#
5729 .unindent(),
5730 );
5731 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5732 cx.assert_editor_state(
5733 &r#"
5734 <p>A«</p>
5735 <p>ˇ»B</p>ˇ
5736 <p>C«</p>
5737 <p>ˇ»D</p>ˇ
5738 "#
5739 .unindent(),
5740 );
5741
5742 // Toggle comments when different languages are active for different
5743 // selections.
5744 cx.set_state(
5745 &r#"
5746 ˇ<script>
5747 ˇvar x = new Y();
5748 ˇ</script>
5749 "#
5750 .unindent(),
5751 );
5752 cx.foreground().run_until_parked();
5753 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5754 cx.assert_editor_state(
5755 &r#"
5756 <!-- ˇ<script> -->
5757 // ˇvar x = new Y();
5758 <!-- ˇ</script> -->
5759 "#
5760 .unindent(),
5761 );
5762}
5763
5764#[gpui::test]
5765fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5766 init_test(cx, |_| {});
5767
5768 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5769 let multibuffer = cx.add_model(|cx| {
5770 let mut multibuffer = MultiBuffer::new(0);
5771 multibuffer.push_excerpts(
5772 buffer.clone(),
5773 [
5774 ExcerptRange {
5775 context: Point::new(0, 0)..Point::new(0, 4),
5776 primary: None,
5777 },
5778 ExcerptRange {
5779 context: Point::new(1, 0)..Point::new(1, 4),
5780 primary: None,
5781 },
5782 ],
5783 cx,
5784 );
5785 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5786 multibuffer
5787 });
5788
5789 let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5790 view.update(cx, |view, cx| {
5791 assert_eq!(view.text(cx), "aaaa\nbbbb");
5792 view.change_selections(None, cx, |s| {
5793 s.select_ranges([
5794 Point::new(0, 0)..Point::new(0, 0),
5795 Point::new(1, 0)..Point::new(1, 0),
5796 ])
5797 });
5798
5799 view.handle_input("X", cx);
5800 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5801 assert_eq!(
5802 view.selections.ranges(cx),
5803 [
5804 Point::new(0, 1)..Point::new(0, 1),
5805 Point::new(1, 1)..Point::new(1, 1),
5806 ]
5807 );
5808
5809 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5810 view.change_selections(None, cx, |s| {
5811 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5812 });
5813 view.backspace(&Default::default(), cx);
5814 assert_eq!(view.text(cx), "Xa\nbbb");
5815 assert_eq!(
5816 view.selections.ranges(cx),
5817 [Point::new(1, 0)..Point::new(1, 0)]
5818 );
5819
5820 view.change_selections(None, cx, |s| {
5821 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5822 });
5823 view.backspace(&Default::default(), cx);
5824 assert_eq!(view.text(cx), "X\nbb");
5825 assert_eq!(
5826 view.selections.ranges(cx),
5827 [Point::new(0, 1)..Point::new(0, 1)]
5828 );
5829 });
5830}
5831
5832#[gpui::test]
5833fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5834 init_test(cx, |_| {});
5835
5836 let markers = vec![('[', ']').into(), ('(', ')').into()];
5837 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5838 indoc! {"
5839 [aaaa
5840 (bbbb]
5841 cccc)",
5842 },
5843 markers.clone(),
5844 );
5845 let excerpt_ranges = markers.into_iter().map(|marker| {
5846 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5847 ExcerptRange {
5848 context,
5849 primary: None,
5850 }
5851 });
5852 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5853 let multibuffer = cx.add_model(|cx| {
5854 let mut multibuffer = MultiBuffer::new(0);
5855 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5856 multibuffer
5857 });
5858
5859 let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5860 view.update(cx, |view, cx| {
5861 let (expected_text, selection_ranges) = marked_text_ranges(
5862 indoc! {"
5863 aaaa
5864 bˇbbb
5865 bˇbbˇb
5866 cccc"
5867 },
5868 true,
5869 );
5870 assert_eq!(view.text(cx), expected_text);
5871 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5872
5873 view.handle_input("X", cx);
5874
5875 let (expected_text, expected_selections) = marked_text_ranges(
5876 indoc! {"
5877 aaaa
5878 bXˇbbXb
5879 bXˇbbXˇb
5880 cccc"
5881 },
5882 false,
5883 );
5884 assert_eq!(view.text(cx), expected_text);
5885 assert_eq!(view.selections.ranges(cx), expected_selections);
5886
5887 view.newline(&Newline, cx);
5888 let (expected_text, expected_selections) = marked_text_ranges(
5889 indoc! {"
5890 aaaa
5891 bX
5892 ˇbbX
5893 b
5894 bX
5895 ˇbbX
5896 ˇb
5897 cccc"
5898 },
5899 false,
5900 );
5901 assert_eq!(view.text(cx), expected_text);
5902 assert_eq!(view.selections.ranges(cx), expected_selections);
5903 });
5904}
5905
5906#[gpui::test]
5907fn test_refresh_selections(cx: &mut TestAppContext) {
5908 init_test(cx, |_| {});
5909
5910 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5911 let mut excerpt1_id = None;
5912 let multibuffer = cx.add_model(|cx| {
5913 let mut multibuffer = MultiBuffer::new(0);
5914 excerpt1_id = multibuffer
5915 .push_excerpts(
5916 buffer.clone(),
5917 [
5918 ExcerptRange {
5919 context: Point::new(0, 0)..Point::new(1, 4),
5920 primary: None,
5921 },
5922 ExcerptRange {
5923 context: Point::new(1, 0)..Point::new(2, 4),
5924 primary: None,
5925 },
5926 ],
5927 cx,
5928 )
5929 .into_iter()
5930 .next();
5931 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5932 multibuffer
5933 });
5934
5935 let editor = cx
5936 .add_window(|cx| {
5937 let mut editor = build_editor(multibuffer.clone(), cx);
5938 let snapshot = editor.snapshot(cx);
5939 editor.change_selections(None, cx, |s| {
5940 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5941 });
5942 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5943 assert_eq!(
5944 editor.selections.ranges(cx),
5945 [
5946 Point::new(1, 3)..Point::new(1, 3),
5947 Point::new(2, 1)..Point::new(2, 1),
5948 ]
5949 );
5950 editor
5951 })
5952 .root(cx);
5953
5954 // Refreshing selections is a no-op when excerpts haven't changed.
5955 editor.update(cx, |editor, cx| {
5956 editor.change_selections(None, cx, |s| s.refresh());
5957 assert_eq!(
5958 editor.selections.ranges(cx),
5959 [
5960 Point::new(1, 3)..Point::new(1, 3),
5961 Point::new(2, 1)..Point::new(2, 1),
5962 ]
5963 );
5964 });
5965
5966 multibuffer.update(cx, |multibuffer, cx| {
5967 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5968 });
5969 editor.update(cx, |editor, cx| {
5970 // Removing an excerpt causes the first selection to become degenerate.
5971 assert_eq!(
5972 editor.selections.ranges(cx),
5973 [
5974 Point::new(0, 0)..Point::new(0, 0),
5975 Point::new(0, 1)..Point::new(0, 1)
5976 ]
5977 );
5978
5979 // Refreshing selections will relocate the first selection to the original buffer
5980 // location.
5981 editor.change_selections(None, cx, |s| s.refresh());
5982 assert_eq!(
5983 editor.selections.ranges(cx),
5984 [
5985 Point::new(0, 1)..Point::new(0, 1),
5986 Point::new(0, 3)..Point::new(0, 3)
5987 ]
5988 );
5989 assert!(editor.selections.pending_anchor().is_some());
5990 });
5991}
5992
5993#[gpui::test]
5994fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5995 init_test(cx, |_| {});
5996
5997 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5998 let mut excerpt1_id = None;
5999 let multibuffer = cx.add_model(|cx| {
6000 let mut multibuffer = MultiBuffer::new(0);
6001 excerpt1_id = multibuffer
6002 .push_excerpts(
6003 buffer.clone(),
6004 [
6005 ExcerptRange {
6006 context: Point::new(0, 0)..Point::new(1, 4),
6007 primary: None,
6008 },
6009 ExcerptRange {
6010 context: Point::new(1, 0)..Point::new(2, 4),
6011 primary: None,
6012 },
6013 ],
6014 cx,
6015 )
6016 .into_iter()
6017 .next();
6018 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6019 multibuffer
6020 });
6021
6022 let editor = cx
6023 .add_window(|cx| {
6024 let mut editor = build_editor(multibuffer.clone(), cx);
6025 let snapshot = editor.snapshot(cx);
6026 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6027 assert_eq!(
6028 editor.selections.ranges(cx),
6029 [Point::new(1, 3)..Point::new(1, 3)]
6030 );
6031 editor
6032 })
6033 .root(cx);
6034
6035 multibuffer.update(cx, |multibuffer, cx| {
6036 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6037 });
6038 editor.update(cx, |editor, cx| {
6039 assert_eq!(
6040 editor.selections.ranges(cx),
6041 [Point::new(0, 0)..Point::new(0, 0)]
6042 );
6043
6044 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6045 editor.change_selections(None, cx, |s| s.refresh());
6046 assert_eq!(
6047 editor.selections.ranges(cx),
6048 [Point::new(0, 3)..Point::new(0, 3)]
6049 );
6050 assert!(editor.selections.pending_anchor().is_some());
6051 });
6052}
6053
6054#[gpui::test]
6055async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6056 init_test(cx, |_| {});
6057
6058 let language = Arc::new(
6059 Language::new(
6060 LanguageConfig {
6061 brackets: BracketPairConfig {
6062 pairs: vec![
6063 BracketPair {
6064 start: "{".to_string(),
6065 end: "}".to_string(),
6066 close: true,
6067 newline: true,
6068 },
6069 BracketPair {
6070 start: "/* ".to_string(),
6071 end: " */".to_string(),
6072 close: true,
6073 newline: true,
6074 },
6075 ],
6076 ..Default::default()
6077 },
6078 ..Default::default()
6079 },
6080 Some(tree_sitter_rust::language()),
6081 )
6082 .with_indents_query("")
6083 .unwrap(),
6084 );
6085
6086 let text = concat!(
6087 "{ }\n", //
6088 " x\n", //
6089 " /* */\n", //
6090 "x\n", //
6091 "{{} }\n", //
6092 );
6093
6094 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
6095 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
6096 let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
6097 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6098 .await;
6099
6100 view.update(cx, |view, cx| {
6101 view.change_selections(None, cx, |s| {
6102 s.select_display_ranges([
6103 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6104 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6105 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6106 ])
6107 });
6108 view.newline(&Newline, cx);
6109
6110 assert_eq!(
6111 view.buffer().read(cx).read(cx).text(),
6112 concat!(
6113 "{ \n", // Suppress rustfmt
6114 "\n", //
6115 "}\n", //
6116 " x\n", //
6117 " /* \n", //
6118 " \n", //
6119 " */\n", //
6120 "x\n", //
6121 "{{} \n", //
6122 "}\n", //
6123 )
6124 );
6125 });
6126}
6127
6128#[gpui::test]
6129fn test_highlighted_ranges(cx: &mut TestAppContext) {
6130 init_test(cx, |_| {});
6131
6132 let editor = cx
6133 .add_window(|cx| {
6134 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6135 build_editor(buffer.clone(), cx)
6136 })
6137 .root(cx);
6138
6139 editor.update(cx, |editor, cx| {
6140 struct Type1;
6141 struct Type2;
6142
6143 let buffer = editor.buffer.read(cx).snapshot(cx);
6144
6145 let anchor_range =
6146 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6147
6148 editor.highlight_background::<Type1>(
6149 vec![
6150 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6151 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6152 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6153 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6154 ],
6155 |_| Color::red(),
6156 cx,
6157 );
6158 editor.highlight_background::<Type2>(
6159 vec![
6160 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6161 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6162 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6163 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6164 ],
6165 |_| Color::green(),
6166 cx,
6167 );
6168
6169 let snapshot = editor.snapshot(cx);
6170 let mut highlighted_ranges = editor.background_highlights_in_range(
6171 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6172 &snapshot,
6173 theme::current(cx).as_ref(),
6174 );
6175 // Enforce a consistent ordering based on color without relying on the ordering of the
6176 // highlight's `TypeId` which is non-deterministic.
6177 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6178 assert_eq!(
6179 highlighted_ranges,
6180 &[
6181 (
6182 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6183 Color::green(),
6184 ),
6185 (
6186 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6187 Color::green(),
6188 ),
6189 (
6190 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6191 Color::red(),
6192 ),
6193 (
6194 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6195 Color::red(),
6196 ),
6197 ]
6198 );
6199 assert_eq!(
6200 editor.background_highlights_in_range(
6201 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6202 &snapshot,
6203 theme::current(cx).as_ref(),
6204 ),
6205 &[(
6206 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6207 Color::red(),
6208 )]
6209 );
6210 });
6211}
6212
6213#[gpui::test]
6214async fn test_following(cx: &mut gpui::TestAppContext) {
6215 init_test(cx, |_| {});
6216
6217 let fs = FakeFs::new(cx.background());
6218 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6219
6220 let buffer = project.update(cx, |project, cx| {
6221 let buffer = project
6222 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6223 .unwrap();
6224 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
6225 });
6226 let leader = cx
6227 .add_window(|cx| build_editor(buffer.clone(), cx))
6228 .root(cx);
6229 let follower = cx
6230 .update(|cx| {
6231 cx.add_window(
6232 WindowOptions {
6233 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
6234 ..Default::default()
6235 },
6236 |cx| build_editor(buffer.clone(), cx),
6237 )
6238 })
6239 .root(cx);
6240
6241 let is_still_following = Rc::new(RefCell::new(true));
6242 let follower_edit_event_count = Rc::new(RefCell::new(0));
6243 let pending_update = Rc::new(RefCell::new(None));
6244 follower.update(cx, {
6245 let update = pending_update.clone();
6246 let is_still_following = is_still_following.clone();
6247 let follower_edit_event_count = follower_edit_event_count.clone();
6248 |_, cx| {
6249 cx.subscribe(&leader, move |_, leader, event, cx| {
6250 leader
6251 .read(cx)
6252 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6253 })
6254 .detach();
6255
6256 cx.subscribe(&follower, move |_, _, event, cx| {
6257 if Editor::should_unfollow_on_event(event, cx) {
6258 *is_still_following.borrow_mut() = false;
6259 }
6260 if let Event::BufferEdited = event {
6261 *follower_edit_event_count.borrow_mut() += 1;
6262 }
6263 })
6264 .detach();
6265 }
6266 });
6267
6268 // Update the selections only
6269 leader.update(cx, |leader, cx| {
6270 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6271 });
6272 follower
6273 .update(cx, |follower, cx| {
6274 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6275 })
6276 .await
6277 .unwrap();
6278 follower.read_with(cx, |follower, cx| {
6279 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6280 });
6281 assert_eq!(*is_still_following.borrow(), true);
6282 assert_eq!(*follower_edit_event_count.borrow(), 0);
6283
6284 // Update the scroll position only
6285 leader.update(cx, |leader, cx| {
6286 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6287 });
6288 follower
6289 .update(cx, |follower, cx| {
6290 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6291 })
6292 .await
6293 .unwrap();
6294 assert_eq!(
6295 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
6296 vec2f(1.5, 3.5)
6297 );
6298 assert_eq!(*is_still_following.borrow(), true);
6299 assert_eq!(*follower_edit_event_count.borrow(), 0);
6300
6301 // Update the selections and scroll position. The follower's scroll position is updated
6302 // via autoscroll, not via the leader's exact scroll position.
6303 leader.update(cx, |leader, cx| {
6304 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6305 leader.request_autoscroll(Autoscroll::newest(), cx);
6306 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6307 });
6308 follower
6309 .update(cx, |follower, cx| {
6310 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6311 })
6312 .await
6313 .unwrap();
6314 follower.update(cx, |follower, cx| {
6315 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
6316 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6317 });
6318 assert_eq!(*is_still_following.borrow(), true);
6319
6320 // Creating a pending selection that precedes another selection
6321 leader.update(cx, |leader, cx| {
6322 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6323 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6324 });
6325 follower
6326 .update(cx, |follower, cx| {
6327 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6328 })
6329 .await
6330 .unwrap();
6331 follower.read_with(cx, |follower, cx| {
6332 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6333 });
6334 assert_eq!(*is_still_following.borrow(), true);
6335
6336 // Extend the pending selection so that it surrounds another selection
6337 leader.update(cx, |leader, cx| {
6338 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6339 });
6340 follower
6341 .update(cx, |follower, cx| {
6342 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6343 })
6344 .await
6345 .unwrap();
6346 follower.read_with(cx, |follower, cx| {
6347 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6348 });
6349
6350 // Scrolling locally breaks the follow
6351 follower.update(cx, |follower, cx| {
6352 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6353 follower.set_scroll_anchor(
6354 ScrollAnchor {
6355 anchor: top_anchor,
6356 offset: vec2f(0.0, 0.5),
6357 },
6358 cx,
6359 );
6360 });
6361 assert_eq!(*is_still_following.borrow(), false);
6362}
6363
6364#[gpui::test]
6365async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6366 init_test(cx, |_| {});
6367
6368 let fs = FakeFs::new(cx.background());
6369 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6370 let workspace = cx
6371 .add_window(|cx| Workspace::test_new(project.clone(), cx))
6372 .root(cx);
6373 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6374
6375 let leader = pane.update(cx, |_, cx| {
6376 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
6377 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
6378 });
6379
6380 // Start following the editor when it has no excerpts.
6381 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6382 let follower_1 = cx
6383 .update(|cx| {
6384 Editor::from_state_proto(
6385 pane.clone(),
6386 project.clone(),
6387 ViewId {
6388 creator: Default::default(),
6389 id: 0,
6390 },
6391 &mut state_message,
6392 cx,
6393 )
6394 })
6395 .unwrap()
6396 .await
6397 .unwrap();
6398
6399 let update_message = Rc::new(RefCell::new(None));
6400 follower_1.update(cx, {
6401 let update = update_message.clone();
6402 |_, cx| {
6403 cx.subscribe(&leader, move |_, leader, event, cx| {
6404 leader
6405 .read(cx)
6406 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6407 })
6408 .detach();
6409 }
6410 });
6411
6412 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6413 (
6414 project
6415 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6416 .unwrap(),
6417 project
6418 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6419 .unwrap(),
6420 )
6421 });
6422
6423 // Insert some excerpts.
6424 leader.update(cx, |leader, cx| {
6425 leader.buffer.update(cx, |multibuffer, cx| {
6426 let excerpt_ids = multibuffer.push_excerpts(
6427 buffer_1.clone(),
6428 [
6429 ExcerptRange {
6430 context: 1..6,
6431 primary: None,
6432 },
6433 ExcerptRange {
6434 context: 12..15,
6435 primary: None,
6436 },
6437 ExcerptRange {
6438 context: 0..3,
6439 primary: None,
6440 },
6441 ],
6442 cx,
6443 );
6444 multibuffer.insert_excerpts_after(
6445 excerpt_ids[0],
6446 buffer_2.clone(),
6447 [
6448 ExcerptRange {
6449 context: 8..12,
6450 primary: None,
6451 },
6452 ExcerptRange {
6453 context: 0..6,
6454 primary: None,
6455 },
6456 ],
6457 cx,
6458 );
6459 });
6460 });
6461
6462 // Apply the update of adding the excerpts.
6463 follower_1
6464 .update(cx, |follower, cx| {
6465 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6466 })
6467 .await
6468 .unwrap();
6469 assert_eq!(
6470 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6471 leader.read_with(cx, |editor, cx| editor.text(cx))
6472 );
6473 update_message.borrow_mut().take();
6474
6475 // Start following separately after it already has excerpts.
6476 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6477 let follower_2 = cx
6478 .update(|cx| {
6479 Editor::from_state_proto(
6480 pane.clone(),
6481 project.clone(),
6482 ViewId {
6483 creator: Default::default(),
6484 id: 0,
6485 },
6486 &mut state_message,
6487 cx,
6488 )
6489 })
6490 .unwrap()
6491 .await
6492 .unwrap();
6493 assert_eq!(
6494 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6495 leader.read_with(cx, |editor, cx| editor.text(cx))
6496 );
6497
6498 // Remove some excerpts.
6499 leader.update(cx, |leader, cx| {
6500 leader.buffer.update(cx, |multibuffer, cx| {
6501 let excerpt_ids = multibuffer.excerpt_ids();
6502 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6503 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6504 });
6505 });
6506
6507 // Apply the update of removing the excerpts.
6508 follower_1
6509 .update(cx, |follower, cx| {
6510 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6511 })
6512 .await
6513 .unwrap();
6514 follower_2
6515 .update(cx, |follower, cx| {
6516 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6517 })
6518 .await
6519 .unwrap();
6520 update_message.borrow_mut().take();
6521 assert_eq!(
6522 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6523 leader.read_with(cx, |editor, cx| editor.text(cx))
6524 );
6525}
6526
6527#[test]
6528fn test_combine_syntax_and_fuzzy_match_highlights() {
6529 let string = "abcdefghijklmnop";
6530 let syntax_ranges = [
6531 (
6532 0..3,
6533 HighlightStyle {
6534 color: Some(Color::red()),
6535 ..Default::default()
6536 },
6537 ),
6538 (
6539 4..8,
6540 HighlightStyle {
6541 color: Some(Color::green()),
6542 ..Default::default()
6543 },
6544 ),
6545 ];
6546 let match_indices = [4, 6, 7, 8];
6547 assert_eq!(
6548 combine_syntax_and_fuzzy_match_highlights(
6549 string,
6550 Default::default(),
6551 syntax_ranges.into_iter(),
6552 &match_indices,
6553 ),
6554 &[
6555 (
6556 0..3,
6557 HighlightStyle {
6558 color: Some(Color::red()),
6559 ..Default::default()
6560 },
6561 ),
6562 (
6563 4..5,
6564 HighlightStyle {
6565 color: Some(Color::green()),
6566 weight: Some(fonts::Weight::BOLD),
6567 ..Default::default()
6568 },
6569 ),
6570 (
6571 5..6,
6572 HighlightStyle {
6573 color: Some(Color::green()),
6574 ..Default::default()
6575 },
6576 ),
6577 (
6578 6..8,
6579 HighlightStyle {
6580 color: Some(Color::green()),
6581 weight: Some(fonts::Weight::BOLD),
6582 ..Default::default()
6583 },
6584 ),
6585 (
6586 8..9,
6587 HighlightStyle {
6588 weight: Some(fonts::Weight::BOLD),
6589 ..Default::default()
6590 },
6591 ),
6592 ]
6593 );
6594}
6595
6596#[gpui::test]
6597async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6598 init_test(cx, |_| {});
6599
6600 let mut cx = EditorTestContext::new(cx).await;
6601
6602 let diff_base = r#"
6603 use some::mod;
6604
6605 const A: u32 = 42;
6606
6607 fn main() {
6608 println!("hello");
6609
6610 println!("world");
6611 }
6612 "#
6613 .unindent();
6614
6615 // Edits are modified, removed, modified, added
6616 cx.set_state(
6617 &r#"
6618 use some::modified;
6619
6620 ˇ
6621 fn main() {
6622 println!("hello there");
6623
6624 println!("around the");
6625 println!("world");
6626 }
6627 "#
6628 .unindent(),
6629 );
6630
6631 cx.set_diff_base(Some(&diff_base));
6632 deterministic.run_until_parked();
6633
6634 cx.update_editor(|editor, cx| {
6635 //Wrap around the bottom of the buffer
6636 for _ in 0..3 {
6637 editor.go_to_hunk(&GoToHunk, cx);
6638 }
6639 });
6640
6641 cx.assert_editor_state(
6642 &r#"
6643 ˇuse some::modified;
6644
6645
6646 fn main() {
6647 println!("hello there");
6648
6649 println!("around the");
6650 println!("world");
6651 }
6652 "#
6653 .unindent(),
6654 );
6655
6656 cx.update_editor(|editor, cx| {
6657 //Wrap around the top of the buffer
6658 for _ in 0..2 {
6659 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6660 }
6661 });
6662
6663 cx.assert_editor_state(
6664 &r#"
6665 use some::modified;
6666
6667
6668 fn main() {
6669 ˇ println!("hello there");
6670
6671 println!("around the");
6672 println!("world");
6673 }
6674 "#
6675 .unindent(),
6676 );
6677
6678 cx.update_editor(|editor, cx| {
6679 editor.fold(&Fold, cx);
6680
6681 //Make sure that the fold only gets one hunk
6682 for _ in 0..4 {
6683 editor.go_to_hunk(&GoToHunk, cx);
6684 }
6685 });
6686
6687 cx.assert_editor_state(
6688 &r#"
6689 ˇuse some::modified;
6690
6691
6692 fn main() {
6693 println!("hello there");
6694
6695 println!("around the");
6696 println!("world");
6697 }
6698 "#
6699 .unindent(),
6700 );
6701}
6702
6703#[test]
6704fn test_split_words() {
6705 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6706 split_words(text).collect()
6707 }
6708
6709 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6710 assert_eq!(split("hello_world"), &["hello_", "world"]);
6711 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6712 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6713 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6714 assert_eq!(split("helloworld"), &["helloworld"]);
6715}
6716
6717#[gpui::test]
6718async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6719 init_test(cx, |_| {});
6720
6721 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6722 let mut assert = |before, after| {
6723 let _state_context = cx.set_state(before);
6724 cx.update_editor(|editor, cx| {
6725 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6726 });
6727 cx.assert_editor_state(after);
6728 };
6729
6730 // Outside bracket jumps to outside of matching bracket
6731 assert("console.logˇ(var);", "console.log(var)ˇ;");
6732 assert("console.log(var)ˇ;", "console.logˇ(var);");
6733
6734 // Inside bracket jumps to inside of matching bracket
6735 assert("console.log(ˇvar);", "console.log(varˇ);");
6736 assert("console.log(varˇ);", "console.log(ˇvar);");
6737
6738 // When outside a bracket and inside, favor jumping to the inside bracket
6739 assert(
6740 "console.log('foo', [1, 2, 3]ˇ);",
6741 "console.log(ˇ'foo', [1, 2, 3]);",
6742 );
6743 assert(
6744 "console.log(ˇ'foo', [1, 2, 3]);",
6745 "console.log('foo', [1, 2, 3]ˇ);",
6746 );
6747
6748 // Bias forward if two options are equally likely
6749 assert(
6750 "let result = curried_fun()ˇ();",
6751 "let result = curried_fun()()ˇ;",
6752 );
6753
6754 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6755 assert(
6756 indoc! {"
6757 function test() {
6758 console.log('test')ˇ
6759 }"},
6760 indoc! {"
6761 function test() {
6762 console.logˇ('test')
6763 }"},
6764 );
6765}
6766
6767#[gpui::test(iterations = 10)]
6768async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6769 init_test(cx, |_| {});
6770
6771 let (copilot, copilot_lsp) = Copilot::fake(cx);
6772 cx.update(|cx| cx.set_global(copilot));
6773 let mut cx = EditorLspTestContext::new_rust(
6774 lsp::ServerCapabilities {
6775 completion_provider: Some(lsp::CompletionOptions {
6776 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6777 ..Default::default()
6778 }),
6779 ..Default::default()
6780 },
6781 cx,
6782 )
6783 .await;
6784
6785 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6786 cx.set_state(indoc! {"
6787 oneˇ
6788 two
6789 three
6790 "});
6791 cx.simulate_keystroke(".");
6792 let _ = handle_completion_request(
6793 &mut cx,
6794 indoc! {"
6795 one.|<>
6796 two
6797 three
6798 "},
6799 vec!["completion_a", "completion_b"],
6800 );
6801 handle_copilot_completion_request(
6802 &copilot_lsp,
6803 vec![copilot::request::Completion {
6804 text: "one.copilot1".into(),
6805 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6806 ..Default::default()
6807 }],
6808 vec![],
6809 );
6810 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6811 cx.update_editor(|editor, cx| {
6812 assert!(editor.context_menu_visible());
6813 assert!(!editor.has_active_copilot_suggestion(cx));
6814
6815 // Confirming a completion inserts it and hides the context menu, without showing
6816 // the copilot suggestion afterwards.
6817 editor
6818 .confirm_completion(&Default::default(), cx)
6819 .unwrap()
6820 .detach();
6821 assert!(!editor.context_menu_visible());
6822 assert!(!editor.has_active_copilot_suggestion(cx));
6823 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6824 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6825 });
6826
6827 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6828 cx.set_state(indoc! {"
6829 oneˇ
6830 two
6831 three
6832 "});
6833 cx.simulate_keystroke(".");
6834 let _ = handle_completion_request(
6835 &mut cx,
6836 indoc! {"
6837 one.|<>
6838 two
6839 three
6840 "},
6841 vec![],
6842 );
6843 handle_copilot_completion_request(
6844 &copilot_lsp,
6845 vec![copilot::request::Completion {
6846 text: "one.copilot1".into(),
6847 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6848 ..Default::default()
6849 }],
6850 vec![],
6851 );
6852 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6853 cx.update_editor(|editor, cx| {
6854 assert!(!editor.context_menu_visible());
6855 assert!(editor.has_active_copilot_suggestion(cx));
6856 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6857 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6858 });
6859
6860 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6861 cx.set_state(indoc! {"
6862 oneˇ
6863 two
6864 three
6865 "});
6866 cx.simulate_keystroke(".");
6867 let _ = handle_completion_request(
6868 &mut cx,
6869 indoc! {"
6870 one.|<>
6871 two
6872 three
6873 "},
6874 vec!["completion_a", "completion_b"],
6875 );
6876 handle_copilot_completion_request(
6877 &copilot_lsp,
6878 vec![copilot::request::Completion {
6879 text: "one.copilot1".into(),
6880 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6881 ..Default::default()
6882 }],
6883 vec![],
6884 );
6885 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6886 cx.update_editor(|editor, cx| {
6887 assert!(editor.context_menu_visible());
6888 assert!(!editor.has_active_copilot_suggestion(cx));
6889
6890 // When hiding the context menu, the Copilot suggestion becomes visible.
6891 editor.hide_context_menu(cx);
6892 assert!(!editor.context_menu_visible());
6893 assert!(editor.has_active_copilot_suggestion(cx));
6894 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6895 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6896 });
6897
6898 // Ensure existing completion is interpolated when inserting again.
6899 cx.simulate_keystroke("c");
6900 deterministic.run_until_parked();
6901 cx.update_editor(|editor, cx| {
6902 assert!(!editor.context_menu_visible());
6903 assert!(editor.has_active_copilot_suggestion(cx));
6904 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6905 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6906 });
6907
6908 // After debouncing, new Copilot completions should be requested.
6909 handle_copilot_completion_request(
6910 &copilot_lsp,
6911 vec![copilot::request::Completion {
6912 text: "one.copilot2".into(),
6913 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6914 ..Default::default()
6915 }],
6916 vec![],
6917 );
6918 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6919 cx.update_editor(|editor, cx| {
6920 assert!(!editor.context_menu_visible());
6921 assert!(editor.has_active_copilot_suggestion(cx));
6922 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6923 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6924
6925 // Canceling should remove the active Copilot suggestion.
6926 editor.cancel(&Default::default(), cx);
6927 assert!(!editor.has_active_copilot_suggestion(cx));
6928 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6929 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6930
6931 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6932 editor.tab(&Default::default(), cx);
6933 assert!(!editor.has_active_copilot_suggestion(cx));
6934 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6935 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6936
6937 // When undoing the previously active suggestion is shown again.
6938 editor.undo(&Default::default(), cx);
6939 assert!(editor.has_active_copilot_suggestion(cx));
6940 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6941 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6942 });
6943
6944 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6945 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6946 cx.update_editor(|editor, cx| {
6947 assert!(editor.has_active_copilot_suggestion(cx));
6948 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6949 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6950
6951 // Tabbing when there is an active suggestion inserts it.
6952 editor.tab(&Default::default(), cx);
6953 assert!(!editor.has_active_copilot_suggestion(cx));
6954 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6955 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6956
6957 // When undoing the previously active suggestion is shown again.
6958 editor.undo(&Default::default(), cx);
6959 assert!(editor.has_active_copilot_suggestion(cx));
6960 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6961 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6962
6963 // Hide suggestion.
6964 editor.cancel(&Default::default(), cx);
6965 assert!(!editor.has_active_copilot_suggestion(cx));
6966 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6967 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6968 });
6969
6970 // If an edit occurs outside of this editor but no suggestion is being shown,
6971 // we won't make it visible.
6972 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6973 cx.update_editor(|editor, cx| {
6974 assert!(!editor.has_active_copilot_suggestion(cx));
6975 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6976 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6977 });
6978
6979 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6980 cx.update_editor(|editor, cx| {
6981 editor.set_text("fn foo() {\n \n}", cx);
6982 editor.change_selections(None, cx, |s| {
6983 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6984 });
6985 });
6986 handle_copilot_completion_request(
6987 &copilot_lsp,
6988 vec![copilot::request::Completion {
6989 text: " let x = 4;".into(),
6990 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6991 ..Default::default()
6992 }],
6993 vec![],
6994 );
6995
6996 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6997 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6998 cx.update_editor(|editor, cx| {
6999 assert!(editor.has_active_copilot_suggestion(cx));
7000 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7001 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7002
7003 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7004 editor.tab(&Default::default(), cx);
7005 assert!(editor.has_active_copilot_suggestion(cx));
7006 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7007 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7008
7009 // Tabbing again accepts the suggestion.
7010 editor.tab(&Default::default(), cx);
7011 assert!(!editor.has_active_copilot_suggestion(cx));
7012 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7013 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7014 });
7015}
7016
7017#[gpui::test]
7018async fn test_copilot_completion_invalidation(
7019 deterministic: Arc<Deterministic>,
7020 cx: &mut gpui::TestAppContext,
7021) {
7022 init_test(cx, |_| {});
7023
7024 let (copilot, copilot_lsp) = Copilot::fake(cx);
7025 cx.update(|cx| cx.set_global(copilot));
7026 let mut cx = EditorLspTestContext::new_rust(
7027 lsp::ServerCapabilities {
7028 completion_provider: Some(lsp::CompletionOptions {
7029 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7030 ..Default::default()
7031 }),
7032 ..Default::default()
7033 },
7034 cx,
7035 )
7036 .await;
7037
7038 cx.set_state(indoc! {"
7039 one
7040 twˇ
7041 three
7042 "});
7043
7044 handle_copilot_completion_request(
7045 &copilot_lsp,
7046 vec![copilot::request::Completion {
7047 text: "two.foo()".into(),
7048 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7049 ..Default::default()
7050 }],
7051 vec![],
7052 );
7053 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7054 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7055 cx.update_editor(|editor, cx| {
7056 assert!(editor.has_active_copilot_suggestion(cx));
7057 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7058 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7059
7060 editor.backspace(&Default::default(), cx);
7061 assert!(editor.has_active_copilot_suggestion(cx));
7062 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7063 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7064
7065 editor.backspace(&Default::default(), cx);
7066 assert!(editor.has_active_copilot_suggestion(cx));
7067 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7068 assert_eq!(editor.text(cx), "one\n\nthree\n");
7069
7070 // Deleting across the original suggestion range invalidates it.
7071 editor.backspace(&Default::default(), cx);
7072 assert!(!editor.has_active_copilot_suggestion(cx));
7073 assert_eq!(editor.display_text(cx), "one\nthree\n");
7074 assert_eq!(editor.text(cx), "one\nthree\n");
7075
7076 // Undoing the deletion restores the suggestion.
7077 editor.undo(&Default::default(), cx);
7078 assert!(editor.has_active_copilot_suggestion(cx));
7079 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7080 assert_eq!(editor.text(cx), "one\n\nthree\n");
7081 });
7082}
7083
7084#[gpui::test]
7085async fn test_copilot_multibuffer(
7086 deterministic: Arc<Deterministic>,
7087 cx: &mut gpui::TestAppContext,
7088) {
7089 init_test(cx, |_| {});
7090
7091 let (copilot, copilot_lsp) = Copilot::fake(cx);
7092 cx.update(|cx| cx.set_global(copilot));
7093
7094 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
7095 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
7096 let multibuffer = cx.add_model(|cx| {
7097 let mut multibuffer = MultiBuffer::new(0);
7098 multibuffer.push_excerpts(
7099 buffer_1.clone(),
7100 [ExcerptRange {
7101 context: Point::new(0, 0)..Point::new(2, 0),
7102 primary: None,
7103 }],
7104 cx,
7105 );
7106 multibuffer.push_excerpts(
7107 buffer_2.clone(),
7108 [ExcerptRange {
7109 context: Point::new(0, 0)..Point::new(2, 0),
7110 primary: None,
7111 }],
7112 cx,
7113 );
7114 multibuffer
7115 });
7116 let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7117
7118 handle_copilot_completion_request(
7119 &copilot_lsp,
7120 vec![copilot::request::Completion {
7121 text: "b = 2 + a".into(),
7122 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7123 ..Default::default()
7124 }],
7125 vec![],
7126 );
7127 editor.update(cx, |editor, cx| {
7128 // Ensure copilot suggestions are shown for the first excerpt.
7129 editor.change_selections(None, cx, |s| {
7130 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7131 });
7132 editor.next_copilot_suggestion(&Default::default(), cx);
7133 });
7134 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7135 editor.update(cx, |editor, cx| {
7136 assert!(editor.has_active_copilot_suggestion(cx));
7137 assert_eq!(
7138 editor.display_text(cx),
7139 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7140 );
7141 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7142 });
7143
7144 handle_copilot_completion_request(
7145 &copilot_lsp,
7146 vec![copilot::request::Completion {
7147 text: "d = 4 + c".into(),
7148 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7149 ..Default::default()
7150 }],
7151 vec![],
7152 );
7153 editor.update(cx, |editor, cx| {
7154 // Move to another excerpt, ensuring the suggestion gets cleared.
7155 editor.change_selections(None, cx, |s| {
7156 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7157 });
7158 assert!(!editor.has_active_copilot_suggestion(cx));
7159 assert_eq!(
7160 editor.display_text(cx),
7161 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7162 );
7163 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7164
7165 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7166 editor.handle_input(" ", cx);
7167 assert!(!editor.has_active_copilot_suggestion(cx));
7168 assert_eq!(
7169 editor.display_text(cx),
7170 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7171 );
7172 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7173 });
7174
7175 // Ensure the new suggestion is displayed when the debounce timeout expires.
7176 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7177 editor.update(cx, |editor, cx| {
7178 assert!(editor.has_active_copilot_suggestion(cx));
7179 assert_eq!(
7180 editor.display_text(cx),
7181 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7182 );
7183 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7184 });
7185}
7186
7187#[gpui::test]
7188async fn test_copilot_disabled_globs(
7189 deterministic: Arc<Deterministic>,
7190 cx: &mut gpui::TestAppContext,
7191) {
7192 init_test(cx, |settings| {
7193 settings
7194 .copilot
7195 .get_or_insert(Default::default())
7196 .disabled_globs = Some(vec![".env*".to_string()]);
7197 });
7198
7199 let (copilot, copilot_lsp) = Copilot::fake(cx);
7200 cx.update(|cx| cx.set_global(copilot));
7201
7202 let fs = FakeFs::new(cx.background());
7203 fs.insert_tree(
7204 "/test",
7205 json!({
7206 ".env": "SECRET=something\n",
7207 "README.md": "hello\n"
7208 }),
7209 )
7210 .await;
7211 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7212
7213 let private_buffer = project
7214 .update(cx, |project, cx| {
7215 project.open_local_buffer("/test/.env", cx)
7216 })
7217 .await
7218 .unwrap();
7219 let public_buffer = project
7220 .update(cx, |project, cx| {
7221 project.open_local_buffer("/test/README.md", cx)
7222 })
7223 .await
7224 .unwrap();
7225
7226 let multibuffer = cx.add_model(|cx| {
7227 let mut multibuffer = MultiBuffer::new(0);
7228 multibuffer.push_excerpts(
7229 private_buffer.clone(),
7230 [ExcerptRange {
7231 context: Point::new(0, 0)..Point::new(1, 0),
7232 primary: None,
7233 }],
7234 cx,
7235 );
7236 multibuffer.push_excerpts(
7237 public_buffer.clone(),
7238 [ExcerptRange {
7239 context: Point::new(0, 0)..Point::new(1, 0),
7240 primary: None,
7241 }],
7242 cx,
7243 );
7244 multibuffer
7245 });
7246 let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7247
7248 let mut copilot_requests = copilot_lsp
7249 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7250 Ok(copilot::request::GetCompletionsResult {
7251 completions: vec![copilot::request::Completion {
7252 text: "next line".into(),
7253 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7254 ..Default::default()
7255 }],
7256 })
7257 });
7258
7259 editor.update(cx, |editor, cx| {
7260 editor.change_selections(None, cx, |selections| {
7261 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7262 });
7263 editor.next_copilot_suggestion(&Default::default(), cx);
7264 });
7265
7266 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7267 assert!(copilot_requests.try_next().is_err());
7268
7269 editor.update(cx, |editor, cx| {
7270 editor.change_selections(None, cx, |s| {
7271 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7272 });
7273 editor.next_copilot_suggestion(&Default::default(), cx);
7274 });
7275
7276 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7277 assert!(copilot_requests.try_next().is_ok());
7278}
7279
7280#[gpui::test]
7281async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7282 init_test(cx, |_| {});
7283
7284 let mut language = Language::new(
7285 LanguageConfig {
7286 name: "Rust".into(),
7287 path_suffixes: vec!["rs".to_string()],
7288 brackets: BracketPairConfig {
7289 pairs: vec![BracketPair {
7290 start: "{".to_string(),
7291 end: "}".to_string(),
7292 close: true,
7293 newline: true,
7294 }],
7295 disabled_scopes_by_bracket_ix: Vec::new(),
7296 },
7297 ..Default::default()
7298 },
7299 Some(tree_sitter_rust::language()),
7300 );
7301 let mut fake_servers = language
7302 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7303 capabilities: lsp::ServerCapabilities {
7304 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7305 first_trigger_character: "{".to_string(),
7306 more_trigger_character: None,
7307 }),
7308 ..Default::default()
7309 },
7310 ..Default::default()
7311 }))
7312 .await;
7313
7314 let fs = FakeFs::new(cx.background());
7315 fs.insert_tree(
7316 "/a",
7317 json!({
7318 "main.rs": "fn main() { let a = 5; }",
7319 "other.rs": "// Test file",
7320 }),
7321 )
7322 .await;
7323 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7324 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7325 let workspace = cx
7326 .add_window(|cx| Workspace::test_new(project.clone(), cx))
7327 .root(cx);
7328 let worktree_id = workspace.update(cx, |workspace, cx| {
7329 workspace.project().read_with(cx, |project, cx| {
7330 project.worktrees(cx).next().unwrap().read(cx).id()
7331 })
7332 });
7333
7334 let buffer = project
7335 .update(cx, |project, cx| {
7336 project.open_local_buffer("/a/main.rs", cx)
7337 })
7338 .await
7339 .unwrap();
7340 cx.foreground().run_until_parked();
7341 cx.foreground().start_waiting();
7342 let fake_server = fake_servers.next().await.unwrap();
7343 let editor_handle = workspace
7344 .update(cx, |workspace, cx| {
7345 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7346 })
7347 .await
7348 .unwrap()
7349 .downcast::<Editor>()
7350 .unwrap();
7351
7352 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7353 assert_eq!(
7354 params.text_document_position.text_document.uri,
7355 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7356 );
7357 assert_eq!(
7358 params.text_document_position.position,
7359 lsp::Position::new(0, 21),
7360 );
7361
7362 Ok(Some(vec![lsp::TextEdit {
7363 new_text: "]".to_string(),
7364 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7365 }]))
7366 });
7367
7368 editor_handle.update(cx, |editor, cx| {
7369 cx.focus(&editor_handle);
7370 editor.change_selections(None, cx, |s| {
7371 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7372 });
7373 editor.handle_input("{", cx);
7374 });
7375
7376 cx.foreground().run_until_parked();
7377
7378 buffer.read_with(cx, |buffer, _| {
7379 assert_eq!(
7380 buffer.text(),
7381 "fn main() { let a = {5}; }",
7382 "No extra braces from on type formatting should appear in the buffer"
7383 )
7384 });
7385}
7386
7387#[gpui::test]
7388async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7389 init_test(cx, |_| {});
7390
7391 let language_name: Arc<str> = "Rust".into();
7392 let mut language = Language::new(
7393 LanguageConfig {
7394 name: Arc::clone(&language_name),
7395 path_suffixes: vec!["rs".to_string()],
7396 ..Default::default()
7397 },
7398 Some(tree_sitter_rust::language()),
7399 );
7400
7401 let server_restarts = Arc::new(AtomicUsize::new(0));
7402 let closure_restarts = Arc::clone(&server_restarts);
7403 let language_server_name = "test language server";
7404 let mut fake_servers = language
7405 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7406 name: language_server_name,
7407 initialization_options: Some(json!({
7408 "testOptionValue": true
7409 })),
7410 initializer: Some(Box::new(move |fake_server| {
7411 let task_restarts = Arc::clone(&closure_restarts);
7412 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7413 task_restarts.fetch_add(1, atomic::Ordering::Release);
7414 futures::future::ready(Ok(()))
7415 });
7416 })),
7417 ..Default::default()
7418 }))
7419 .await;
7420
7421 let fs = FakeFs::new(cx.background());
7422 fs.insert_tree(
7423 "/a",
7424 json!({
7425 "main.rs": "fn main() { let a = 5; }",
7426 "other.rs": "// Test file",
7427 }),
7428 )
7429 .await;
7430 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7431 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7432 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7433 let _buffer = project
7434 .update(cx, |project, cx| {
7435 project.open_local_buffer("/a/main.rs", cx)
7436 })
7437 .await
7438 .unwrap();
7439 let _fake_server = fake_servers.next().await.unwrap();
7440 update_test_language_settings(cx, |language_settings| {
7441 language_settings.languages.insert(
7442 Arc::clone(&language_name),
7443 LanguageSettingsContent {
7444 tab_size: NonZeroU32::new(8),
7445 ..Default::default()
7446 },
7447 );
7448 });
7449 cx.foreground().run_until_parked();
7450 assert_eq!(
7451 server_restarts.load(atomic::Ordering::Acquire),
7452 0,
7453 "Should not restart LSP server on an unrelated change"
7454 );
7455
7456 update_test_project_settings(cx, |project_settings| {
7457 project_settings.lsp.insert(
7458 "Some other server name".into(),
7459 LspSettings {
7460 initialization_options: Some(json!({
7461 "some other init value": false
7462 })),
7463 },
7464 );
7465 });
7466 cx.foreground().run_until_parked();
7467 assert_eq!(
7468 server_restarts.load(atomic::Ordering::Acquire),
7469 0,
7470 "Should not restart LSP server on an unrelated LSP settings change"
7471 );
7472
7473 update_test_project_settings(cx, |project_settings| {
7474 project_settings.lsp.insert(
7475 language_server_name.into(),
7476 LspSettings {
7477 initialization_options: Some(json!({
7478 "anotherInitValue": false
7479 })),
7480 },
7481 );
7482 });
7483 cx.foreground().run_until_parked();
7484 assert_eq!(
7485 server_restarts.load(atomic::Ordering::Acquire),
7486 1,
7487 "Should restart LSP server on a related LSP settings change"
7488 );
7489
7490 update_test_project_settings(cx, |project_settings| {
7491 project_settings.lsp.insert(
7492 language_server_name.into(),
7493 LspSettings {
7494 initialization_options: Some(json!({
7495 "anotherInitValue": false
7496 })),
7497 },
7498 );
7499 });
7500 cx.foreground().run_until_parked();
7501 assert_eq!(
7502 server_restarts.load(atomic::Ordering::Acquire),
7503 1,
7504 "Should not restart LSP server on a related LSP settings change that is the same"
7505 );
7506
7507 update_test_project_settings(cx, |project_settings| {
7508 project_settings.lsp.insert(
7509 language_server_name.into(),
7510 LspSettings {
7511 initialization_options: None,
7512 },
7513 );
7514 });
7515 cx.foreground().run_until_parked();
7516 assert_eq!(
7517 server_restarts.load(atomic::Ordering::Acquire),
7518 2,
7519 "Should restart LSP server on another related LSP settings change"
7520 );
7521}
7522
7523#[gpui::test]
7524async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7525 init_test(cx, |_| {});
7526
7527 let mut cx = EditorLspTestContext::new_rust(
7528 lsp::ServerCapabilities {
7529 completion_provider: Some(lsp::CompletionOptions {
7530 trigger_characters: Some(vec![".".to_string()]),
7531 ..Default::default()
7532 }),
7533 ..Default::default()
7534 },
7535 cx,
7536 )
7537 .await;
7538
7539 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7540 cx.simulate_keystroke(".");
7541 let completion_item = lsp::CompletionItem {
7542 label: "some".into(),
7543 kind: Some(lsp::CompletionItemKind::SNIPPET),
7544 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7545 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7546 kind: lsp::MarkupKind::Markdown,
7547 value: "```rust\nSome(2)\n```".to_string(),
7548 })),
7549 deprecated: Some(false),
7550 sort_text: Some("fffffff2".to_string()),
7551 filter_text: Some("some".to_string()),
7552 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7553 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7554 range: lsp::Range {
7555 start: lsp::Position {
7556 line: 0,
7557 character: 22,
7558 },
7559 end: lsp::Position {
7560 line: 0,
7561 character: 22,
7562 },
7563 },
7564 new_text: "Some(2)".to_string(),
7565 })),
7566 additional_text_edits: Some(vec![lsp::TextEdit {
7567 range: lsp::Range {
7568 start: lsp::Position {
7569 line: 0,
7570 character: 20,
7571 },
7572 end: lsp::Position {
7573 line: 0,
7574 character: 22,
7575 },
7576 },
7577 new_text: "".to_string(),
7578 }]),
7579 ..Default::default()
7580 };
7581
7582 let closure_completion_item = completion_item.clone();
7583 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7584 let task_completion_item = closure_completion_item.clone();
7585 async move {
7586 Ok(Some(lsp::CompletionResponse::Array(vec![
7587 task_completion_item,
7588 ])))
7589 }
7590 });
7591
7592 request.next().await;
7593
7594 cx.condition(|editor, _| editor.context_menu_visible())
7595 .await;
7596 let apply_additional_edits = cx.update_editor(|editor, cx| {
7597 editor
7598 .confirm_completion(&ConfirmCompletion::default(), cx)
7599 .unwrap()
7600 });
7601 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7602
7603 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7604 let task_completion_item = completion_item.clone();
7605 async move { Ok(task_completion_item) }
7606 })
7607 .next()
7608 .await
7609 .unwrap();
7610 apply_additional_edits.await.unwrap();
7611 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7612}
7613
7614fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
7615 let point = DisplayPoint::new(row as u32, column as u32);
7616 point..point
7617}
7618
7619fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
7620 let (text, ranges) = marked_text_ranges(marked_text, true);
7621 assert_eq!(view.text(cx), text);
7622 assert_eq!(
7623 view.selections.ranges(cx),
7624 ranges,
7625 "Assert selections are {}",
7626 marked_text
7627 );
7628}
7629
7630/// Handle completion request passing a marked string specifying where the completion
7631/// should be triggered from using '|' character, what range should be replaced, and what completions
7632/// should be returned using '<' and '>' to delimit the range
7633fn handle_completion_request<'a>(
7634 cx: &mut EditorLspTestContext<'a>,
7635 marked_string: &str,
7636 completions: Vec<&'static str>,
7637) -> impl Future<Output = ()> {
7638 let complete_from_marker: TextRangeMarker = '|'.into();
7639 let replace_range_marker: TextRangeMarker = ('<', '>').into();
7640 let (_, mut marked_ranges) = marked_text_ranges_by(
7641 marked_string,
7642 vec![complete_from_marker.clone(), replace_range_marker.clone()],
7643 );
7644
7645 let complete_from_position =
7646 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
7647 let replace_range =
7648 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
7649
7650 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
7651 let completions = completions.clone();
7652 async move {
7653 assert_eq!(params.text_document_position.text_document.uri, url.clone());
7654 assert_eq!(
7655 params.text_document_position.position,
7656 complete_from_position
7657 );
7658 Ok(Some(lsp::CompletionResponse::Array(
7659 completions
7660 .iter()
7661 .map(|completion_text| lsp::CompletionItem {
7662 label: completion_text.to_string(),
7663 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7664 range: replace_range,
7665 new_text: completion_text.to_string(),
7666 })),
7667 ..Default::default()
7668 })
7669 .collect(),
7670 )))
7671 }
7672 });
7673
7674 async move {
7675 request.next().await;
7676 }
7677}
7678
7679fn handle_resolve_completion_request<'a>(
7680 cx: &mut EditorLspTestContext<'a>,
7681 edits: Option<Vec<(&'static str, &'static str)>>,
7682) -> impl Future<Output = ()> {
7683 let edits = edits.map(|edits| {
7684 edits
7685 .iter()
7686 .map(|(marked_string, new_text)| {
7687 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
7688 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
7689 lsp::TextEdit::new(replace_range, new_text.to_string())
7690 })
7691 .collect::<Vec<_>>()
7692 });
7693
7694 let mut request =
7695 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7696 let edits = edits.clone();
7697 async move {
7698 Ok(lsp::CompletionItem {
7699 additional_text_edits: edits,
7700 ..Default::default()
7701 })
7702 }
7703 });
7704
7705 async move {
7706 request.next().await;
7707 }
7708}
7709
7710fn handle_copilot_completion_request(
7711 lsp: &lsp::FakeLanguageServer,
7712 completions: Vec<copilot::request::Completion>,
7713 completions_cycling: Vec<copilot::request::Completion>,
7714) {
7715 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
7716 let completions = completions.clone();
7717 async move {
7718 Ok(copilot::request::GetCompletionsResult {
7719 completions: completions.clone(),
7720 })
7721 }
7722 });
7723 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
7724 let completions_cycling = completions_cycling.clone();
7725 async move {
7726 Ok(copilot::request::GetCompletionsResult {
7727 completions: completions_cycling.clone(),
7728 })
7729 }
7730 });
7731}
7732
7733pub(crate) fn update_test_language_settings(
7734 cx: &mut TestAppContext,
7735 f: impl Fn(&mut AllLanguageSettingsContent),
7736) {
7737 cx.update(|cx| {
7738 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7739 store.update_user_settings::<AllLanguageSettings>(cx, f);
7740 });
7741 });
7742}
7743
7744pub(crate) fn update_test_project_settings(
7745 cx: &mut TestAppContext,
7746 f: impl Fn(&mut ProjectSettings),
7747) {
7748 cx.update(|cx| {
7749 cx.update_global::<SettingsStore, _, _>(|store, cx| {
7750 store.update_user_settings::<ProjectSettings>(cx, f);
7751 });
7752 });
7753}
7754
7755pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
7756 cx.foreground().forbid_parking();
7757
7758 cx.update(|cx| {
7759 cx.set_global(SettingsStore::test(cx));
7760 theme::init((), cx);
7761 client::init_settings(cx);
7762 language::init(cx);
7763 Project::init_settings(cx);
7764 workspace::init_settings(cx);
7765 crate::init(cx);
7766 });
7767
7768 update_test_language_settings(cx, f);
7769}