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