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