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