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