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