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