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