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