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