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