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