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_start_of_paragraph_end_of_paragraph(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#"ˇone
1256 two
1257
1258 three
1259 fourˇ
1260 five
1261
1262 six"#
1263 .unindent(),
1264 );
1265
1266 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1267 cx.assert_editor_state(
1268 &r#"one
1269 two
1270 ˇ
1271 three
1272 four
1273 five
1274 ˇ
1275 six"#
1276 .unindent(),
1277 );
1278
1279 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1280 cx.assert_editor_state(
1281 &r#"one
1282 two
1283
1284 three
1285 four
1286 five
1287 ˇ
1288 sixˇ"#
1289 .unindent(),
1290 );
1291
1292 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1293 cx.assert_editor_state(
1294 &r#"ˇone
1295 two
1296
1297 three
1298 four
1299 five
1300
1301 sixˇ"#
1302 .unindent(),
1303 );
1304
1305 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1306 cx.assert_editor_state(
1307 &r#"ˇone
1308 two
1309 ˇ
1310 three
1311 four
1312 five
1313
1314 six"#
1315 .unindent(),
1316 );
1317
1318 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1319 cx.assert_editor_state(
1320 &r#"ˇone
1321 two
1322
1323 three
1324 four
1325 five
1326
1327 sixˇ"#
1328 .unindent(),
1329 );
1330
1331 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1332 cx.assert_editor_state(
1333 &r#"one
1334 two
1335
1336 three
1337 four
1338 five
1339 ˇ
1340 sixˇ"#
1341 .unindent(),
1342 );
1343
1344 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1345 cx.assert_editor_state(
1346 &r#"one
1347 two
1348 ˇ
1349 three
1350 four
1351 five
1352 ˇ
1353 six"#
1354 .unindent(),
1355 );
1356}
1357
1358#[gpui::test]
1359async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1360 init_test(cx, |_| {});
1361 let mut cx = EditorTestContext::new(cx);
1362
1363 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1364 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1365
1366 cx.set_state(
1367 &r#"
1368 ˇone
1369 two
1370 threeˇ
1371 four
1372 five
1373 six
1374 seven
1375 eight
1376 nine
1377 ten
1378 "#
1379 .unindent(),
1380 );
1381
1382 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1383 cx.assert_editor_state(
1384 &r#"
1385 one
1386 two
1387 three
1388 ˇfour
1389 five
1390 sixˇ
1391 seven
1392 eight
1393 nine
1394 ten
1395 "#
1396 .unindent(),
1397 );
1398
1399 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1400 cx.assert_editor_state(
1401 &r#"
1402 one
1403 two
1404 three
1405 four
1406 five
1407 six
1408 ˇseven
1409 eight
1410 nineˇ
1411 ten
1412 "#
1413 .unindent(),
1414 );
1415
1416 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1417 cx.assert_editor_state(
1418 &r#"
1419 one
1420 two
1421 three
1422 ˇfour
1423 five
1424 sixˇ
1425 seven
1426 eight
1427 nine
1428 ten
1429 "#
1430 .unindent(),
1431 );
1432
1433 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1434 cx.assert_editor_state(
1435 &r#"
1436 ˇone
1437 two
1438 threeˇ
1439 four
1440 five
1441 six
1442 seven
1443 eight
1444 nine
1445 ten
1446 "#
1447 .unindent(),
1448 );
1449
1450 // Test select collapsing
1451 cx.update_editor(|editor, cx| {
1452 editor.move_page_down(&MovePageDown::default(), cx);
1453 editor.move_page_down(&MovePageDown::default(), cx);
1454 editor.move_page_down(&MovePageDown::default(), cx);
1455 });
1456 cx.assert_editor_state(
1457 &r#"
1458 one
1459 two
1460 three
1461 four
1462 five
1463 six
1464 seven
1465 eight
1466 nine
1467 ˇten
1468 ˇ"#
1469 .unindent(),
1470 );
1471}
1472
1473#[gpui::test]
1474async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1475 init_test(cx, |_| {});
1476 let mut cx = EditorTestContext::new(cx);
1477 cx.set_state("one «two threeˇ» four");
1478 cx.update_editor(|editor, cx| {
1479 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1480 assert_eq!(editor.text(cx), " four");
1481 });
1482}
1483
1484#[gpui::test]
1485fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1486 init_test(cx, |_| {});
1487
1488 let (_, view) = cx.add_window(|cx| {
1489 let buffer = MultiBuffer::build_simple("one two three four", cx);
1490 build_editor(buffer.clone(), cx)
1491 });
1492
1493 view.update(cx, |view, cx| {
1494 view.change_selections(None, cx, |s| {
1495 s.select_display_ranges([
1496 // an empty selection - the preceding word fragment is deleted
1497 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1498 // characters selected - they are deleted
1499 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1500 ])
1501 });
1502 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1503 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1504 });
1505
1506 view.update(cx, |view, cx| {
1507 view.change_selections(None, cx, |s| {
1508 s.select_display_ranges([
1509 // an empty selection - the following word fragment is deleted
1510 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1511 // characters selected - they are deleted
1512 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1513 ])
1514 });
1515 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1516 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1517 });
1518}
1519
1520#[gpui::test]
1521fn test_newline(cx: &mut TestAppContext) {
1522 init_test(cx, |_| {});
1523
1524 let (_, view) = cx.add_window(|cx| {
1525 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1526 build_editor(buffer.clone(), cx)
1527 });
1528
1529 view.update(cx, |view, cx| {
1530 view.change_selections(None, cx, |s| {
1531 s.select_display_ranges([
1532 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1533 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1534 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1535 ])
1536 });
1537
1538 view.newline(&Newline, cx);
1539 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1540 });
1541}
1542
1543#[gpui::test]
1544fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1545 init_test(cx, |_| {});
1546
1547 let (_, editor) = cx.add_window(|cx| {
1548 let buffer = MultiBuffer::build_simple(
1549 "
1550 a
1551 b(
1552 X
1553 )
1554 c(
1555 X
1556 )
1557 "
1558 .unindent()
1559 .as_str(),
1560 cx,
1561 );
1562 let mut editor = build_editor(buffer.clone(), cx);
1563 editor.change_selections(None, cx, |s| {
1564 s.select_ranges([
1565 Point::new(2, 4)..Point::new(2, 5),
1566 Point::new(5, 4)..Point::new(5, 5),
1567 ])
1568 });
1569 editor
1570 });
1571
1572 editor.update(cx, |editor, cx| {
1573 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1574 editor.buffer.update(cx, |buffer, cx| {
1575 buffer.edit(
1576 [
1577 (Point::new(1, 2)..Point::new(3, 0), ""),
1578 (Point::new(4, 2)..Point::new(6, 0), ""),
1579 ],
1580 None,
1581 cx,
1582 );
1583 assert_eq!(
1584 buffer.read(cx).text(),
1585 "
1586 a
1587 b()
1588 c()
1589 "
1590 .unindent()
1591 );
1592 });
1593 assert_eq!(
1594 editor.selections.ranges(cx),
1595 &[
1596 Point::new(1, 2)..Point::new(1, 2),
1597 Point::new(2, 2)..Point::new(2, 2),
1598 ],
1599 );
1600
1601 editor.newline(&Newline, cx);
1602 assert_eq!(
1603 editor.text(cx),
1604 "
1605 a
1606 b(
1607 )
1608 c(
1609 )
1610 "
1611 .unindent()
1612 );
1613
1614 // The selections are moved after the inserted newlines
1615 assert_eq!(
1616 editor.selections.ranges(cx),
1617 &[
1618 Point::new(2, 0)..Point::new(2, 0),
1619 Point::new(4, 0)..Point::new(4, 0),
1620 ],
1621 );
1622 });
1623}
1624
1625#[gpui::test]
1626async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1627 init_test(cx, |settings| {
1628 settings.defaults.tab_size = NonZeroU32::new(4)
1629 });
1630
1631 let language = Arc::new(
1632 Language::new(
1633 LanguageConfig::default(),
1634 Some(tree_sitter_rust::language()),
1635 )
1636 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1637 .unwrap(),
1638 );
1639
1640 let mut cx = EditorTestContext::new(cx);
1641 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1642 cx.set_state(indoc! {"
1643 const a: ˇA = (
1644 (ˇ
1645 «const_functionˇ»(ˇ),
1646 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1647 )ˇ
1648 ˇ);ˇ
1649 "});
1650
1651 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1652 cx.assert_editor_state(indoc! {"
1653 ˇ
1654 const a: A = (
1655 ˇ
1656 (
1657 ˇ
1658 ˇ
1659 const_function(),
1660 ˇ
1661 ˇ
1662 ˇ
1663 ˇ
1664 something_else,
1665 ˇ
1666 )
1667 ˇ
1668 ˇ
1669 );
1670 "});
1671}
1672
1673#[gpui::test]
1674async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1675 init_test(cx, |settings| {
1676 settings.defaults.tab_size = NonZeroU32::new(4)
1677 });
1678
1679 let language = Arc::new(
1680 Language::new(
1681 LanguageConfig::default(),
1682 Some(tree_sitter_rust::language()),
1683 )
1684 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1685 .unwrap(),
1686 );
1687
1688 let mut cx = EditorTestContext::new(cx);
1689 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1690 cx.set_state(indoc! {"
1691 const a: ˇA = (
1692 (ˇ
1693 «const_functionˇ»(ˇ),
1694 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1695 )ˇ
1696 ˇ);ˇ
1697 "});
1698
1699 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1700 cx.assert_editor_state(indoc! {"
1701 const a: A = (
1702 ˇ
1703 (
1704 ˇ
1705 const_function(),
1706 ˇ
1707 ˇ
1708 something_else,
1709 ˇ
1710 ˇ
1711 ˇ
1712 ˇ
1713 )
1714 ˇ
1715 );
1716 ˇ
1717 ˇ
1718 "});
1719}
1720
1721#[gpui::test]
1722fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1723 init_test(cx, |_| {});
1724
1725 let (_, editor) = cx.add_window(|cx| {
1726 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1727 let mut editor = build_editor(buffer.clone(), cx);
1728 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1729 editor
1730 });
1731
1732 editor.update(cx, |editor, cx| {
1733 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1734 editor.buffer.update(cx, |buffer, cx| {
1735 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1736 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1737 });
1738 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1739
1740 editor.insert("Z", cx);
1741 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1742
1743 // The selections are moved after the inserted characters
1744 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1745 });
1746}
1747
1748#[gpui::test]
1749async fn test_tab(cx: &mut gpui::TestAppContext) {
1750 init_test(cx, |settings| {
1751 settings.defaults.tab_size = NonZeroU32::new(3)
1752 });
1753
1754 let mut cx = EditorTestContext::new(cx);
1755 cx.set_state(indoc! {"
1756 ˇabˇc
1757 ˇ🏀ˇ🏀ˇefg
1758 dˇ
1759 "});
1760 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1761 cx.assert_editor_state(indoc! {"
1762 ˇab ˇc
1763 ˇ🏀 ˇ🏀 ˇefg
1764 d ˇ
1765 "});
1766
1767 cx.set_state(indoc! {"
1768 a
1769 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1770 "});
1771 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1772 cx.assert_editor_state(indoc! {"
1773 a
1774 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1775 "});
1776}
1777
1778#[gpui::test]
1779async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1780 init_test(cx, |_| {});
1781
1782 let mut cx = EditorTestContext::new(cx);
1783 let language = Arc::new(
1784 Language::new(
1785 LanguageConfig::default(),
1786 Some(tree_sitter_rust::language()),
1787 )
1788 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1789 .unwrap(),
1790 );
1791 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1792
1793 // cursors that are already at the suggested indent level insert
1794 // a soft tab. cursors that are to the left of the suggested indent
1795 // auto-indent their line.
1796 cx.set_state(indoc! {"
1797 ˇ
1798 const a: B = (
1799 c(
1800 d(
1801 ˇ
1802 )
1803 ˇ
1804 ˇ )
1805 );
1806 "});
1807 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1808 cx.assert_editor_state(indoc! {"
1809 ˇ
1810 const a: B = (
1811 c(
1812 d(
1813 ˇ
1814 )
1815 ˇ
1816 ˇ)
1817 );
1818 "});
1819
1820 // handle auto-indent when there are multiple cursors on the same line
1821 cx.set_state(indoc! {"
1822 const a: B = (
1823 c(
1824 ˇ ˇ
1825 ˇ )
1826 );
1827 "});
1828 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1829 cx.assert_editor_state(indoc! {"
1830 const a: B = (
1831 c(
1832 ˇ
1833 ˇ)
1834 );
1835 "});
1836}
1837
1838#[gpui::test]
1839async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1840 init_test(cx, |settings| {
1841 settings.defaults.tab_size = NonZeroU32::new(4)
1842 });
1843
1844 let language = Arc::new(
1845 Language::new(
1846 LanguageConfig::default(),
1847 Some(tree_sitter_rust::language()),
1848 )
1849 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1850 .unwrap(),
1851 );
1852
1853 let mut cx = EditorTestContext::new(cx);
1854 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1855 cx.set_state(indoc! {"
1856 fn a() {
1857 if b {
1858 \t ˇc
1859 }
1860 }
1861 "});
1862
1863 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1864 cx.assert_editor_state(indoc! {"
1865 fn a() {
1866 if b {
1867 ˇc
1868 }
1869 }
1870 "});
1871}
1872
1873#[gpui::test]
1874async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1875 init_test(cx, |settings| {
1876 settings.defaults.tab_size = NonZeroU32::new(4);
1877 });
1878
1879 let mut cx = EditorTestContext::new(cx);
1880
1881 cx.set_state(indoc! {"
1882 «oneˇ» «twoˇ»
1883 three
1884 four
1885 "});
1886 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1887 cx.assert_editor_state(indoc! {"
1888 «oneˇ» «twoˇ»
1889 three
1890 four
1891 "});
1892
1893 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1894 cx.assert_editor_state(indoc! {"
1895 «oneˇ» «twoˇ»
1896 three
1897 four
1898 "});
1899
1900 // select across line ending
1901 cx.set_state(indoc! {"
1902 one two
1903 t«hree
1904 ˇ» four
1905 "});
1906 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1907 cx.assert_editor_state(indoc! {"
1908 one two
1909 t«hree
1910 ˇ» four
1911 "});
1912
1913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1914 cx.assert_editor_state(indoc! {"
1915 one two
1916 t«hree
1917 ˇ» four
1918 "});
1919
1920 // Ensure that indenting/outdenting works when the cursor is at column 0.
1921 cx.set_state(indoc! {"
1922 one two
1923 ˇthree
1924 four
1925 "});
1926 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1927 cx.assert_editor_state(indoc! {"
1928 one two
1929 ˇthree
1930 four
1931 "});
1932
1933 cx.set_state(indoc! {"
1934 one two
1935 ˇ three
1936 four
1937 "});
1938 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1939 cx.assert_editor_state(indoc! {"
1940 one two
1941 ˇthree
1942 four
1943 "});
1944}
1945
1946#[gpui::test]
1947async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
1948 init_test(cx, |settings| {
1949 settings.defaults.hard_tabs = Some(true);
1950 });
1951
1952 let mut cx = EditorTestContext::new(cx);
1953
1954 // select two ranges on one line
1955 cx.set_state(indoc! {"
1956 «oneˇ» «twoˇ»
1957 three
1958 four
1959 "});
1960 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1961 cx.assert_editor_state(indoc! {"
1962 \t«oneˇ» «twoˇ»
1963 three
1964 four
1965 "});
1966 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1967 cx.assert_editor_state(indoc! {"
1968 \t\t«oneˇ» «twoˇ»
1969 three
1970 four
1971 "});
1972 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1973 cx.assert_editor_state(indoc! {"
1974 \t«oneˇ» «twoˇ»
1975 three
1976 four
1977 "});
1978 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1979 cx.assert_editor_state(indoc! {"
1980 «oneˇ» «twoˇ»
1981 three
1982 four
1983 "});
1984
1985 // select across a line ending
1986 cx.set_state(indoc! {"
1987 one two
1988 t«hree
1989 ˇ»four
1990 "});
1991 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1992 cx.assert_editor_state(indoc! {"
1993 one two
1994 \tt«hree
1995 ˇ»four
1996 "});
1997 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1998 cx.assert_editor_state(indoc! {"
1999 one two
2000 \t\tt«hree
2001 ˇ»four
2002 "});
2003 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2004 cx.assert_editor_state(indoc! {"
2005 one two
2006 \tt«hree
2007 ˇ»four
2008 "});
2009 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2010 cx.assert_editor_state(indoc! {"
2011 one two
2012 t«hree
2013 ˇ»four
2014 "});
2015
2016 // Ensure that indenting/outdenting works when the cursor is at column 0.
2017 cx.set_state(indoc! {"
2018 one two
2019 ˇthree
2020 four
2021 "});
2022 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2023 cx.assert_editor_state(indoc! {"
2024 one two
2025 ˇthree
2026 four
2027 "});
2028 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2029 cx.assert_editor_state(indoc! {"
2030 one two
2031 \tˇthree
2032 four
2033 "});
2034 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2035 cx.assert_editor_state(indoc! {"
2036 one two
2037 ˇthree
2038 four
2039 "});
2040}
2041
2042#[gpui::test]
2043fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2044 init_test(cx, |settings| {
2045 settings.languages.extend([
2046 (
2047 "TOML".into(),
2048 LanguageSettingsContent {
2049 tab_size: NonZeroU32::new(2),
2050 ..Default::default()
2051 },
2052 ),
2053 (
2054 "Rust".into(),
2055 LanguageSettingsContent {
2056 tab_size: NonZeroU32::new(4),
2057 ..Default::default()
2058 },
2059 ),
2060 ]);
2061 });
2062
2063 let toml_language = Arc::new(Language::new(
2064 LanguageConfig {
2065 name: "TOML".into(),
2066 ..Default::default()
2067 },
2068 None,
2069 ));
2070 let rust_language = Arc::new(Language::new(
2071 LanguageConfig {
2072 name: "Rust".into(),
2073 ..Default::default()
2074 },
2075 None,
2076 ));
2077
2078 let toml_buffer =
2079 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2080 let rust_buffer = cx.add_model(|cx| {
2081 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
2082 });
2083 let multibuffer = cx.add_model(|cx| {
2084 let mut multibuffer = MultiBuffer::new(0);
2085 multibuffer.push_excerpts(
2086 toml_buffer.clone(),
2087 [ExcerptRange {
2088 context: Point::new(0, 0)..Point::new(2, 0),
2089 primary: None,
2090 }],
2091 cx,
2092 );
2093 multibuffer.push_excerpts(
2094 rust_buffer.clone(),
2095 [ExcerptRange {
2096 context: Point::new(0, 0)..Point::new(1, 0),
2097 primary: None,
2098 }],
2099 cx,
2100 );
2101 multibuffer
2102 });
2103
2104 cx.add_window(|cx| {
2105 let mut editor = build_editor(multibuffer, cx);
2106
2107 assert_eq!(
2108 editor.text(cx),
2109 indoc! {"
2110 a = 1
2111 b = 2
2112
2113 const c: usize = 3;
2114 "}
2115 );
2116
2117 select_ranges(
2118 &mut editor,
2119 indoc! {"
2120 «aˇ» = 1
2121 b = 2
2122
2123 «const c:ˇ» usize = 3;
2124 "},
2125 cx,
2126 );
2127
2128 editor.tab(&Tab, cx);
2129 assert_text_with_selections(
2130 &mut editor,
2131 indoc! {"
2132 «aˇ» = 1
2133 b = 2
2134
2135 «const c:ˇ» usize = 3;
2136 "},
2137 cx,
2138 );
2139 editor.tab_prev(&TabPrev, cx);
2140 assert_text_with_selections(
2141 &mut editor,
2142 indoc! {"
2143 «aˇ» = 1
2144 b = 2
2145
2146 «const c:ˇ» usize = 3;
2147 "},
2148 cx,
2149 );
2150
2151 editor
2152 });
2153}
2154
2155#[gpui::test]
2156async fn test_backspace(cx: &mut gpui::TestAppContext) {
2157 init_test(cx, |_| {});
2158
2159 let mut cx = EditorTestContext::new(cx);
2160
2161 // Basic backspace
2162 cx.set_state(indoc! {"
2163 onˇe two three
2164 fou«rˇ» five six
2165 seven «ˇeight nine
2166 »ten
2167 "});
2168 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2169 cx.assert_editor_state(indoc! {"
2170 oˇe two three
2171 fouˇ five six
2172 seven ˇten
2173 "});
2174
2175 // Test backspace inside and around indents
2176 cx.set_state(indoc! {"
2177 zero
2178 ˇone
2179 ˇtwo
2180 ˇ ˇ ˇ three
2181 ˇ ˇ four
2182 "});
2183 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2184 cx.assert_editor_state(indoc! {"
2185 zero
2186 ˇone
2187 ˇtwo
2188 ˇ threeˇ four
2189 "});
2190
2191 // Test backspace with line_mode set to true
2192 cx.update_editor(|e, _| e.selections.line_mode = true);
2193 cx.set_state(indoc! {"
2194 The ˇquick ˇbrown
2195 fox jumps over
2196 the lazy dog
2197 ˇThe qu«ick bˇ»rown"});
2198 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2199 cx.assert_editor_state(indoc! {"
2200 ˇfox jumps over
2201 the lazy dogˇ"});
2202}
2203
2204#[gpui::test]
2205async fn test_delete(cx: &mut gpui::TestAppContext) {
2206 init_test(cx, |_| {});
2207
2208 let mut cx = EditorTestContext::new(cx);
2209 cx.set_state(indoc! {"
2210 onˇe two three
2211 fou«rˇ» five six
2212 seven «ˇeight nine
2213 »ten
2214 "});
2215 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2216 cx.assert_editor_state(indoc! {"
2217 onˇ two three
2218 fouˇ five six
2219 seven ˇten
2220 "});
2221
2222 // Test backspace with line_mode set to true
2223 cx.update_editor(|e, _| e.selections.line_mode = true);
2224 cx.set_state(indoc! {"
2225 The ˇquick ˇbrown
2226 fox «ˇjum»ps over
2227 the lazy dog
2228 ˇThe qu«ick bˇ»rown"});
2229 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2230 cx.assert_editor_state("ˇthe lazy dogˇ");
2231}
2232
2233#[gpui::test]
2234fn test_delete_line(cx: &mut TestAppContext) {
2235 init_test(cx, |_| {});
2236
2237 let (_, view) = cx.add_window(|cx| {
2238 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2239 build_editor(buffer, cx)
2240 });
2241 view.update(cx, |view, cx| {
2242 view.change_selections(None, cx, |s| {
2243 s.select_display_ranges([
2244 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2245 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2246 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2247 ])
2248 });
2249 view.delete_line(&DeleteLine, cx);
2250 assert_eq!(view.display_text(cx), "ghi");
2251 assert_eq!(
2252 view.selections.display_ranges(cx),
2253 vec![
2254 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2255 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2256 ]
2257 );
2258 });
2259
2260 let (_, view) = cx.add_window(|cx| {
2261 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2262 build_editor(buffer, cx)
2263 });
2264 view.update(cx, |view, cx| {
2265 view.change_selections(None, cx, |s| {
2266 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2267 });
2268 view.delete_line(&DeleteLine, cx);
2269 assert_eq!(view.display_text(cx), "ghi\n");
2270 assert_eq!(
2271 view.selections.display_ranges(cx),
2272 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2273 );
2274 });
2275}
2276
2277#[gpui::test]
2278fn test_duplicate_line(cx: &mut TestAppContext) {
2279 init_test(cx, |_| {});
2280
2281 let (_, view) = cx.add_window(|cx| {
2282 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2283 build_editor(buffer, cx)
2284 });
2285 view.update(cx, |view, cx| {
2286 view.change_selections(None, cx, |s| {
2287 s.select_display_ranges([
2288 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2289 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2290 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2291 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2292 ])
2293 });
2294 view.duplicate_line(&DuplicateLine, cx);
2295 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2296 assert_eq!(
2297 view.selections.display_ranges(cx),
2298 vec![
2299 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2300 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2301 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2302 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2303 ]
2304 );
2305 });
2306
2307 let (_, view) = cx.add_window(|cx| {
2308 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2309 build_editor(buffer, cx)
2310 });
2311 view.update(cx, |view, cx| {
2312 view.change_selections(None, cx, |s| {
2313 s.select_display_ranges([
2314 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2315 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2316 ])
2317 });
2318 view.duplicate_line(&DuplicateLine, cx);
2319 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2320 assert_eq!(
2321 view.selections.display_ranges(cx),
2322 vec![
2323 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2324 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2325 ]
2326 );
2327 });
2328}
2329
2330#[gpui::test]
2331fn test_move_line_up_down(cx: &mut TestAppContext) {
2332 init_test(cx, |_| {});
2333
2334 let (_, view) = cx.add_window(|cx| {
2335 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2336 build_editor(buffer, cx)
2337 });
2338 view.update(cx, |view, cx| {
2339 view.fold_ranges(
2340 vec![
2341 Point::new(0, 2)..Point::new(1, 2),
2342 Point::new(2, 3)..Point::new(4, 1),
2343 Point::new(7, 0)..Point::new(8, 4),
2344 ],
2345 true,
2346 cx,
2347 );
2348 view.change_selections(None, cx, |s| {
2349 s.select_display_ranges([
2350 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2351 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2352 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2353 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2354 ])
2355 });
2356 assert_eq!(
2357 view.display_text(cx),
2358 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2359 );
2360
2361 view.move_line_up(&MoveLineUp, cx);
2362 assert_eq!(
2363 view.display_text(cx),
2364 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2365 );
2366 assert_eq!(
2367 view.selections.display_ranges(cx),
2368 vec![
2369 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2370 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2371 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2372 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2373 ]
2374 );
2375 });
2376
2377 view.update(cx, |view, cx| {
2378 view.move_line_down(&MoveLineDown, cx);
2379 assert_eq!(
2380 view.display_text(cx),
2381 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2382 );
2383 assert_eq!(
2384 view.selections.display_ranges(cx),
2385 vec![
2386 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2387 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2388 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2389 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2390 ]
2391 );
2392 });
2393
2394 view.update(cx, |view, cx| {
2395 view.move_line_down(&MoveLineDown, cx);
2396 assert_eq!(
2397 view.display_text(cx),
2398 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2399 );
2400 assert_eq!(
2401 view.selections.display_ranges(cx),
2402 vec![
2403 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2404 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2405 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2406 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2407 ]
2408 );
2409 });
2410
2411 view.update(cx, |view, cx| {
2412 view.move_line_up(&MoveLineUp, cx);
2413 assert_eq!(
2414 view.display_text(cx),
2415 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2416 );
2417 assert_eq!(
2418 view.selections.display_ranges(cx),
2419 vec![
2420 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2421 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2422 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2423 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2424 ]
2425 );
2426 });
2427}
2428
2429#[gpui::test]
2430fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2431 init_test(cx, |_| {});
2432
2433 let (_, editor) = cx.add_window(|cx| {
2434 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2435 build_editor(buffer, cx)
2436 });
2437 editor.update(cx, |editor, cx| {
2438 let snapshot = editor.buffer.read(cx).snapshot(cx);
2439 editor.insert_blocks(
2440 [BlockProperties {
2441 style: BlockStyle::Fixed,
2442 position: snapshot.anchor_after(Point::new(2, 0)),
2443 disposition: BlockDisposition::Below,
2444 height: 1,
2445 render: Arc::new(|_| Empty::new().into_any()),
2446 }],
2447 cx,
2448 );
2449 editor.change_selections(None, cx, |s| {
2450 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2451 });
2452 editor.move_line_down(&MoveLineDown, cx);
2453 });
2454}
2455
2456#[gpui::test]
2457fn test_transpose(cx: &mut TestAppContext) {
2458 init_test(cx, |_| {});
2459
2460 _ = cx
2461 .add_window(|cx| {
2462 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2463
2464 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2465 editor.transpose(&Default::default(), cx);
2466 assert_eq!(editor.text(cx), "bac");
2467 assert_eq!(editor.selections.ranges(cx), [2..2]);
2468
2469 editor.transpose(&Default::default(), cx);
2470 assert_eq!(editor.text(cx), "bca");
2471 assert_eq!(editor.selections.ranges(cx), [3..3]);
2472
2473 editor.transpose(&Default::default(), cx);
2474 assert_eq!(editor.text(cx), "bac");
2475 assert_eq!(editor.selections.ranges(cx), [3..3]);
2476
2477 editor
2478 })
2479 .1;
2480
2481 _ = cx
2482 .add_window(|cx| {
2483 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2484
2485 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2486 editor.transpose(&Default::default(), cx);
2487 assert_eq!(editor.text(cx), "acb\nde");
2488 assert_eq!(editor.selections.ranges(cx), [3..3]);
2489
2490 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2491 editor.transpose(&Default::default(), cx);
2492 assert_eq!(editor.text(cx), "acbd\ne");
2493 assert_eq!(editor.selections.ranges(cx), [5..5]);
2494
2495 editor.transpose(&Default::default(), cx);
2496 assert_eq!(editor.text(cx), "acbde\n");
2497 assert_eq!(editor.selections.ranges(cx), [6..6]);
2498
2499 editor.transpose(&Default::default(), cx);
2500 assert_eq!(editor.text(cx), "acbd\ne");
2501 assert_eq!(editor.selections.ranges(cx), [6..6]);
2502
2503 editor
2504 })
2505 .1;
2506
2507 _ = cx
2508 .add_window(|cx| {
2509 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2510
2511 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2512 editor.transpose(&Default::default(), cx);
2513 assert_eq!(editor.text(cx), "bacd\ne");
2514 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2515
2516 editor.transpose(&Default::default(), cx);
2517 assert_eq!(editor.text(cx), "bcade\n");
2518 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2519
2520 editor.transpose(&Default::default(), cx);
2521 assert_eq!(editor.text(cx), "bcda\ne");
2522 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2523
2524 editor.transpose(&Default::default(), cx);
2525 assert_eq!(editor.text(cx), "bcade\n");
2526 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2527
2528 editor.transpose(&Default::default(), cx);
2529 assert_eq!(editor.text(cx), "bcaed\n");
2530 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2531
2532 editor
2533 })
2534 .1;
2535
2536 _ = cx
2537 .add_window(|cx| {
2538 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2539
2540 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2541 editor.transpose(&Default::default(), cx);
2542 assert_eq!(editor.text(cx), "🏀🍐✋");
2543 assert_eq!(editor.selections.ranges(cx), [8..8]);
2544
2545 editor.transpose(&Default::default(), cx);
2546 assert_eq!(editor.text(cx), "🏀✋🍐");
2547 assert_eq!(editor.selections.ranges(cx), [11..11]);
2548
2549 editor.transpose(&Default::default(), cx);
2550 assert_eq!(editor.text(cx), "🏀🍐✋");
2551 assert_eq!(editor.selections.ranges(cx), [11..11]);
2552
2553 editor
2554 })
2555 .1;
2556}
2557
2558#[gpui::test]
2559async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2560 init_test(cx, |_| {});
2561
2562 let mut cx = EditorTestContext::new(cx);
2563
2564 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2565 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2566 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2567
2568 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2569 cx.set_state("two ˇfour ˇsix ˇ");
2570 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2571 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2572
2573 // Paste again but with only two cursors. Since the number of cursors doesn't
2574 // match the number of slices in the clipboard, the entire clipboard text
2575 // is pasted at each cursor.
2576 cx.set_state("ˇtwo one✅ four three six five ˇ");
2577 cx.update_editor(|e, cx| {
2578 e.handle_input("( ", cx);
2579 e.paste(&Paste, cx);
2580 e.handle_input(") ", cx);
2581 });
2582 cx.assert_editor_state(
2583 &([
2584 "( one✅ ",
2585 "three ",
2586 "five ) ˇtwo one✅ four three six five ( one✅ ",
2587 "three ",
2588 "five ) ˇ",
2589 ]
2590 .join("\n")),
2591 );
2592
2593 // Cut with three selections, one of which is full-line.
2594 cx.set_state(indoc! {"
2595 1«2ˇ»3
2596 4ˇ567
2597 «8ˇ»9"});
2598 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2599 cx.assert_editor_state(indoc! {"
2600 1ˇ3
2601 ˇ9"});
2602
2603 // Paste with three selections, noticing how the copied selection that was full-line
2604 // gets inserted before the second cursor.
2605 cx.set_state(indoc! {"
2606 1ˇ3
2607 9ˇ
2608 «oˇ»ne"});
2609 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2610 cx.assert_editor_state(indoc! {"
2611 12ˇ3
2612 4567
2613 9ˇ
2614 8ˇne"});
2615
2616 // Copy with a single cursor only, which writes the whole line into the clipboard.
2617 cx.set_state(indoc! {"
2618 The quick brown
2619 fox juˇmps over
2620 the lazy dog"});
2621 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2622 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2623
2624 // Paste with three selections, noticing how the copied full-line selection is inserted
2625 // before the empty selections but replaces the selection that is non-empty.
2626 cx.set_state(indoc! {"
2627 Tˇhe quick brown
2628 «foˇ»x jumps over
2629 tˇhe lazy dog"});
2630 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2631 cx.assert_editor_state(indoc! {"
2632 fox jumps over
2633 Tˇhe quick brown
2634 fox jumps over
2635 ˇx jumps over
2636 fox jumps over
2637 tˇhe lazy dog"});
2638}
2639
2640#[gpui::test]
2641async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2642 init_test(cx, |_| {});
2643
2644 let mut cx = EditorTestContext::new(cx);
2645 let language = Arc::new(Language::new(
2646 LanguageConfig::default(),
2647 Some(tree_sitter_rust::language()),
2648 ));
2649 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2650
2651 // Cut an indented block, without the leading whitespace.
2652 cx.set_state(indoc! {"
2653 const a: B = (
2654 c(),
2655 «d(
2656 e,
2657 f
2658 )ˇ»
2659 );
2660 "});
2661 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2662 cx.assert_editor_state(indoc! {"
2663 const a: B = (
2664 c(),
2665 ˇ
2666 );
2667 "});
2668
2669 // Paste it at the same position.
2670 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2671 cx.assert_editor_state(indoc! {"
2672 const a: B = (
2673 c(),
2674 d(
2675 e,
2676 f
2677 )ˇ
2678 );
2679 "});
2680
2681 // Paste it at a line with a lower indent level.
2682 cx.set_state(indoc! {"
2683 ˇ
2684 const a: B = (
2685 c(),
2686 );
2687 "});
2688 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2689 cx.assert_editor_state(indoc! {"
2690 d(
2691 e,
2692 f
2693 )ˇ
2694 const a: B = (
2695 c(),
2696 );
2697 "});
2698
2699 // Cut an indented block, with the leading whitespace.
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(),
2703 « d(
2704 e,
2705 f
2706 )
2707 ˇ»);
2708 "});
2709 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2710 cx.assert_editor_state(indoc! {"
2711 const a: B = (
2712 c(),
2713 ˇ);
2714 "});
2715
2716 // Paste it at the same position.
2717 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2718 cx.assert_editor_state(indoc! {"
2719 const a: B = (
2720 c(),
2721 d(
2722 e,
2723 f
2724 )
2725 ˇ);
2726 "});
2727
2728 // Paste it at a line with a higher indent level.
2729 cx.set_state(indoc! {"
2730 const a: B = (
2731 c(),
2732 d(
2733 e,
2734 fˇ
2735 )
2736 );
2737 "});
2738 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2739 cx.assert_editor_state(indoc! {"
2740 const a: B = (
2741 c(),
2742 d(
2743 e,
2744 f d(
2745 e,
2746 f
2747 )
2748 ˇ
2749 )
2750 );
2751 "});
2752}
2753
2754#[gpui::test]
2755fn test_select_all(cx: &mut TestAppContext) {
2756 init_test(cx, |_| {});
2757
2758 let (_, view) = cx.add_window(|cx| {
2759 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2760 build_editor(buffer, cx)
2761 });
2762 view.update(cx, |view, cx| {
2763 view.select_all(&SelectAll, cx);
2764 assert_eq!(
2765 view.selections.display_ranges(cx),
2766 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2767 );
2768 });
2769}
2770
2771#[gpui::test]
2772fn test_select_line(cx: &mut TestAppContext) {
2773 init_test(cx, |_| {});
2774
2775 let (_, view) = cx.add_window(|cx| {
2776 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2777 build_editor(buffer, cx)
2778 });
2779 view.update(cx, |view, cx| {
2780 view.change_selections(None, cx, |s| {
2781 s.select_display_ranges([
2782 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2783 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2784 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2785 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2786 ])
2787 });
2788 view.select_line(&SelectLine, cx);
2789 assert_eq!(
2790 view.selections.display_ranges(cx),
2791 vec![
2792 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2793 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2794 ]
2795 );
2796 });
2797
2798 view.update(cx, |view, cx| {
2799 view.select_line(&SelectLine, cx);
2800 assert_eq!(
2801 view.selections.display_ranges(cx),
2802 vec![
2803 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2804 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2805 ]
2806 );
2807 });
2808
2809 view.update(cx, |view, cx| {
2810 view.select_line(&SelectLine, cx);
2811 assert_eq!(
2812 view.selections.display_ranges(cx),
2813 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2814 );
2815 });
2816}
2817
2818#[gpui::test]
2819fn test_split_selection_into_lines(cx: &mut TestAppContext) {
2820 init_test(cx, |_| {});
2821
2822 let (_, view) = cx.add_window(|cx| {
2823 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2824 build_editor(buffer, cx)
2825 });
2826 view.update(cx, |view, cx| {
2827 view.fold_ranges(
2828 vec![
2829 Point::new(0, 2)..Point::new(1, 2),
2830 Point::new(2, 3)..Point::new(4, 1),
2831 Point::new(7, 0)..Point::new(8, 4),
2832 ],
2833 true,
2834 cx,
2835 );
2836 view.change_selections(None, cx, |s| {
2837 s.select_display_ranges([
2838 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2839 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2840 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2841 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2842 ])
2843 });
2844 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
2845 });
2846
2847 view.update(cx, |view, cx| {
2848 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2849 assert_eq!(
2850 view.display_text(cx),
2851 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
2852 );
2853 assert_eq!(
2854 view.selections.display_ranges(cx),
2855 [
2856 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2857 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2858 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2859 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2860 ]
2861 );
2862 });
2863
2864 view.update(cx, |view, cx| {
2865 view.change_selections(None, cx, |s| {
2866 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2867 });
2868 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2869 assert_eq!(
2870 view.display_text(cx),
2871 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2872 );
2873 assert_eq!(
2874 view.selections.display_ranges(cx),
2875 [
2876 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2877 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2878 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2879 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2880 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2881 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2882 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2883 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2884 ]
2885 );
2886 });
2887}
2888
2889#[gpui::test]
2890fn test_add_selection_above_below(cx: &mut TestAppContext) {
2891 init_test(cx, |_| {});
2892
2893 let (_, view) = cx.add_window(|cx| {
2894 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2895 build_editor(buffer, cx)
2896 });
2897
2898 view.update(cx, |view, cx| {
2899 view.change_selections(None, cx, |s| {
2900 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2901 });
2902 });
2903 view.update(cx, |view, cx| {
2904 view.add_selection_above(&AddSelectionAbove, cx);
2905 assert_eq!(
2906 view.selections.display_ranges(cx),
2907 vec![
2908 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2909 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2910 ]
2911 );
2912 });
2913
2914 view.update(cx, |view, cx| {
2915 view.add_selection_above(&AddSelectionAbove, cx);
2916 assert_eq!(
2917 view.selections.display_ranges(cx),
2918 vec![
2919 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2920 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2921 ]
2922 );
2923 });
2924
2925 view.update(cx, |view, cx| {
2926 view.add_selection_below(&AddSelectionBelow, cx);
2927 assert_eq!(
2928 view.selections.display_ranges(cx),
2929 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2930 );
2931
2932 view.undo_selection(&UndoSelection, cx);
2933 assert_eq!(
2934 view.selections.display_ranges(cx),
2935 vec![
2936 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2937 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2938 ]
2939 );
2940
2941 view.redo_selection(&RedoSelection, cx);
2942 assert_eq!(
2943 view.selections.display_ranges(cx),
2944 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2945 );
2946 });
2947
2948 view.update(cx, |view, cx| {
2949 view.add_selection_below(&AddSelectionBelow, cx);
2950 assert_eq!(
2951 view.selections.display_ranges(cx),
2952 vec![
2953 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2954 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
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, 3),
2965 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2966 ]
2967 );
2968 });
2969
2970 view.update(cx, |view, cx| {
2971 view.change_selections(None, cx, |s| {
2972 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
2973 });
2974 });
2975 view.update(cx, |view, cx| {
2976 view.add_selection_below(&AddSelectionBelow, cx);
2977 assert_eq!(
2978 view.selections.display_ranges(cx),
2979 vec![
2980 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2981 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2982 ]
2983 );
2984 });
2985
2986 view.update(cx, |view, cx| {
2987 view.add_selection_below(&AddSelectionBelow, cx);
2988 assert_eq!(
2989 view.selections.display_ranges(cx),
2990 vec![
2991 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2992 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2993 ]
2994 );
2995 });
2996
2997 view.update(cx, |view, cx| {
2998 view.add_selection_above(&AddSelectionAbove, cx);
2999 assert_eq!(
3000 view.selections.display_ranges(cx),
3001 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3002 );
3003 });
3004
3005 view.update(cx, |view, cx| {
3006 view.add_selection_above(&AddSelectionAbove, cx);
3007 assert_eq!(
3008 view.selections.display_ranges(cx),
3009 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3010 );
3011 });
3012
3013 view.update(cx, |view, cx| {
3014 view.change_selections(None, cx, |s| {
3015 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3016 });
3017 view.add_selection_below(&AddSelectionBelow, cx);
3018 assert_eq!(
3019 view.selections.display_ranges(cx),
3020 vec![
3021 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3022 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3023 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3024 ]
3025 );
3026 });
3027
3028 view.update(cx, |view, cx| {
3029 view.add_selection_below(&AddSelectionBelow, cx);
3030 assert_eq!(
3031 view.selections.display_ranges(cx),
3032 vec![
3033 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3034 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3035 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3036 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3037 ]
3038 );
3039 });
3040
3041 view.update(cx, |view, cx| {
3042 view.add_selection_above(&AddSelectionAbove, cx);
3043 assert_eq!(
3044 view.selections.display_ranges(cx),
3045 vec![
3046 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3047 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3048 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3049 ]
3050 );
3051 });
3052
3053 view.update(cx, |view, cx| {
3054 view.change_selections(None, cx, |s| {
3055 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3056 });
3057 });
3058 view.update(cx, |view, cx| {
3059 view.add_selection_above(&AddSelectionAbove, cx);
3060 assert_eq!(
3061 view.selections.display_ranges(cx),
3062 vec![
3063 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3064 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3065 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3066 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3067 ]
3068 );
3069 });
3070
3071 view.update(cx, |view, cx| {
3072 view.add_selection_below(&AddSelectionBelow, cx);
3073 assert_eq!(
3074 view.selections.display_ranges(cx),
3075 vec![
3076 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3077 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3078 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3079 ]
3080 );
3081 });
3082}
3083
3084#[gpui::test]
3085async fn test_select_next(cx: &mut gpui::TestAppContext) {
3086 init_test(cx, |_| {});
3087
3088 let mut cx = EditorTestContext::new(cx);
3089 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3090
3091 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3092 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3093
3094 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3095 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3096
3097 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3098 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3099
3100 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3101 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3102
3103 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3104 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3105
3106 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3107 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3108}
3109
3110#[gpui::test]
3111async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3112 init_test(cx, |_| {});
3113
3114 let language = Arc::new(Language::new(
3115 LanguageConfig::default(),
3116 Some(tree_sitter_rust::language()),
3117 ));
3118
3119 let text = r#"
3120 use mod1::mod2::{mod3, mod4};
3121
3122 fn fn_1(param1: bool, param2: &str) {
3123 let var1 = "text";
3124 }
3125 "#
3126 .unindent();
3127
3128 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3129 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3130 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3131 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3132 .await;
3133
3134 view.update(cx, |view, cx| {
3135 view.change_selections(None, cx, |s| {
3136 s.select_display_ranges([
3137 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3138 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3139 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3140 ]);
3141 });
3142 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3143 });
3144 assert_eq!(
3145 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3146 &[
3147 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3148 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3149 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3150 ]
3151 );
3152
3153 view.update(cx, |view, cx| {
3154 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3155 });
3156 assert_eq!(
3157 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3158 &[
3159 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3160 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3161 ]
3162 );
3163
3164 view.update(cx, |view, cx| {
3165 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3166 });
3167 assert_eq!(
3168 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3169 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3170 );
3171
3172 // Trying to expand the selected syntax node one more time has no effect.
3173 view.update(cx, |view, cx| {
3174 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3175 });
3176 assert_eq!(
3177 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3178 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3179 );
3180
3181 view.update(cx, |view, cx| {
3182 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3183 });
3184 assert_eq!(
3185 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3186 &[
3187 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3188 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3189 ]
3190 );
3191
3192 view.update(cx, |view, cx| {
3193 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3194 });
3195 assert_eq!(
3196 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3197 &[
3198 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3199 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3200 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3201 ]
3202 );
3203
3204 view.update(cx, |view, cx| {
3205 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3206 });
3207 assert_eq!(
3208 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3209 &[
3210 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3211 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3212 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3213 ]
3214 );
3215
3216 // Trying to shrink the selected syntax node one more time has no effect.
3217 view.update(cx, |view, cx| {
3218 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3219 });
3220 assert_eq!(
3221 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3222 &[
3223 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3224 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3225 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3226 ]
3227 );
3228
3229 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3230 // a fold.
3231 view.update(cx, |view, cx| {
3232 view.fold_ranges(
3233 vec![
3234 Point::new(0, 21)..Point::new(0, 24),
3235 Point::new(3, 20)..Point::new(3, 22),
3236 ],
3237 true,
3238 cx,
3239 );
3240 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3241 });
3242 assert_eq!(
3243 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3244 &[
3245 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3246 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3247 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3248 ]
3249 );
3250}
3251
3252#[gpui::test]
3253async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3254 init_test(cx, |_| {});
3255
3256 let language = Arc::new(
3257 Language::new(
3258 LanguageConfig {
3259 brackets: BracketPairConfig {
3260 pairs: vec![
3261 BracketPair {
3262 start: "{".to_string(),
3263 end: "}".to_string(),
3264 close: false,
3265 newline: true,
3266 },
3267 BracketPair {
3268 start: "(".to_string(),
3269 end: ")".to_string(),
3270 close: false,
3271 newline: true,
3272 },
3273 ],
3274 ..Default::default()
3275 },
3276 ..Default::default()
3277 },
3278 Some(tree_sitter_rust::language()),
3279 )
3280 .with_indents_query(
3281 r#"
3282 (_ "(" ")" @end) @indent
3283 (_ "{" "}" @end) @indent
3284 "#,
3285 )
3286 .unwrap(),
3287 );
3288
3289 let text = "fn a() {}";
3290
3291 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3292 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3293 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3294 editor
3295 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3296 .await;
3297
3298 editor.update(cx, |editor, cx| {
3299 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3300 editor.newline(&Newline, cx);
3301 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3302 assert_eq!(
3303 editor.selections.ranges(cx),
3304 &[
3305 Point::new(1, 4)..Point::new(1, 4),
3306 Point::new(3, 4)..Point::new(3, 4),
3307 Point::new(5, 0)..Point::new(5, 0)
3308 ]
3309 );
3310 });
3311}
3312
3313#[gpui::test]
3314async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3315 init_test(cx, |_| {});
3316
3317 let mut cx = EditorTestContext::new(cx);
3318
3319 let language = Arc::new(Language::new(
3320 LanguageConfig {
3321 brackets: BracketPairConfig {
3322 pairs: vec![
3323 BracketPair {
3324 start: "{".to_string(),
3325 end: "}".to_string(),
3326 close: true,
3327 newline: true,
3328 },
3329 BracketPair {
3330 start: "(".to_string(),
3331 end: ")".to_string(),
3332 close: true,
3333 newline: true,
3334 },
3335 BracketPair {
3336 start: "/*".to_string(),
3337 end: " */".to_string(),
3338 close: true,
3339 newline: true,
3340 },
3341 BracketPair {
3342 start: "[".to_string(),
3343 end: "]".to_string(),
3344 close: false,
3345 newline: true,
3346 },
3347 BracketPair {
3348 start: "\"".to_string(),
3349 end: "\"".to_string(),
3350 close: true,
3351 newline: false,
3352 },
3353 ],
3354 ..Default::default()
3355 },
3356 autoclose_before: "})]".to_string(),
3357 ..Default::default()
3358 },
3359 Some(tree_sitter_rust::language()),
3360 ));
3361
3362 let registry = Arc::new(LanguageRegistry::test());
3363 registry.add(language.clone());
3364 cx.update_buffer(|buffer, cx| {
3365 buffer.set_language_registry(registry);
3366 buffer.set_language(Some(language), cx);
3367 });
3368
3369 cx.set_state(
3370 &r#"
3371 🏀ˇ
3372 εˇ
3373 ❤️ˇ
3374 "#
3375 .unindent(),
3376 );
3377
3378 // autoclose multiple nested brackets at multiple cursors
3379 cx.update_editor(|view, cx| {
3380 view.handle_input("{", cx);
3381 view.handle_input("{", cx);
3382 view.handle_input("{", cx);
3383 });
3384 cx.assert_editor_state(
3385 &"
3386 🏀{{{ˇ}}}
3387 ε{{{ˇ}}}
3388 ❤️{{{ˇ}}}
3389 "
3390 .unindent(),
3391 );
3392
3393 // insert a different closing bracket
3394 cx.update_editor(|view, cx| {
3395 view.handle_input(")", cx);
3396 });
3397 cx.assert_editor_state(
3398 &"
3399 🏀{{{)ˇ}}}
3400 ε{{{)ˇ}}}
3401 ❤️{{{)ˇ}}}
3402 "
3403 .unindent(),
3404 );
3405
3406 // skip over the auto-closed brackets when typing a closing bracket
3407 cx.update_editor(|view, cx| {
3408 view.move_right(&MoveRight, cx);
3409 view.handle_input("}", cx);
3410 view.handle_input("}", cx);
3411 view.handle_input("}", cx);
3412 });
3413 cx.assert_editor_state(
3414 &"
3415 🏀{{{)}}}}ˇ
3416 ε{{{)}}}}ˇ
3417 ❤️{{{)}}}}ˇ
3418 "
3419 .unindent(),
3420 );
3421
3422 // autoclose multi-character pairs
3423 cx.set_state(
3424 &"
3425 ˇ
3426 ˇ
3427 "
3428 .unindent(),
3429 );
3430 cx.update_editor(|view, cx| {
3431 view.handle_input("/", cx);
3432 view.handle_input("*", cx);
3433 });
3434 cx.assert_editor_state(
3435 &"
3436 /*ˇ */
3437 /*ˇ */
3438 "
3439 .unindent(),
3440 );
3441
3442 // one cursor autocloses a multi-character pair, one cursor
3443 // does not autoclose.
3444 cx.set_state(
3445 &"
3446 /ˇ
3447 ˇ
3448 "
3449 .unindent(),
3450 );
3451 cx.update_editor(|view, cx| view.handle_input("*", cx));
3452 cx.assert_editor_state(
3453 &"
3454 /*ˇ */
3455 *ˇ
3456 "
3457 .unindent(),
3458 );
3459
3460 // Don't autoclose if the next character isn't whitespace and isn't
3461 // listed in the language's "autoclose_before" section.
3462 cx.set_state("ˇa b");
3463 cx.update_editor(|view, cx| view.handle_input("{", cx));
3464 cx.assert_editor_state("{ˇa b");
3465
3466 // Don't autoclose if `close` is false for the bracket pair
3467 cx.set_state("ˇ");
3468 cx.update_editor(|view, cx| view.handle_input("[", cx));
3469 cx.assert_editor_state("[ˇ");
3470
3471 // Surround with brackets if text is selected
3472 cx.set_state("«aˇ» b");
3473 cx.update_editor(|view, cx| view.handle_input("{", cx));
3474 cx.assert_editor_state("{«aˇ»} b");
3475
3476 // Autclose pair where the start and end characters are the same
3477 cx.set_state("aˇ");
3478 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3479 cx.assert_editor_state("a\"ˇ\"");
3480 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3481 cx.assert_editor_state("a\"\"ˇ");
3482}
3483
3484#[gpui::test]
3485async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3486 init_test(cx, |_| {});
3487
3488 let mut cx = EditorTestContext::new(cx);
3489
3490 let html_language = Arc::new(
3491 Language::new(
3492 LanguageConfig {
3493 name: "HTML".into(),
3494 brackets: BracketPairConfig {
3495 pairs: vec![
3496 BracketPair {
3497 start: "<".into(),
3498 end: ">".into(),
3499 close: true,
3500 ..Default::default()
3501 },
3502 BracketPair {
3503 start: "{".into(),
3504 end: "}".into(),
3505 close: true,
3506 ..Default::default()
3507 },
3508 BracketPair {
3509 start: "(".into(),
3510 end: ")".into(),
3511 close: true,
3512 ..Default::default()
3513 },
3514 ],
3515 ..Default::default()
3516 },
3517 autoclose_before: "})]>".into(),
3518 ..Default::default()
3519 },
3520 Some(tree_sitter_html::language()),
3521 )
3522 .with_injection_query(
3523 r#"
3524 (script_element
3525 (raw_text) @content
3526 (#set! "language" "javascript"))
3527 "#,
3528 )
3529 .unwrap(),
3530 );
3531
3532 let javascript_language = Arc::new(Language::new(
3533 LanguageConfig {
3534 name: "JavaScript".into(),
3535 brackets: BracketPairConfig {
3536 pairs: vec![
3537 BracketPair {
3538 start: "/*".into(),
3539 end: " */".into(),
3540 close: true,
3541 ..Default::default()
3542 },
3543 BracketPair {
3544 start: "{".into(),
3545 end: "}".into(),
3546 close: true,
3547 ..Default::default()
3548 },
3549 BracketPair {
3550 start: "(".into(),
3551 end: ")".into(),
3552 close: true,
3553 ..Default::default()
3554 },
3555 ],
3556 ..Default::default()
3557 },
3558 autoclose_before: "})]>".into(),
3559 ..Default::default()
3560 },
3561 Some(tree_sitter_javascript::language()),
3562 ));
3563
3564 let registry = Arc::new(LanguageRegistry::test());
3565 registry.add(html_language.clone());
3566 registry.add(javascript_language.clone());
3567
3568 cx.update_buffer(|buffer, cx| {
3569 buffer.set_language_registry(registry);
3570 buffer.set_language(Some(html_language), cx);
3571 });
3572
3573 cx.set_state(
3574 &r#"
3575 <body>ˇ
3576 <script>
3577 var x = 1;ˇ
3578 </script>
3579 </body>ˇ
3580 "#
3581 .unindent(),
3582 );
3583
3584 // Precondition: different languages are active at different locations.
3585 cx.update_editor(|editor, cx| {
3586 let snapshot = editor.snapshot(cx);
3587 let cursors = editor.selections.ranges::<usize>(cx);
3588 let languages = cursors
3589 .iter()
3590 .map(|c| snapshot.language_at(c.start).unwrap().name())
3591 .collect::<Vec<_>>();
3592 assert_eq!(
3593 languages,
3594 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3595 );
3596 });
3597
3598 // Angle brackets autoclose in HTML, but not JavaScript.
3599 cx.update_editor(|editor, cx| {
3600 editor.handle_input("<", cx);
3601 editor.handle_input("a", cx);
3602 });
3603 cx.assert_editor_state(
3604 &r#"
3605 <body><aˇ>
3606 <script>
3607 var x = 1;<aˇ
3608 </script>
3609 </body><aˇ>
3610 "#
3611 .unindent(),
3612 );
3613
3614 // Curly braces and parens autoclose in both HTML and JavaScript.
3615 cx.update_editor(|editor, cx| {
3616 editor.handle_input(" b=", cx);
3617 editor.handle_input("{", cx);
3618 editor.handle_input("c", cx);
3619 editor.handle_input("(", cx);
3620 });
3621 cx.assert_editor_state(
3622 &r#"
3623 <body><a b={c(ˇ)}>
3624 <script>
3625 var x = 1;<a b={c(ˇ)}
3626 </script>
3627 </body><a b={c(ˇ)}>
3628 "#
3629 .unindent(),
3630 );
3631
3632 // Brackets that were already autoclosed are skipped.
3633 cx.update_editor(|editor, cx| {
3634 editor.handle_input(")", cx);
3635 editor.handle_input("d", cx);
3636 editor.handle_input("}", cx);
3637 });
3638 cx.assert_editor_state(
3639 &r#"
3640 <body><a b={c()d}ˇ>
3641 <script>
3642 var x = 1;<a b={c()d}ˇ
3643 </script>
3644 </body><a b={c()d}ˇ>
3645 "#
3646 .unindent(),
3647 );
3648 cx.update_editor(|editor, cx| {
3649 editor.handle_input(">", cx);
3650 });
3651 cx.assert_editor_state(
3652 &r#"
3653 <body><a b={c()d}>ˇ
3654 <script>
3655 var x = 1;<a b={c()d}>ˇ
3656 </script>
3657 </body><a b={c()d}>ˇ
3658 "#
3659 .unindent(),
3660 );
3661
3662 // Reset
3663 cx.set_state(
3664 &r#"
3665 <body>ˇ
3666 <script>
3667 var x = 1;ˇ
3668 </script>
3669 </body>ˇ
3670 "#
3671 .unindent(),
3672 );
3673
3674 cx.update_editor(|editor, cx| {
3675 editor.handle_input("<", cx);
3676 });
3677 cx.assert_editor_state(
3678 &r#"
3679 <body><ˇ>
3680 <script>
3681 var x = 1;<ˇ
3682 </script>
3683 </body><ˇ>
3684 "#
3685 .unindent(),
3686 );
3687
3688 // When backspacing, the closing angle brackets are removed.
3689 cx.update_editor(|editor, cx| {
3690 editor.backspace(&Backspace, cx);
3691 });
3692 cx.assert_editor_state(
3693 &r#"
3694 <body>ˇ
3695 <script>
3696 var x = 1;ˇ
3697 </script>
3698 </body>ˇ
3699 "#
3700 .unindent(),
3701 );
3702
3703 // Block comments autoclose in JavaScript, but not HTML.
3704 cx.update_editor(|editor, cx| {
3705 editor.handle_input("/", cx);
3706 editor.handle_input("*", cx);
3707 });
3708 cx.assert_editor_state(
3709 &r#"
3710 <body>/*ˇ
3711 <script>
3712 var x = 1;/*ˇ */
3713 </script>
3714 </body>/*ˇ
3715 "#
3716 .unindent(),
3717 );
3718}
3719
3720#[gpui::test]
3721async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
3722 init_test(cx, |_| {});
3723
3724 let mut cx = EditorTestContext::new(cx);
3725
3726 let rust_language = Arc::new(
3727 Language::new(
3728 LanguageConfig {
3729 name: "Rust".into(),
3730 brackets: serde_json::from_value(json!([
3731 { "start": "{", "end": "}", "close": true, "newline": true },
3732 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
3733 ]))
3734 .unwrap(),
3735 autoclose_before: "})]>".into(),
3736 ..Default::default()
3737 },
3738 Some(tree_sitter_rust::language()),
3739 )
3740 .with_override_query("(string_literal) @string")
3741 .unwrap(),
3742 );
3743
3744 let registry = Arc::new(LanguageRegistry::test());
3745 registry.add(rust_language.clone());
3746
3747 cx.update_buffer(|buffer, cx| {
3748 buffer.set_language_registry(registry);
3749 buffer.set_language(Some(rust_language), cx);
3750 });
3751
3752 cx.set_state(
3753 &r#"
3754 let x = ˇ
3755 "#
3756 .unindent(),
3757 );
3758
3759 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
3760 cx.update_editor(|editor, cx| {
3761 editor.handle_input("\"", cx);
3762 });
3763 cx.assert_editor_state(
3764 &r#"
3765 let x = "ˇ"
3766 "#
3767 .unindent(),
3768 );
3769
3770 // Inserting another quotation mark. The cursor moves across the existing
3771 // automatically-inserted quotation mark.
3772 cx.update_editor(|editor, cx| {
3773 editor.handle_input("\"", cx);
3774 });
3775 cx.assert_editor_state(
3776 &r#"
3777 let x = ""ˇ
3778 "#
3779 .unindent(),
3780 );
3781
3782 // Reset
3783 cx.set_state(
3784 &r#"
3785 let x = ˇ
3786 "#
3787 .unindent(),
3788 );
3789
3790 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
3791 cx.update_editor(|editor, cx| {
3792 editor.handle_input("\"", cx);
3793 editor.handle_input(" ", cx);
3794 editor.move_left(&Default::default(), cx);
3795 editor.handle_input("\\", cx);
3796 editor.handle_input("\"", cx);
3797 });
3798 cx.assert_editor_state(
3799 &r#"
3800 let x = "\"ˇ "
3801 "#
3802 .unindent(),
3803 );
3804
3805 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
3806 // mark. Nothing is inserted.
3807 cx.update_editor(|editor, cx| {
3808 editor.move_right(&Default::default(), cx);
3809 editor.handle_input("\"", cx);
3810 });
3811 cx.assert_editor_state(
3812 &r#"
3813 let x = "\" "ˇ
3814 "#
3815 .unindent(),
3816 );
3817}
3818
3819#[gpui::test]
3820async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3821 init_test(cx, |_| {});
3822
3823 let language = Arc::new(Language::new(
3824 LanguageConfig {
3825 brackets: BracketPairConfig {
3826 pairs: vec![
3827 BracketPair {
3828 start: "{".to_string(),
3829 end: "}".to_string(),
3830 close: true,
3831 newline: true,
3832 },
3833 BracketPair {
3834 start: "/* ".to_string(),
3835 end: "*/".to_string(),
3836 close: true,
3837 ..Default::default()
3838 },
3839 ],
3840 ..Default::default()
3841 },
3842 ..Default::default()
3843 },
3844 Some(tree_sitter_rust::language()),
3845 ));
3846
3847 let text = r#"
3848 a
3849 b
3850 c
3851 "#
3852 .unindent();
3853
3854 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3855 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3856 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3857 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3858 .await;
3859
3860 view.update(cx, |view, cx| {
3861 view.change_selections(None, cx, |s| {
3862 s.select_display_ranges([
3863 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3864 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3865 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3866 ])
3867 });
3868
3869 view.handle_input("{", cx);
3870 view.handle_input("{", cx);
3871 view.handle_input("{", cx);
3872 assert_eq!(
3873 view.text(cx),
3874 "
3875 {{{a}}}
3876 {{{b}}}
3877 {{{c}}}
3878 "
3879 .unindent()
3880 );
3881 assert_eq!(
3882 view.selections.display_ranges(cx),
3883 [
3884 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3885 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3886 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3887 ]
3888 );
3889
3890 view.undo(&Undo, cx);
3891 view.undo(&Undo, cx);
3892 view.undo(&Undo, cx);
3893 assert_eq!(
3894 view.text(cx),
3895 "
3896 a
3897 b
3898 c
3899 "
3900 .unindent()
3901 );
3902 assert_eq!(
3903 view.selections.display_ranges(cx),
3904 [
3905 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3906 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3907 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3908 ]
3909 );
3910
3911 // Ensure inserting the first character of a multi-byte bracket pair
3912 // doesn't surround the selections with the bracket.
3913 view.handle_input("/", cx);
3914 assert_eq!(
3915 view.text(cx),
3916 "
3917 /
3918 /
3919 /
3920 "
3921 .unindent()
3922 );
3923 assert_eq!(
3924 view.selections.display_ranges(cx),
3925 [
3926 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3927 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3928 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
3929 ]
3930 );
3931
3932 view.undo(&Undo, cx);
3933 assert_eq!(
3934 view.text(cx),
3935 "
3936 a
3937 b
3938 c
3939 "
3940 .unindent()
3941 );
3942 assert_eq!(
3943 view.selections.display_ranges(cx),
3944 [
3945 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3946 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3947 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3948 ]
3949 );
3950
3951 // Ensure inserting the last character of a multi-byte bracket pair
3952 // doesn't surround the selections with the bracket.
3953 view.handle_input("*", cx);
3954 assert_eq!(
3955 view.text(cx),
3956 "
3957 *
3958 *
3959 *
3960 "
3961 .unindent()
3962 );
3963 assert_eq!(
3964 view.selections.display_ranges(cx),
3965 [
3966 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3967 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3968 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
3969 ]
3970 );
3971 });
3972}
3973
3974#[gpui::test]
3975async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3976 init_test(cx, |_| {});
3977
3978 let language = Arc::new(Language::new(
3979 LanguageConfig {
3980 brackets: BracketPairConfig {
3981 pairs: vec![BracketPair {
3982 start: "{".to_string(),
3983 end: "}".to_string(),
3984 close: true,
3985 newline: true,
3986 }],
3987 ..Default::default()
3988 },
3989 autoclose_before: "}".to_string(),
3990 ..Default::default()
3991 },
3992 Some(tree_sitter_rust::language()),
3993 ));
3994
3995 let text = r#"
3996 a
3997 b
3998 c
3999 "#
4000 .unindent();
4001
4002 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4003 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4004 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4005 editor
4006 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4007 .await;
4008
4009 editor.update(cx, |editor, cx| {
4010 editor.change_selections(None, cx, |s| {
4011 s.select_ranges([
4012 Point::new(0, 1)..Point::new(0, 1),
4013 Point::new(1, 1)..Point::new(1, 1),
4014 Point::new(2, 1)..Point::new(2, 1),
4015 ])
4016 });
4017
4018 editor.handle_input("{", cx);
4019 editor.handle_input("{", cx);
4020 editor.handle_input("_", cx);
4021 assert_eq!(
4022 editor.text(cx),
4023 "
4024 a{{_}}
4025 b{{_}}
4026 c{{_}}
4027 "
4028 .unindent()
4029 );
4030 assert_eq!(
4031 editor.selections.ranges::<Point>(cx),
4032 [
4033 Point::new(0, 4)..Point::new(0, 4),
4034 Point::new(1, 4)..Point::new(1, 4),
4035 Point::new(2, 4)..Point::new(2, 4)
4036 ]
4037 );
4038
4039 editor.backspace(&Default::default(), cx);
4040 editor.backspace(&Default::default(), cx);
4041 assert_eq!(
4042 editor.text(cx),
4043 "
4044 a{}
4045 b{}
4046 c{}
4047 "
4048 .unindent()
4049 );
4050 assert_eq!(
4051 editor.selections.ranges::<Point>(cx),
4052 [
4053 Point::new(0, 2)..Point::new(0, 2),
4054 Point::new(1, 2)..Point::new(1, 2),
4055 Point::new(2, 2)..Point::new(2, 2)
4056 ]
4057 );
4058
4059 editor.delete_to_previous_word_start(&Default::default(), cx);
4060 assert_eq!(
4061 editor.text(cx),
4062 "
4063 a
4064 b
4065 c
4066 "
4067 .unindent()
4068 );
4069 assert_eq!(
4070 editor.selections.ranges::<Point>(cx),
4071 [
4072 Point::new(0, 1)..Point::new(0, 1),
4073 Point::new(1, 1)..Point::new(1, 1),
4074 Point::new(2, 1)..Point::new(2, 1)
4075 ]
4076 );
4077 });
4078}
4079
4080#[gpui::test]
4081async fn test_snippets(cx: &mut gpui::TestAppContext) {
4082 init_test(cx, |_| {});
4083
4084 let (text, insertion_ranges) = marked_text_ranges(
4085 indoc! {"
4086 a.ˇ b
4087 a.ˇ b
4088 a.ˇ b
4089 "},
4090 false,
4091 );
4092
4093 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4094 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4095
4096 editor.update(cx, |editor, cx| {
4097 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4098
4099 editor
4100 .insert_snippet(&insertion_ranges, snippet, cx)
4101 .unwrap();
4102
4103 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4104 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4105 assert_eq!(editor.text(cx), expected_text);
4106 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4107 }
4108
4109 assert(
4110 editor,
4111 cx,
4112 indoc! {"
4113 a.f(«one», two, «three») b
4114 a.f(«one», two, «three») b
4115 a.f(«one», two, «three») b
4116 "},
4117 );
4118
4119 // Can't move earlier than the first tab stop
4120 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4121 assert(
4122 editor,
4123 cx,
4124 indoc! {"
4125 a.f(«one», two, «three») b
4126 a.f(«one», two, «three») b
4127 a.f(«one», two, «three») b
4128 "},
4129 );
4130
4131 assert!(editor.move_to_next_snippet_tabstop(cx));
4132 assert(
4133 editor,
4134 cx,
4135 indoc! {"
4136 a.f(one, «two», three) b
4137 a.f(one, «two», three) b
4138 a.f(one, «two», three) b
4139 "},
4140 );
4141
4142 editor.move_to_prev_snippet_tabstop(cx);
4143 assert(
4144 editor,
4145 cx,
4146 indoc! {"
4147 a.f(«one», two, «three») b
4148 a.f(«one», two, «three») b
4149 a.f(«one», two, «three») b
4150 "},
4151 );
4152
4153 assert!(editor.move_to_next_snippet_tabstop(cx));
4154 assert(
4155 editor,
4156 cx,
4157 indoc! {"
4158 a.f(one, «two», three) b
4159 a.f(one, «two», three) b
4160 a.f(one, «two», three) b
4161 "},
4162 );
4163 assert!(editor.move_to_next_snippet_tabstop(cx));
4164 assert(
4165 editor,
4166 cx,
4167 indoc! {"
4168 a.f(one, two, three)ˇ b
4169 a.f(one, two, three)ˇ b
4170 a.f(one, two, three)ˇ b
4171 "},
4172 );
4173
4174 // As soon as the last tab stop is reached, snippet state is gone
4175 editor.move_to_prev_snippet_tabstop(cx);
4176 assert(
4177 editor,
4178 cx,
4179 indoc! {"
4180 a.f(one, two, three)ˇ b
4181 a.f(one, two, three)ˇ b
4182 a.f(one, two, three)ˇ b
4183 "},
4184 );
4185 });
4186}
4187
4188#[gpui::test]
4189async fn test_document_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_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::Formatting, _, _>(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::Formatting, _, _>(move |params, _| async move {
4256 assert_eq!(
4257 params.text_document.uri,
4258 lsp::Url::from_file_path("/file.rs").unwrap()
4259 );
4260 futures::future::pending::<()>().await;
4261 unreachable!()
4262 });
4263 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4264 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4265 cx.foreground().start_waiting();
4266 save.await.unwrap();
4267 assert_eq!(
4268 editor.read_with(cx, |editor, cx| editor.text(cx)),
4269 "one\ntwo\nthree\n"
4270 );
4271 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4272
4273 // Set rust language override and assert overriden tabsize is sent to language server
4274 update_test_settings(cx, |settings| {
4275 settings.languages.insert(
4276 "Rust".into(),
4277 LanguageSettingsContent {
4278 tab_size: NonZeroU32::new(8),
4279 ..Default::default()
4280 },
4281 );
4282 });
4283
4284 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4285 fake_server
4286 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4287 assert_eq!(
4288 params.text_document.uri,
4289 lsp::Url::from_file_path("/file.rs").unwrap()
4290 );
4291 assert_eq!(params.options.tab_size, 8);
4292 Ok(Some(vec![]))
4293 })
4294 .next()
4295 .await;
4296 cx.foreground().start_waiting();
4297 save.await.unwrap();
4298}
4299
4300#[gpui::test]
4301async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4302 init_test(cx, |_| {});
4303
4304 let mut language = Language::new(
4305 LanguageConfig {
4306 name: "Rust".into(),
4307 path_suffixes: vec!["rs".to_string()],
4308 ..Default::default()
4309 },
4310 Some(tree_sitter_rust::language()),
4311 );
4312 let mut fake_servers = language
4313 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4314 capabilities: lsp::ServerCapabilities {
4315 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4316 ..Default::default()
4317 },
4318 ..Default::default()
4319 }))
4320 .await;
4321
4322 let fs = FakeFs::new(cx.background());
4323 fs.insert_file("/file.rs", Default::default()).await;
4324
4325 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4326 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4327 let buffer = project
4328 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4329 .await
4330 .unwrap();
4331
4332 cx.foreground().start_waiting();
4333 let fake_server = fake_servers.next().await.unwrap();
4334
4335 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4336 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4337 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4338 assert!(cx.read(|cx| editor.is_dirty(cx)));
4339
4340 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4341 fake_server
4342 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4343 assert_eq!(
4344 params.text_document.uri,
4345 lsp::Url::from_file_path("/file.rs").unwrap()
4346 );
4347 assert_eq!(params.options.tab_size, 4);
4348 Ok(Some(vec![lsp::TextEdit::new(
4349 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4350 ", ".to_string(),
4351 )]))
4352 })
4353 .next()
4354 .await;
4355 cx.foreground().start_waiting();
4356 save.await.unwrap();
4357 assert_eq!(
4358 editor.read_with(cx, |editor, cx| editor.text(cx)),
4359 "one, two\nthree\n"
4360 );
4361 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4362
4363 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4364 assert!(cx.read(|cx| editor.is_dirty(cx)));
4365
4366 // Ensure we can still save even if formatting hangs.
4367 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4368 move |params, _| async move {
4369 assert_eq!(
4370 params.text_document.uri,
4371 lsp::Url::from_file_path("/file.rs").unwrap()
4372 );
4373 futures::future::pending::<()>().await;
4374 unreachable!()
4375 },
4376 );
4377 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4378 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4379 cx.foreground().start_waiting();
4380 save.await.unwrap();
4381 assert_eq!(
4382 editor.read_with(cx, |editor, cx| editor.text(cx)),
4383 "one\ntwo\nthree\n"
4384 );
4385 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4386
4387 // Set rust language override and assert overriden tabsize is sent to language server
4388 update_test_settings(cx, |settings| {
4389 settings.languages.insert(
4390 "Rust".into(),
4391 LanguageSettingsContent {
4392 tab_size: NonZeroU32::new(8),
4393 ..Default::default()
4394 },
4395 );
4396 });
4397
4398 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4399 fake_server
4400 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4401 assert_eq!(
4402 params.text_document.uri,
4403 lsp::Url::from_file_path("/file.rs").unwrap()
4404 );
4405 assert_eq!(params.options.tab_size, 8);
4406 Ok(Some(vec![]))
4407 })
4408 .next()
4409 .await;
4410 cx.foreground().start_waiting();
4411 save.await.unwrap();
4412}
4413
4414#[gpui::test]
4415async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4416 init_test(cx, |_| {});
4417
4418 let mut language = Language::new(
4419 LanguageConfig {
4420 name: "Rust".into(),
4421 path_suffixes: vec!["rs".to_string()],
4422 ..Default::default()
4423 },
4424 Some(tree_sitter_rust::language()),
4425 );
4426 let mut fake_servers = language
4427 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4428 capabilities: lsp::ServerCapabilities {
4429 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4430 ..Default::default()
4431 },
4432 ..Default::default()
4433 }))
4434 .await;
4435
4436 let fs = FakeFs::new(cx.background());
4437 fs.insert_file("/file.rs", Default::default()).await;
4438
4439 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4440 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4441 let buffer = project
4442 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4443 .await
4444 .unwrap();
4445
4446 cx.foreground().start_waiting();
4447 let fake_server = fake_servers.next().await.unwrap();
4448
4449 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4450 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4451 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4452
4453 let format = editor.update(cx, |editor, cx| {
4454 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4455 });
4456 fake_server
4457 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4458 assert_eq!(
4459 params.text_document.uri,
4460 lsp::Url::from_file_path("/file.rs").unwrap()
4461 );
4462 assert_eq!(params.options.tab_size, 4);
4463 Ok(Some(vec![lsp::TextEdit::new(
4464 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4465 ", ".to_string(),
4466 )]))
4467 })
4468 .next()
4469 .await;
4470 cx.foreground().start_waiting();
4471 format.await.unwrap();
4472 assert_eq!(
4473 editor.read_with(cx, |editor, cx| editor.text(cx)),
4474 "one, two\nthree\n"
4475 );
4476
4477 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4478 // Ensure we don't lock if formatting hangs.
4479 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4480 assert_eq!(
4481 params.text_document.uri,
4482 lsp::Url::from_file_path("/file.rs").unwrap()
4483 );
4484 futures::future::pending::<()>().await;
4485 unreachable!()
4486 });
4487 let format = editor.update(cx, |editor, cx| {
4488 editor.perform_format(project, FormatTrigger::Manual, cx)
4489 });
4490 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4491 cx.foreground().start_waiting();
4492 format.await.unwrap();
4493 assert_eq!(
4494 editor.read_with(cx, |editor, cx| editor.text(cx)),
4495 "one\ntwo\nthree\n"
4496 );
4497}
4498
4499#[gpui::test]
4500async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4501 init_test(cx, |_| {});
4502
4503 let mut cx = EditorLspTestContext::new_rust(
4504 lsp::ServerCapabilities {
4505 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4506 ..Default::default()
4507 },
4508 cx,
4509 )
4510 .await;
4511
4512 cx.set_state(indoc! {"
4513 one.twoˇ
4514 "});
4515
4516 // The format request takes a long time. When it completes, it inserts
4517 // a newline and an indent before the `.`
4518 cx.lsp
4519 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4520 let executor = cx.background();
4521 async move {
4522 executor.timer(Duration::from_millis(100)).await;
4523 Ok(Some(vec![lsp::TextEdit {
4524 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4525 new_text: "\n ".into(),
4526 }]))
4527 }
4528 });
4529
4530 // Submit a format request.
4531 let format_1 = cx
4532 .update_editor(|editor, cx| editor.format(&Format, cx))
4533 .unwrap();
4534 cx.foreground().run_until_parked();
4535
4536 // Submit a second format request.
4537 let format_2 = cx
4538 .update_editor(|editor, cx| editor.format(&Format, cx))
4539 .unwrap();
4540 cx.foreground().run_until_parked();
4541
4542 // Wait for both format requests to complete
4543 cx.foreground().advance_clock(Duration::from_millis(200));
4544 cx.foreground().start_waiting();
4545 format_1.await.unwrap();
4546 cx.foreground().start_waiting();
4547 format_2.await.unwrap();
4548
4549 // The formatting edits only happens once.
4550 cx.assert_editor_state(indoc! {"
4551 one
4552 .twoˇ
4553 "});
4554}
4555
4556#[gpui::test]
4557async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4558 init_test(cx, |_| {});
4559
4560 let mut cx = EditorLspTestContext::new_rust(
4561 lsp::ServerCapabilities {
4562 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4563 ..Default::default()
4564 },
4565 cx,
4566 )
4567 .await;
4568
4569 // Set up a buffer white some trailing whitespace and no trailing newline.
4570 cx.set_state(
4571 &[
4572 "one ", //
4573 "twoˇ", //
4574 "three ", //
4575 "four", //
4576 ]
4577 .join("\n"),
4578 );
4579
4580 // Submit a format request.
4581 let format = cx
4582 .update_editor(|editor, cx| editor.format(&Format, cx))
4583 .unwrap();
4584
4585 // Record which buffer changes have been sent to the language server
4586 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
4587 cx.lsp
4588 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
4589 let buffer_changes = buffer_changes.clone();
4590 move |params, _| {
4591 buffer_changes.lock().extend(
4592 params
4593 .content_changes
4594 .into_iter()
4595 .map(|e| (e.range.unwrap(), e.text)),
4596 );
4597 }
4598 });
4599
4600 // Handle formatting requests to the language server.
4601 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
4602 let buffer_changes = buffer_changes.clone();
4603 move |_, _| {
4604 // When formatting is requested, trailing whitespace has already been stripped,
4605 // and the trailing newline has already been added.
4606 assert_eq!(
4607 &buffer_changes.lock()[1..],
4608 &[
4609 (
4610 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
4611 "".into()
4612 ),
4613 (
4614 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
4615 "".into()
4616 ),
4617 (
4618 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
4619 "\n".into()
4620 ),
4621 ]
4622 );
4623
4624 // Insert blank lines between each line of the buffer.
4625 async move {
4626 Ok(Some(vec![
4627 lsp::TextEdit {
4628 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
4629 new_text: "\n".into(),
4630 },
4631 lsp::TextEdit {
4632 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
4633 new_text: "\n".into(),
4634 },
4635 ]))
4636 }
4637 }
4638 });
4639
4640 // After formatting the buffer, the trailing whitespace is stripped,
4641 // a newline is appended, and the edits provided by the language server
4642 // have been applied.
4643 format.await.unwrap();
4644 cx.assert_editor_state(
4645 &[
4646 "one", //
4647 "", //
4648 "twoˇ", //
4649 "", //
4650 "three", //
4651 "four", //
4652 "", //
4653 ]
4654 .join("\n"),
4655 );
4656
4657 // Undoing the formatting undoes the trailing whitespace removal, the
4658 // trailing newline, and the LSP edits.
4659 cx.update_buffer(|buffer, cx| buffer.undo(cx));
4660 cx.assert_editor_state(
4661 &[
4662 "one ", //
4663 "twoˇ", //
4664 "three ", //
4665 "four", //
4666 ]
4667 .join("\n"),
4668 );
4669}
4670
4671#[gpui::test]
4672async fn test_completion(cx: &mut gpui::TestAppContext) {
4673 init_test(cx, |_| {});
4674
4675 let mut cx = EditorLspTestContext::new_rust(
4676 lsp::ServerCapabilities {
4677 completion_provider: Some(lsp::CompletionOptions {
4678 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4679 ..Default::default()
4680 }),
4681 ..Default::default()
4682 },
4683 cx,
4684 )
4685 .await;
4686
4687 cx.set_state(indoc! {"
4688 oneˇ
4689 two
4690 three
4691 "});
4692 cx.simulate_keystroke(".");
4693 handle_completion_request(
4694 &mut cx,
4695 indoc! {"
4696 one.|<>
4697 two
4698 three
4699 "},
4700 vec!["first_completion", "second_completion"],
4701 )
4702 .await;
4703 cx.condition(|editor, _| editor.context_menu_visible())
4704 .await;
4705 let apply_additional_edits = cx.update_editor(|editor, cx| {
4706 editor.move_down(&MoveDown, cx);
4707 editor
4708 .confirm_completion(&ConfirmCompletion::default(), cx)
4709 .unwrap()
4710 });
4711 cx.assert_editor_state(indoc! {"
4712 one.second_completionˇ
4713 two
4714 three
4715 "});
4716
4717 handle_resolve_completion_request(
4718 &mut cx,
4719 Some(vec![
4720 (
4721 //This overlaps with the primary completion edit which is
4722 //misbehavior from the LSP spec, test that we filter it out
4723 indoc! {"
4724 one.second_ˇcompletion
4725 two
4726 threeˇ
4727 "},
4728 "overlapping aditional edit",
4729 ),
4730 (
4731 indoc! {"
4732 one.second_completion
4733 two
4734 threeˇ
4735 "},
4736 "\nadditional edit",
4737 ),
4738 ]),
4739 )
4740 .await;
4741 apply_additional_edits.await.unwrap();
4742 cx.assert_editor_state(indoc! {"
4743 one.second_completionˇ
4744 two
4745 three
4746 additional edit
4747 "});
4748
4749 cx.set_state(indoc! {"
4750 one.second_completion
4751 twoˇ
4752 threeˇ
4753 additional edit
4754 "});
4755 cx.simulate_keystroke(" ");
4756 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4757 cx.simulate_keystroke("s");
4758 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4759
4760 cx.assert_editor_state(indoc! {"
4761 one.second_completion
4762 two sˇ
4763 three sˇ
4764 additional edit
4765 "});
4766 handle_completion_request(
4767 &mut cx,
4768 indoc! {"
4769 one.second_completion
4770 two s
4771 three <s|>
4772 additional edit
4773 "},
4774 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4775 )
4776 .await;
4777 cx.condition(|editor, _| editor.context_menu_visible())
4778 .await;
4779
4780 cx.simulate_keystroke("i");
4781
4782 handle_completion_request(
4783 &mut cx,
4784 indoc! {"
4785 one.second_completion
4786 two si
4787 three <si|>
4788 additional edit
4789 "},
4790 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4791 )
4792 .await;
4793 cx.condition(|editor, _| editor.context_menu_visible())
4794 .await;
4795
4796 let apply_additional_edits = cx.update_editor(|editor, cx| {
4797 editor
4798 .confirm_completion(&ConfirmCompletion::default(), cx)
4799 .unwrap()
4800 });
4801 cx.assert_editor_state(indoc! {"
4802 one.second_completion
4803 two sixth_completionˇ
4804 three sixth_completionˇ
4805 additional edit
4806 "});
4807
4808 handle_resolve_completion_request(&mut cx, None).await;
4809 apply_additional_edits.await.unwrap();
4810
4811 cx.update(|cx| {
4812 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
4813 settings.update_user_settings::<EditorSettings>(cx, |settings| {
4814 settings.show_completions_on_input = Some(false);
4815 });
4816 })
4817 });
4818 cx.set_state("editorˇ");
4819 cx.simulate_keystroke(".");
4820 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4821 cx.simulate_keystroke("c");
4822 cx.simulate_keystroke("l");
4823 cx.simulate_keystroke("o");
4824 cx.assert_editor_state("editor.cloˇ");
4825 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4826 cx.update_editor(|editor, cx| {
4827 editor.show_completions(&ShowCompletions, cx);
4828 });
4829 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4830 cx.condition(|editor, _| editor.context_menu_visible())
4831 .await;
4832 let apply_additional_edits = cx.update_editor(|editor, cx| {
4833 editor
4834 .confirm_completion(&ConfirmCompletion::default(), cx)
4835 .unwrap()
4836 });
4837 cx.assert_editor_state("editor.closeˇ");
4838 handle_resolve_completion_request(&mut cx, None).await;
4839 apply_additional_edits.await.unwrap();
4840}
4841
4842#[gpui::test]
4843async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4844 init_test(cx, |_| {});
4845
4846 let language = Arc::new(Language::new(
4847 LanguageConfig {
4848 line_comment: Some("// ".into()),
4849 ..Default::default()
4850 },
4851 Some(tree_sitter_rust::language()),
4852 ));
4853
4854 let text = "
4855 fn a() {
4856 //b();
4857 // c();
4858 // d();
4859 }
4860 "
4861 .unindent();
4862
4863 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4864 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4865 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4866
4867 view.update(cx, |editor, cx| {
4868 // If multiple selections intersect a line, the line is only
4869 // toggled once.
4870 editor.change_selections(None, cx, |s| {
4871 s.select_display_ranges([
4872 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4873 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4874 ])
4875 });
4876 editor.toggle_comments(&ToggleComments::default(), cx);
4877 assert_eq!(
4878 editor.text(cx),
4879 "
4880 fn a() {
4881 b();
4882 c();
4883 d();
4884 }
4885 "
4886 .unindent()
4887 );
4888
4889 // The comment prefix is inserted at the same column for every line
4890 // in a selection.
4891 editor.change_selections(None, cx, |s| {
4892 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4893 });
4894 editor.toggle_comments(&ToggleComments::default(), cx);
4895 assert_eq!(
4896 editor.text(cx),
4897 "
4898 fn a() {
4899 // b();
4900 // c();
4901 // d();
4902 }
4903 "
4904 .unindent()
4905 );
4906
4907 // If a selection ends at the beginning of a line, that line is not toggled.
4908 editor.change_selections(None, cx, |s| {
4909 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4910 });
4911 editor.toggle_comments(&ToggleComments::default(), cx);
4912 assert_eq!(
4913 editor.text(cx),
4914 "
4915 fn a() {
4916 // b();
4917 c();
4918 // d();
4919 }
4920 "
4921 .unindent()
4922 );
4923 });
4924}
4925
4926#[gpui::test]
4927async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
4928 init_test(cx, |_| {});
4929
4930 let language = Arc::new(Language::new(
4931 LanguageConfig {
4932 line_comment: Some("// ".into()),
4933 ..Default::default()
4934 },
4935 Some(tree_sitter_rust::language()),
4936 ));
4937
4938 let registry = Arc::new(LanguageRegistry::test());
4939 registry.add(language.clone());
4940
4941 let mut cx = EditorTestContext::new(cx);
4942 cx.update_buffer(|buffer, cx| {
4943 buffer.set_language_registry(registry);
4944 buffer.set_language(Some(language), cx);
4945 });
4946
4947 let toggle_comments = &ToggleComments {
4948 advance_downwards: true,
4949 };
4950
4951 // Single cursor on one line -> advance
4952 // Cursor moves horizontally 3 characters as well on non-blank line
4953 cx.set_state(indoc!(
4954 "fn a() {
4955 ˇdog();
4956 cat();
4957 }"
4958 ));
4959 cx.update_editor(|editor, cx| {
4960 editor.toggle_comments(toggle_comments, cx);
4961 });
4962 cx.assert_editor_state(indoc!(
4963 "fn a() {
4964 // dog();
4965 catˇ();
4966 }"
4967 ));
4968
4969 // Single selection on one line -> don't advance
4970 cx.set_state(indoc!(
4971 "fn a() {
4972 «dog()ˇ»;
4973 cat();
4974 }"
4975 ));
4976 cx.update_editor(|editor, cx| {
4977 editor.toggle_comments(toggle_comments, cx);
4978 });
4979 cx.assert_editor_state(indoc!(
4980 "fn a() {
4981 // «dog()ˇ»;
4982 cat();
4983 }"
4984 ));
4985
4986 // Multiple cursors on one line -> advance
4987 cx.set_state(indoc!(
4988 "fn a() {
4989 ˇdˇog();
4990 cat();
4991 }"
4992 ));
4993 cx.update_editor(|editor, cx| {
4994 editor.toggle_comments(toggle_comments, cx);
4995 });
4996 cx.assert_editor_state(indoc!(
4997 "fn a() {
4998 // dog();
4999 catˇ(ˇ);
5000 }"
5001 ));
5002
5003 // Multiple cursors on one line, with selection -> don't advance
5004 cx.set_state(indoc!(
5005 "fn a() {
5006 ˇdˇog«()ˇ»;
5007 cat();
5008 }"
5009 ));
5010 cx.update_editor(|editor, cx| {
5011 editor.toggle_comments(toggle_comments, cx);
5012 });
5013 cx.assert_editor_state(indoc!(
5014 "fn a() {
5015 // ˇdˇog«()ˇ»;
5016 cat();
5017 }"
5018 ));
5019
5020 // Single cursor on one line -> advance
5021 // Cursor moves to column 0 on blank line
5022 cx.set_state(indoc!(
5023 "fn a() {
5024 ˇdog();
5025
5026 cat();
5027 }"
5028 ));
5029 cx.update_editor(|editor, cx| {
5030 editor.toggle_comments(toggle_comments, cx);
5031 });
5032 cx.assert_editor_state(indoc!(
5033 "fn a() {
5034 // dog();
5035 ˇ
5036 cat();
5037 }"
5038 ));
5039
5040 // Single cursor on one line -> advance
5041 // Cursor starts and ends at column 0
5042 cx.set_state(indoc!(
5043 "fn a() {
5044 ˇ dog();
5045 cat();
5046 }"
5047 ));
5048 cx.update_editor(|editor, cx| {
5049 editor.toggle_comments(toggle_comments, cx);
5050 });
5051 cx.assert_editor_state(indoc!(
5052 "fn a() {
5053 // dog();
5054 ˇ cat();
5055 }"
5056 ));
5057}
5058
5059#[gpui::test]
5060async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 let mut cx = EditorTestContext::new(cx);
5064
5065 let html_language = Arc::new(
5066 Language::new(
5067 LanguageConfig {
5068 name: "HTML".into(),
5069 block_comment: Some(("<!-- ".into(), " -->".into())),
5070 ..Default::default()
5071 },
5072 Some(tree_sitter_html::language()),
5073 )
5074 .with_injection_query(
5075 r#"
5076 (script_element
5077 (raw_text) @content
5078 (#set! "language" "javascript"))
5079 "#,
5080 )
5081 .unwrap(),
5082 );
5083
5084 let javascript_language = Arc::new(Language::new(
5085 LanguageConfig {
5086 name: "JavaScript".into(),
5087 line_comment: Some("// ".into()),
5088 ..Default::default()
5089 },
5090 Some(tree_sitter_javascript::language()),
5091 ));
5092
5093 let registry = Arc::new(LanguageRegistry::test());
5094 registry.add(html_language.clone());
5095 registry.add(javascript_language.clone());
5096
5097 cx.update_buffer(|buffer, cx| {
5098 buffer.set_language_registry(registry);
5099 buffer.set_language(Some(html_language), cx);
5100 });
5101
5102 // Toggle comments for empty selections
5103 cx.set_state(
5104 &r#"
5105 <p>A</p>ˇ
5106 <p>B</p>ˇ
5107 <p>C</p>ˇ
5108 "#
5109 .unindent(),
5110 );
5111 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5112 cx.assert_editor_state(
5113 &r#"
5114 <!-- <p>A</p>ˇ -->
5115 <!-- <p>B</p>ˇ -->
5116 <!-- <p>C</p>ˇ -->
5117 "#
5118 .unindent(),
5119 );
5120 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5121 cx.assert_editor_state(
5122 &r#"
5123 <p>A</p>ˇ
5124 <p>B</p>ˇ
5125 <p>C</p>ˇ
5126 "#
5127 .unindent(),
5128 );
5129
5130 // Toggle comments for mixture of empty and non-empty selections, where
5131 // multiple selections occupy a given line.
5132 cx.set_state(
5133 &r#"
5134 <p>A«</p>
5135 <p>ˇ»B</p>ˇ
5136 <p>C«</p>
5137 <p>ˇ»D</p>ˇ
5138 "#
5139 .unindent(),
5140 );
5141
5142 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5143 cx.assert_editor_state(
5144 &r#"
5145 <!-- <p>A«</p>
5146 <p>ˇ»B</p>ˇ -->
5147 <!-- <p>C«</p>
5148 <p>ˇ»D</p>ˇ -->
5149 "#
5150 .unindent(),
5151 );
5152 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5153 cx.assert_editor_state(
5154 &r#"
5155 <p>A«</p>
5156 <p>ˇ»B</p>ˇ
5157 <p>C«</p>
5158 <p>ˇ»D</p>ˇ
5159 "#
5160 .unindent(),
5161 );
5162
5163 // Toggle comments when different languages are active for different
5164 // selections.
5165 cx.set_state(
5166 &r#"
5167 ˇ<script>
5168 ˇvar x = new Y();
5169 ˇ</script>
5170 "#
5171 .unindent(),
5172 );
5173 cx.foreground().run_until_parked();
5174 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5175 cx.assert_editor_state(
5176 &r#"
5177 <!-- ˇ<script> -->
5178 // ˇvar x = new Y();
5179 <!-- ˇ</script> -->
5180 "#
5181 .unindent(),
5182 );
5183}
5184
5185#[gpui::test]
5186fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5187 init_test(cx, |_| {});
5188
5189 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5190 let multibuffer = cx.add_model(|cx| {
5191 let mut multibuffer = MultiBuffer::new(0);
5192 multibuffer.push_excerpts(
5193 buffer.clone(),
5194 [
5195 ExcerptRange {
5196 context: Point::new(0, 0)..Point::new(0, 4),
5197 primary: None,
5198 },
5199 ExcerptRange {
5200 context: Point::new(1, 0)..Point::new(1, 4),
5201 primary: None,
5202 },
5203 ],
5204 cx,
5205 );
5206 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5207 multibuffer
5208 });
5209
5210 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5211 view.update(cx, |view, cx| {
5212 assert_eq!(view.text(cx), "aaaa\nbbbb");
5213 view.change_selections(None, cx, |s| {
5214 s.select_ranges([
5215 Point::new(0, 0)..Point::new(0, 0),
5216 Point::new(1, 0)..Point::new(1, 0),
5217 ])
5218 });
5219
5220 view.handle_input("X", cx);
5221 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5222 assert_eq!(
5223 view.selections.ranges(cx),
5224 [
5225 Point::new(0, 1)..Point::new(0, 1),
5226 Point::new(1, 1)..Point::new(1, 1),
5227 ]
5228 )
5229 });
5230}
5231
5232#[gpui::test]
5233fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5234 init_test(cx, |_| {});
5235
5236 let markers = vec![('[', ']').into(), ('(', ')').into()];
5237 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5238 indoc! {"
5239 [aaaa
5240 (bbbb]
5241 cccc)",
5242 },
5243 markers.clone(),
5244 );
5245 let excerpt_ranges = markers.into_iter().map(|marker| {
5246 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5247 ExcerptRange {
5248 context,
5249 primary: None,
5250 }
5251 });
5252 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5253 let multibuffer = cx.add_model(|cx| {
5254 let mut multibuffer = MultiBuffer::new(0);
5255 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5256 multibuffer
5257 });
5258
5259 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5260 view.update(cx, |view, cx| {
5261 let (expected_text, selection_ranges) = marked_text_ranges(
5262 indoc! {"
5263 aaaa
5264 bˇbbb
5265 bˇbbˇb
5266 cccc"
5267 },
5268 true,
5269 );
5270 assert_eq!(view.text(cx), expected_text);
5271 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5272
5273 view.handle_input("X", cx);
5274
5275 let (expected_text, expected_selections) = marked_text_ranges(
5276 indoc! {"
5277 aaaa
5278 bXˇbbXb
5279 bXˇbbXˇb
5280 cccc"
5281 },
5282 false,
5283 );
5284 assert_eq!(view.text(cx), expected_text);
5285 assert_eq!(view.selections.ranges(cx), expected_selections);
5286
5287 view.newline(&Newline, cx);
5288 let (expected_text, expected_selections) = marked_text_ranges(
5289 indoc! {"
5290 aaaa
5291 bX
5292 ˇbbX
5293 b
5294 bX
5295 ˇbbX
5296 ˇb
5297 cccc"
5298 },
5299 false,
5300 );
5301 assert_eq!(view.text(cx), expected_text);
5302 assert_eq!(view.selections.ranges(cx), expected_selections);
5303 });
5304}
5305
5306#[gpui::test]
5307fn test_refresh_selections(cx: &mut TestAppContext) {
5308 init_test(cx, |_| {});
5309
5310 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5311 let mut excerpt1_id = None;
5312 let multibuffer = cx.add_model(|cx| {
5313 let mut multibuffer = MultiBuffer::new(0);
5314 excerpt1_id = multibuffer
5315 .push_excerpts(
5316 buffer.clone(),
5317 [
5318 ExcerptRange {
5319 context: Point::new(0, 0)..Point::new(1, 4),
5320 primary: None,
5321 },
5322 ExcerptRange {
5323 context: Point::new(1, 0)..Point::new(2, 4),
5324 primary: None,
5325 },
5326 ],
5327 cx,
5328 )
5329 .into_iter()
5330 .next();
5331 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5332 multibuffer
5333 });
5334
5335 let (_, editor) = cx.add_window(|cx| {
5336 let mut editor = build_editor(multibuffer.clone(), cx);
5337 let snapshot = editor.snapshot(cx);
5338 editor.change_selections(None, cx, |s| {
5339 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5340 });
5341 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5342 assert_eq!(
5343 editor.selections.ranges(cx),
5344 [
5345 Point::new(1, 3)..Point::new(1, 3),
5346 Point::new(2, 1)..Point::new(2, 1),
5347 ]
5348 );
5349 editor
5350 });
5351
5352 // Refreshing selections is a no-op when excerpts haven't changed.
5353 editor.update(cx, |editor, cx| {
5354 editor.change_selections(None, cx, |s| s.refresh());
5355 assert_eq!(
5356 editor.selections.ranges(cx),
5357 [
5358 Point::new(1, 3)..Point::new(1, 3),
5359 Point::new(2, 1)..Point::new(2, 1),
5360 ]
5361 );
5362 });
5363
5364 multibuffer.update(cx, |multibuffer, cx| {
5365 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5366 });
5367 editor.update(cx, |editor, cx| {
5368 // Removing an excerpt causes the first selection to become degenerate.
5369 assert_eq!(
5370 editor.selections.ranges(cx),
5371 [
5372 Point::new(0, 0)..Point::new(0, 0),
5373 Point::new(0, 1)..Point::new(0, 1)
5374 ]
5375 );
5376
5377 // Refreshing selections will relocate the first selection to the original buffer
5378 // location.
5379 editor.change_selections(None, cx, |s| s.refresh());
5380 assert_eq!(
5381 editor.selections.ranges(cx),
5382 [
5383 Point::new(0, 1)..Point::new(0, 1),
5384 Point::new(0, 3)..Point::new(0, 3)
5385 ]
5386 );
5387 assert!(editor.selections.pending_anchor().is_some());
5388 });
5389}
5390
5391#[gpui::test]
5392fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5393 init_test(cx, |_| {});
5394
5395 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5396 let mut excerpt1_id = None;
5397 let multibuffer = cx.add_model(|cx| {
5398 let mut multibuffer = MultiBuffer::new(0);
5399 excerpt1_id = multibuffer
5400 .push_excerpts(
5401 buffer.clone(),
5402 [
5403 ExcerptRange {
5404 context: Point::new(0, 0)..Point::new(1, 4),
5405 primary: None,
5406 },
5407 ExcerptRange {
5408 context: Point::new(1, 0)..Point::new(2, 4),
5409 primary: None,
5410 },
5411 ],
5412 cx,
5413 )
5414 .into_iter()
5415 .next();
5416 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5417 multibuffer
5418 });
5419
5420 let (_, editor) = cx.add_window(|cx| {
5421 let mut editor = build_editor(multibuffer.clone(), cx);
5422 let snapshot = editor.snapshot(cx);
5423 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5424 assert_eq!(
5425 editor.selections.ranges(cx),
5426 [Point::new(1, 3)..Point::new(1, 3)]
5427 );
5428 editor
5429 });
5430
5431 multibuffer.update(cx, |multibuffer, cx| {
5432 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5433 });
5434 editor.update(cx, |editor, cx| {
5435 assert_eq!(
5436 editor.selections.ranges(cx),
5437 [Point::new(0, 0)..Point::new(0, 0)]
5438 );
5439
5440 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5441 editor.change_selections(None, cx, |s| s.refresh());
5442 assert_eq!(
5443 editor.selections.ranges(cx),
5444 [Point::new(0, 3)..Point::new(0, 3)]
5445 );
5446 assert!(editor.selections.pending_anchor().is_some());
5447 });
5448}
5449
5450#[gpui::test]
5451async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5452 init_test(cx, |_| {});
5453
5454 let language = Arc::new(
5455 Language::new(
5456 LanguageConfig {
5457 brackets: BracketPairConfig {
5458 pairs: vec![
5459 BracketPair {
5460 start: "{".to_string(),
5461 end: "}".to_string(),
5462 close: true,
5463 newline: true,
5464 },
5465 BracketPair {
5466 start: "/* ".to_string(),
5467 end: " */".to_string(),
5468 close: true,
5469 newline: true,
5470 },
5471 ],
5472 ..Default::default()
5473 },
5474 ..Default::default()
5475 },
5476 Some(tree_sitter_rust::language()),
5477 )
5478 .with_indents_query("")
5479 .unwrap(),
5480 );
5481
5482 let text = concat!(
5483 "{ }\n", //
5484 " x\n", //
5485 " /* */\n", //
5486 "x\n", //
5487 "{{} }\n", //
5488 );
5489
5490 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5491 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5492 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5493 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5494 .await;
5495
5496 view.update(cx, |view, cx| {
5497 view.change_selections(None, cx, |s| {
5498 s.select_display_ranges([
5499 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5500 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5501 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5502 ])
5503 });
5504 view.newline(&Newline, cx);
5505
5506 assert_eq!(
5507 view.buffer().read(cx).read(cx).text(),
5508 concat!(
5509 "{ \n", // Suppress rustfmt
5510 "\n", //
5511 "}\n", //
5512 " x\n", //
5513 " /* \n", //
5514 " \n", //
5515 " */\n", //
5516 "x\n", //
5517 "{{} \n", //
5518 "}\n", //
5519 )
5520 );
5521 });
5522}
5523
5524#[gpui::test]
5525fn test_highlighted_ranges(cx: &mut TestAppContext) {
5526 init_test(cx, |_| {});
5527
5528 let (_, editor) = cx.add_window(|cx| {
5529 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5530 build_editor(buffer.clone(), cx)
5531 });
5532
5533 editor.update(cx, |editor, cx| {
5534 struct Type1;
5535 struct Type2;
5536
5537 let buffer = editor.buffer.read(cx).snapshot(cx);
5538
5539 let anchor_range =
5540 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5541
5542 editor.highlight_background::<Type1>(
5543 vec![
5544 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5545 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5546 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5547 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5548 ],
5549 |_| Color::red(),
5550 cx,
5551 );
5552 editor.highlight_background::<Type2>(
5553 vec![
5554 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5555 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5556 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5557 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5558 ],
5559 |_| Color::green(),
5560 cx,
5561 );
5562
5563 let snapshot = editor.snapshot(cx);
5564 let mut highlighted_ranges = editor.background_highlights_in_range(
5565 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5566 &snapshot,
5567 theme::current(cx).as_ref(),
5568 );
5569 // Enforce a consistent ordering based on color without relying on the ordering of the
5570 // highlight's `TypeId` which is non-deterministic.
5571 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5572 assert_eq!(
5573 highlighted_ranges,
5574 &[
5575 (
5576 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5577 Color::green(),
5578 ),
5579 (
5580 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5581 Color::green(),
5582 ),
5583 (
5584 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5585 Color::red(),
5586 ),
5587 (
5588 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5589 Color::red(),
5590 ),
5591 ]
5592 );
5593 assert_eq!(
5594 editor.background_highlights_in_range(
5595 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5596 &snapshot,
5597 theme::current(cx).as_ref(),
5598 ),
5599 &[(
5600 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5601 Color::red(),
5602 )]
5603 );
5604 });
5605}
5606
5607#[gpui::test]
5608async fn test_following(cx: &mut gpui::TestAppContext) {
5609 init_test(cx, |_| {});
5610
5611 let fs = FakeFs::new(cx.background());
5612 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5613
5614 let buffer = project.update(cx, |project, cx| {
5615 let buffer = project
5616 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5617 .unwrap();
5618 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5619 });
5620 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5621 let (_, follower) = cx.update(|cx| {
5622 cx.add_window(
5623 WindowOptions {
5624 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5625 ..Default::default()
5626 },
5627 |cx| build_editor(buffer.clone(), cx),
5628 )
5629 });
5630
5631 let is_still_following = Rc::new(RefCell::new(true));
5632 let follower_edit_event_count = Rc::new(RefCell::new(0));
5633 let pending_update = Rc::new(RefCell::new(None));
5634 follower.update(cx, {
5635 let update = pending_update.clone();
5636 let is_still_following = is_still_following.clone();
5637 let follower_edit_event_count = follower_edit_event_count.clone();
5638 |_, cx| {
5639 cx.subscribe(&leader, move |_, leader, event, cx| {
5640 leader
5641 .read(cx)
5642 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5643 })
5644 .detach();
5645
5646 cx.subscribe(&follower, move |_, _, event, cx| {
5647 if Editor::should_unfollow_on_event(event, cx) {
5648 *is_still_following.borrow_mut() = false;
5649 }
5650 if let Event::BufferEdited = event {
5651 *follower_edit_event_count.borrow_mut() += 1;
5652 }
5653 })
5654 .detach();
5655 }
5656 });
5657
5658 // Update the selections only
5659 leader.update(cx, |leader, cx| {
5660 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5661 });
5662 follower
5663 .update(cx, |follower, cx| {
5664 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5665 })
5666 .await
5667 .unwrap();
5668 follower.read_with(cx, |follower, cx| {
5669 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5670 });
5671 assert_eq!(*is_still_following.borrow(), true);
5672 assert_eq!(*follower_edit_event_count.borrow(), 0);
5673
5674 // Update the scroll position only
5675 leader.update(cx, |leader, cx| {
5676 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5677 });
5678 follower
5679 .update(cx, |follower, cx| {
5680 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5681 })
5682 .await
5683 .unwrap();
5684 assert_eq!(
5685 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5686 vec2f(1.5, 3.5)
5687 );
5688 assert_eq!(*is_still_following.borrow(), true);
5689 assert_eq!(*follower_edit_event_count.borrow(), 0);
5690
5691 // Update the selections and scroll position. The follower's scroll position is updated
5692 // via autoscroll, not via the leader's exact scroll position.
5693 leader.update(cx, |leader, cx| {
5694 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5695 leader.request_autoscroll(Autoscroll::newest(), cx);
5696 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5697 });
5698 follower
5699 .update(cx, |follower, cx| {
5700 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5701 })
5702 .await
5703 .unwrap();
5704 follower.update(cx, |follower, cx| {
5705 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5706 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5707 });
5708 assert_eq!(*is_still_following.borrow(), true);
5709
5710 // Creating a pending selection that precedes another selection
5711 leader.update(cx, |leader, cx| {
5712 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5713 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5714 });
5715 follower
5716 .update(cx, |follower, cx| {
5717 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5718 })
5719 .await
5720 .unwrap();
5721 follower.read_with(cx, |follower, cx| {
5722 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5723 });
5724 assert_eq!(*is_still_following.borrow(), true);
5725
5726 // Extend the pending selection so that it surrounds another selection
5727 leader.update(cx, |leader, cx| {
5728 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5729 });
5730 follower
5731 .update(cx, |follower, cx| {
5732 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5733 })
5734 .await
5735 .unwrap();
5736 follower.read_with(cx, |follower, cx| {
5737 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5738 });
5739
5740 // Scrolling locally breaks the follow
5741 follower.update(cx, |follower, cx| {
5742 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5743 follower.set_scroll_anchor(
5744 ScrollAnchor {
5745 top_anchor,
5746 offset: vec2f(0.0, 0.5),
5747 },
5748 cx,
5749 );
5750 });
5751 assert_eq!(*is_still_following.borrow(), false);
5752}
5753
5754#[gpui::test]
5755async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5756 init_test(cx, |_| {});
5757
5758 let fs = FakeFs::new(cx.background());
5759 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5760 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5761 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5762
5763 let leader = pane.update(cx, |_, cx| {
5764 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5765 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5766 });
5767
5768 // Start following the editor when it has no excerpts.
5769 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5770 let follower_1 = cx
5771 .update(|cx| {
5772 Editor::from_state_proto(
5773 pane.clone(),
5774 project.clone(),
5775 ViewId {
5776 creator: Default::default(),
5777 id: 0,
5778 },
5779 &mut state_message,
5780 cx,
5781 )
5782 })
5783 .unwrap()
5784 .await
5785 .unwrap();
5786
5787 let update_message = Rc::new(RefCell::new(None));
5788 follower_1.update(cx, {
5789 let update = update_message.clone();
5790 |_, cx| {
5791 cx.subscribe(&leader, move |_, leader, event, cx| {
5792 leader
5793 .read(cx)
5794 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5795 })
5796 .detach();
5797 }
5798 });
5799
5800 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5801 (
5802 project
5803 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5804 .unwrap(),
5805 project
5806 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5807 .unwrap(),
5808 )
5809 });
5810
5811 // Insert some excerpts.
5812 leader.update(cx, |leader, cx| {
5813 leader.buffer.update(cx, |multibuffer, cx| {
5814 let excerpt_ids = multibuffer.push_excerpts(
5815 buffer_1.clone(),
5816 [
5817 ExcerptRange {
5818 context: 1..6,
5819 primary: None,
5820 },
5821 ExcerptRange {
5822 context: 12..15,
5823 primary: None,
5824 },
5825 ExcerptRange {
5826 context: 0..3,
5827 primary: None,
5828 },
5829 ],
5830 cx,
5831 );
5832 multibuffer.insert_excerpts_after(
5833 excerpt_ids[0],
5834 buffer_2.clone(),
5835 [
5836 ExcerptRange {
5837 context: 8..12,
5838 primary: None,
5839 },
5840 ExcerptRange {
5841 context: 0..6,
5842 primary: None,
5843 },
5844 ],
5845 cx,
5846 );
5847 });
5848 });
5849
5850 // Apply the update of adding the excerpts.
5851 follower_1
5852 .update(cx, |follower, cx| {
5853 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5854 })
5855 .await
5856 .unwrap();
5857 assert_eq!(
5858 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
5859 leader.read_with(cx, |editor, cx| editor.text(cx))
5860 );
5861 update_message.borrow_mut().take();
5862
5863 // Start following separately after it already has excerpts.
5864 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5865 let follower_2 = cx
5866 .update(|cx| {
5867 Editor::from_state_proto(
5868 pane.clone(),
5869 project.clone(),
5870 ViewId {
5871 creator: Default::default(),
5872 id: 0,
5873 },
5874 &mut state_message,
5875 cx,
5876 )
5877 })
5878 .unwrap()
5879 .await
5880 .unwrap();
5881 assert_eq!(
5882 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
5883 leader.read_with(cx, |editor, cx| editor.text(cx))
5884 );
5885
5886 // Remove some excerpts.
5887 leader.update(cx, |leader, cx| {
5888 leader.buffer.update(cx, |multibuffer, cx| {
5889 let excerpt_ids = multibuffer.excerpt_ids();
5890 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
5891 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
5892 });
5893 });
5894
5895 // Apply the update of removing the excerpts.
5896 follower_1
5897 .update(cx, |follower, cx| {
5898 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5899 })
5900 .await
5901 .unwrap();
5902 follower_2
5903 .update(cx, |follower, cx| {
5904 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5905 })
5906 .await
5907 .unwrap();
5908 update_message.borrow_mut().take();
5909 assert_eq!(
5910 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
5911 leader.read_with(cx, |editor, cx| editor.text(cx))
5912 );
5913}
5914
5915#[test]
5916fn test_combine_syntax_and_fuzzy_match_highlights() {
5917 let string = "abcdefghijklmnop";
5918 let syntax_ranges = [
5919 (
5920 0..3,
5921 HighlightStyle {
5922 color: Some(Color::red()),
5923 ..Default::default()
5924 },
5925 ),
5926 (
5927 4..8,
5928 HighlightStyle {
5929 color: Some(Color::green()),
5930 ..Default::default()
5931 },
5932 ),
5933 ];
5934 let match_indices = [4, 6, 7, 8];
5935 assert_eq!(
5936 combine_syntax_and_fuzzy_match_highlights(
5937 string,
5938 Default::default(),
5939 syntax_ranges.into_iter(),
5940 &match_indices,
5941 ),
5942 &[
5943 (
5944 0..3,
5945 HighlightStyle {
5946 color: Some(Color::red()),
5947 ..Default::default()
5948 },
5949 ),
5950 (
5951 4..5,
5952 HighlightStyle {
5953 color: Some(Color::green()),
5954 weight: Some(fonts::Weight::BOLD),
5955 ..Default::default()
5956 },
5957 ),
5958 (
5959 5..6,
5960 HighlightStyle {
5961 color: Some(Color::green()),
5962 ..Default::default()
5963 },
5964 ),
5965 (
5966 6..8,
5967 HighlightStyle {
5968 color: Some(Color::green()),
5969 weight: Some(fonts::Weight::BOLD),
5970 ..Default::default()
5971 },
5972 ),
5973 (
5974 8..9,
5975 HighlightStyle {
5976 weight: Some(fonts::Weight::BOLD),
5977 ..Default::default()
5978 },
5979 ),
5980 ]
5981 );
5982}
5983
5984#[gpui::test]
5985async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5986 init_test(cx, |_| {});
5987
5988 let mut cx = EditorTestContext::new(cx);
5989
5990 let diff_base = r#"
5991 use some::mod;
5992
5993 const A: u32 = 42;
5994
5995 fn main() {
5996 println!("hello");
5997
5998 println!("world");
5999 }
6000 "#
6001 .unindent();
6002
6003 // Edits are modified, removed, modified, added
6004 cx.set_state(
6005 &r#"
6006 use some::modified;
6007
6008 ˇ
6009 fn main() {
6010 println!("hello there");
6011
6012 println!("around the");
6013 println!("world");
6014 }
6015 "#
6016 .unindent(),
6017 );
6018
6019 cx.set_diff_base(Some(&diff_base));
6020 deterministic.run_until_parked();
6021
6022 cx.update_editor(|editor, cx| {
6023 //Wrap around the bottom of the buffer
6024 for _ in 0..3 {
6025 editor.go_to_hunk(&GoToHunk, cx);
6026 }
6027 });
6028
6029 cx.assert_editor_state(
6030 &r#"
6031 ˇuse some::modified;
6032
6033
6034 fn main() {
6035 println!("hello there");
6036
6037 println!("around the");
6038 println!("world");
6039 }
6040 "#
6041 .unindent(),
6042 );
6043
6044 cx.update_editor(|editor, cx| {
6045 //Wrap around the top of the buffer
6046 for _ in 0..2 {
6047 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6048 }
6049 });
6050
6051 cx.assert_editor_state(
6052 &r#"
6053 use some::modified;
6054
6055
6056 fn main() {
6057 ˇ println!("hello there");
6058
6059 println!("around the");
6060 println!("world");
6061 }
6062 "#
6063 .unindent(),
6064 );
6065
6066 cx.update_editor(|editor, cx| {
6067 editor.fold(&Fold, cx);
6068
6069 //Make sure that the fold only gets one hunk
6070 for _ in 0..4 {
6071 editor.go_to_hunk(&GoToHunk, cx);
6072 }
6073 });
6074
6075 cx.assert_editor_state(
6076 &r#"
6077 ˇuse some::modified;
6078
6079
6080 fn main() {
6081 println!("hello there");
6082
6083 println!("around the");
6084 println!("world");
6085 }
6086 "#
6087 .unindent(),
6088 );
6089}
6090
6091#[test]
6092fn test_split_words() {
6093 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6094 split_words(text).collect()
6095 }
6096
6097 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6098 assert_eq!(split("hello_world"), &["hello_", "world"]);
6099 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6100 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6101 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6102 assert_eq!(split("helloworld"), &["helloworld"]);
6103}
6104
6105#[gpui::test]
6106async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6107 init_test(cx, |_| {});
6108
6109 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6110 let mut assert = |before, after| {
6111 let _state_context = cx.set_state(before);
6112 cx.update_editor(|editor, cx| {
6113 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6114 });
6115 cx.assert_editor_state(after);
6116 };
6117
6118 // Outside bracket jumps to outside of matching bracket
6119 assert("console.logˇ(var);", "console.log(var)ˇ;");
6120 assert("console.log(var)ˇ;", "console.logˇ(var);");
6121
6122 // Inside bracket jumps to inside of matching bracket
6123 assert("console.log(ˇvar);", "console.log(varˇ);");
6124 assert("console.log(varˇ);", "console.log(ˇvar);");
6125
6126 // When outside a bracket and inside, favor jumping to the inside bracket
6127 assert(
6128 "console.log('foo', [1, 2, 3]ˇ);",
6129 "console.log(ˇ'foo', [1, 2, 3]);",
6130 );
6131 assert(
6132 "console.log(ˇ'foo', [1, 2, 3]);",
6133 "console.log('foo', [1, 2, 3]ˇ);",
6134 );
6135
6136 // Bias forward if two options are equally likely
6137 assert(
6138 "let result = curried_fun()ˇ();",
6139 "let result = curried_fun()()ˇ;",
6140 );
6141
6142 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6143 assert(
6144 indoc! {"
6145 function test() {
6146 console.log('test')ˇ
6147 }"},
6148 indoc! {"
6149 function test() {
6150 console.logˇ('test')
6151 }"},
6152 );
6153}
6154
6155#[gpui::test(iterations = 10)]
6156async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6157 init_test(cx, |_| {});
6158
6159 let (copilot, copilot_lsp) = Copilot::fake(cx);
6160 cx.update(|cx| cx.set_global(copilot));
6161 let mut cx = EditorLspTestContext::new_rust(
6162 lsp::ServerCapabilities {
6163 completion_provider: Some(lsp::CompletionOptions {
6164 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6165 ..Default::default()
6166 }),
6167 ..Default::default()
6168 },
6169 cx,
6170 )
6171 .await;
6172
6173 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6174 cx.set_state(indoc! {"
6175 oneˇ
6176 two
6177 three
6178 "});
6179 cx.simulate_keystroke(".");
6180 let _ = handle_completion_request(
6181 &mut cx,
6182 indoc! {"
6183 one.|<>
6184 two
6185 three
6186 "},
6187 vec!["completion_a", "completion_b"],
6188 );
6189 handle_copilot_completion_request(
6190 &copilot_lsp,
6191 vec![copilot::request::Completion {
6192 text: "one.copilot1".into(),
6193 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6194 ..Default::default()
6195 }],
6196 vec![],
6197 );
6198 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6199 cx.update_editor(|editor, cx| {
6200 assert!(editor.context_menu_visible());
6201 assert!(!editor.has_active_copilot_suggestion(cx));
6202
6203 // Confirming a completion inserts it and hides the context menu, without showing
6204 // the copilot suggestion afterwards.
6205 editor
6206 .confirm_completion(&Default::default(), cx)
6207 .unwrap()
6208 .detach();
6209 assert!(!editor.context_menu_visible());
6210 assert!(!editor.has_active_copilot_suggestion(cx));
6211 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6212 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6213 });
6214
6215 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6216 cx.set_state(indoc! {"
6217 oneˇ
6218 two
6219 three
6220 "});
6221 cx.simulate_keystroke(".");
6222 let _ = handle_completion_request(
6223 &mut cx,
6224 indoc! {"
6225 one.|<>
6226 two
6227 three
6228 "},
6229 vec![],
6230 );
6231 handle_copilot_completion_request(
6232 &copilot_lsp,
6233 vec![copilot::request::Completion {
6234 text: "one.copilot1".into(),
6235 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6236 ..Default::default()
6237 }],
6238 vec![],
6239 );
6240 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6241 cx.update_editor(|editor, cx| {
6242 assert!(!editor.context_menu_visible());
6243 assert!(editor.has_active_copilot_suggestion(cx));
6244 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6245 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6246 });
6247
6248 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6249 cx.set_state(indoc! {"
6250 oneˇ
6251 two
6252 three
6253 "});
6254 cx.simulate_keystroke(".");
6255 let _ = handle_completion_request(
6256 &mut cx,
6257 indoc! {"
6258 one.|<>
6259 two
6260 three
6261 "},
6262 vec!["completion_a", "completion_b"],
6263 );
6264 handle_copilot_completion_request(
6265 &copilot_lsp,
6266 vec![copilot::request::Completion {
6267 text: "one.copilot1".into(),
6268 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6269 ..Default::default()
6270 }],
6271 vec![],
6272 );
6273 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6274 cx.update_editor(|editor, cx| {
6275 assert!(editor.context_menu_visible());
6276 assert!(!editor.has_active_copilot_suggestion(cx));
6277
6278 // When hiding the context menu, the Copilot suggestion becomes visible.
6279 editor.hide_context_menu(cx);
6280 assert!(!editor.context_menu_visible());
6281 assert!(editor.has_active_copilot_suggestion(cx));
6282 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6283 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6284 });
6285
6286 // Ensure existing completion is interpolated when inserting again.
6287 cx.simulate_keystroke("c");
6288 deterministic.run_until_parked();
6289 cx.update_editor(|editor, cx| {
6290 assert!(!editor.context_menu_visible());
6291 assert!(editor.has_active_copilot_suggestion(cx));
6292 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6293 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6294 });
6295
6296 // After debouncing, new Copilot completions should be requested.
6297 handle_copilot_completion_request(
6298 &copilot_lsp,
6299 vec![copilot::request::Completion {
6300 text: "one.copilot2".into(),
6301 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6302 ..Default::default()
6303 }],
6304 vec![],
6305 );
6306 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6307 cx.update_editor(|editor, cx| {
6308 assert!(!editor.context_menu_visible());
6309 assert!(editor.has_active_copilot_suggestion(cx));
6310 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6311 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6312
6313 // Canceling should remove the active Copilot suggestion.
6314 editor.cancel(&Default::default(), cx);
6315 assert!(!editor.has_active_copilot_suggestion(cx));
6316 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6317 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6318
6319 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6320 editor.tab(&Default::default(), cx);
6321 assert!(!editor.has_active_copilot_suggestion(cx));
6322 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6323 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6324
6325 // When undoing the previously active suggestion is shown again.
6326 editor.undo(&Default::default(), cx);
6327 assert!(editor.has_active_copilot_suggestion(cx));
6328 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6329 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6330 });
6331
6332 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6333 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6334 cx.update_editor(|editor, cx| {
6335 assert!(editor.has_active_copilot_suggestion(cx));
6336 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6337 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6338
6339 // Tabbing when there is an active suggestion inserts it.
6340 editor.tab(&Default::default(), cx);
6341 assert!(!editor.has_active_copilot_suggestion(cx));
6342 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6343 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6344
6345 // When undoing the previously active suggestion is shown again.
6346 editor.undo(&Default::default(), cx);
6347 assert!(editor.has_active_copilot_suggestion(cx));
6348 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6349 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6350
6351 // Hide suggestion.
6352 editor.cancel(&Default::default(), cx);
6353 assert!(!editor.has_active_copilot_suggestion(cx));
6354 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6355 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6356 });
6357
6358 // If an edit occurs outside of this editor but no suggestion is being shown,
6359 // we won't make it visible.
6360 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6361 cx.update_editor(|editor, cx| {
6362 assert!(!editor.has_active_copilot_suggestion(cx));
6363 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6364 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6365 });
6366
6367 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6368 cx.update_editor(|editor, cx| {
6369 editor.set_text("fn foo() {\n \n}", cx);
6370 editor.change_selections(None, cx, |s| {
6371 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6372 });
6373 });
6374 handle_copilot_completion_request(
6375 &copilot_lsp,
6376 vec![copilot::request::Completion {
6377 text: " let x = 4;".into(),
6378 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6379 ..Default::default()
6380 }],
6381 vec![],
6382 );
6383
6384 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6385 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6386 cx.update_editor(|editor, cx| {
6387 assert!(editor.has_active_copilot_suggestion(cx));
6388 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6389 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6390
6391 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6392 editor.tab(&Default::default(), cx);
6393 assert!(editor.has_active_copilot_suggestion(cx));
6394 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6395 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6396
6397 // Tabbing again accepts the suggestion.
6398 editor.tab(&Default::default(), cx);
6399 assert!(!editor.has_active_copilot_suggestion(cx));
6400 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6401 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6402 });
6403}
6404
6405#[gpui::test]
6406async fn test_copilot_completion_invalidation(
6407 deterministic: Arc<Deterministic>,
6408 cx: &mut gpui::TestAppContext,
6409) {
6410 init_test(cx, |_| {});
6411
6412 let (copilot, copilot_lsp) = Copilot::fake(cx);
6413 cx.update(|cx| cx.set_global(copilot));
6414 let mut cx = EditorLspTestContext::new_rust(
6415 lsp::ServerCapabilities {
6416 completion_provider: Some(lsp::CompletionOptions {
6417 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6418 ..Default::default()
6419 }),
6420 ..Default::default()
6421 },
6422 cx,
6423 )
6424 .await;
6425
6426 cx.set_state(indoc! {"
6427 one
6428 twˇ
6429 three
6430 "});
6431
6432 handle_copilot_completion_request(
6433 &copilot_lsp,
6434 vec![copilot::request::Completion {
6435 text: "two.foo()".into(),
6436 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6437 ..Default::default()
6438 }],
6439 vec![],
6440 );
6441 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6442 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6443 cx.update_editor(|editor, cx| {
6444 assert!(editor.has_active_copilot_suggestion(cx));
6445 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6446 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6447
6448 editor.backspace(&Default::default(), cx);
6449 assert!(editor.has_active_copilot_suggestion(cx));
6450 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6451 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6452
6453 editor.backspace(&Default::default(), cx);
6454 assert!(editor.has_active_copilot_suggestion(cx));
6455 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6456 assert_eq!(editor.text(cx), "one\n\nthree\n");
6457
6458 // Deleting across the original suggestion range invalidates it.
6459 editor.backspace(&Default::default(), cx);
6460 assert!(!editor.has_active_copilot_suggestion(cx));
6461 assert_eq!(editor.display_text(cx), "one\nthree\n");
6462 assert_eq!(editor.text(cx), "one\nthree\n");
6463
6464 // Undoing the deletion restores the suggestion.
6465 editor.undo(&Default::default(), cx);
6466 assert!(editor.has_active_copilot_suggestion(cx));
6467 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6468 assert_eq!(editor.text(cx), "one\n\nthree\n");
6469 });
6470}
6471
6472#[gpui::test]
6473async fn test_copilot_multibuffer(
6474 deterministic: Arc<Deterministic>,
6475 cx: &mut gpui::TestAppContext,
6476) {
6477 init_test(cx, |_| {});
6478
6479 let (copilot, copilot_lsp) = Copilot::fake(cx);
6480 cx.update(|cx| cx.set_global(copilot));
6481
6482 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6483 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6484 let multibuffer = cx.add_model(|cx| {
6485 let mut multibuffer = MultiBuffer::new(0);
6486 multibuffer.push_excerpts(
6487 buffer_1.clone(),
6488 [ExcerptRange {
6489 context: Point::new(0, 0)..Point::new(2, 0),
6490 primary: None,
6491 }],
6492 cx,
6493 );
6494 multibuffer.push_excerpts(
6495 buffer_2.clone(),
6496 [ExcerptRange {
6497 context: Point::new(0, 0)..Point::new(2, 0),
6498 primary: None,
6499 }],
6500 cx,
6501 );
6502 multibuffer
6503 });
6504 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6505
6506 handle_copilot_completion_request(
6507 &copilot_lsp,
6508 vec![copilot::request::Completion {
6509 text: "b = 2 + a".into(),
6510 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6511 ..Default::default()
6512 }],
6513 vec![],
6514 );
6515 editor.update(cx, |editor, cx| {
6516 // Ensure copilot suggestions are shown for the first excerpt.
6517 editor.change_selections(None, cx, |s| {
6518 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6519 });
6520 editor.next_copilot_suggestion(&Default::default(), cx);
6521 });
6522 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6523 editor.update(cx, |editor, cx| {
6524 assert!(editor.has_active_copilot_suggestion(cx));
6525 assert_eq!(
6526 editor.display_text(cx),
6527 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6528 );
6529 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6530 });
6531
6532 handle_copilot_completion_request(
6533 &copilot_lsp,
6534 vec![copilot::request::Completion {
6535 text: "d = 4 + c".into(),
6536 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
6537 ..Default::default()
6538 }],
6539 vec![],
6540 );
6541 editor.update(cx, |editor, cx| {
6542 // Move to another excerpt, ensuring the suggestion gets cleared.
6543 editor.change_selections(None, cx, |s| {
6544 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
6545 });
6546 assert!(!editor.has_active_copilot_suggestion(cx));
6547 assert_eq!(
6548 editor.display_text(cx),
6549 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
6550 );
6551 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6552
6553 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
6554 editor.handle_input(" ", cx);
6555 assert!(!editor.has_active_copilot_suggestion(cx));
6556 assert_eq!(
6557 editor.display_text(cx),
6558 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
6559 );
6560 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6561 });
6562
6563 // Ensure the new suggestion is displayed when the debounce timeout expires.
6564 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6565 editor.update(cx, |editor, cx| {
6566 assert!(editor.has_active_copilot_suggestion(cx));
6567 assert_eq!(
6568 editor.display_text(cx),
6569 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
6570 );
6571 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6572 });
6573}
6574
6575#[gpui::test]
6576async fn test_copilot_disabled_globs(
6577 deterministic: Arc<Deterministic>,
6578 cx: &mut gpui::TestAppContext,
6579) {
6580 init_test(cx, |settings| {
6581 settings
6582 .copilot
6583 .get_or_insert(Default::default())
6584 .disabled_globs = Some(vec![".env*".to_string()]);
6585 });
6586
6587 let (copilot, copilot_lsp) = Copilot::fake(cx);
6588 cx.update(|cx| cx.set_global(copilot));
6589
6590 let fs = FakeFs::new(cx.background());
6591 fs.insert_tree(
6592 "/test",
6593 json!({
6594 ".env": "SECRET=something\n",
6595 "README.md": "hello\n"
6596 }),
6597 )
6598 .await;
6599 let project = Project::test(fs, ["/test".as_ref()], cx).await;
6600
6601 let private_buffer = project
6602 .update(cx, |project, cx| {
6603 project.open_local_buffer("/test/.env", cx)
6604 })
6605 .await
6606 .unwrap();
6607 let public_buffer = project
6608 .update(cx, |project, cx| {
6609 project.open_local_buffer("/test/README.md", cx)
6610 })
6611 .await
6612 .unwrap();
6613
6614 let multibuffer = cx.add_model(|cx| {
6615 let mut multibuffer = MultiBuffer::new(0);
6616 multibuffer.push_excerpts(
6617 private_buffer.clone(),
6618 [ExcerptRange {
6619 context: Point::new(0, 0)..Point::new(1, 0),
6620 primary: None,
6621 }],
6622 cx,
6623 );
6624 multibuffer.push_excerpts(
6625 public_buffer.clone(),
6626 [ExcerptRange {
6627 context: Point::new(0, 0)..Point::new(1, 0),
6628 primary: None,
6629 }],
6630 cx,
6631 );
6632 multibuffer
6633 });
6634 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6635
6636 let mut copilot_requests = copilot_lsp
6637 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
6638 Ok(copilot::request::GetCompletionsResult {
6639 completions: vec![copilot::request::Completion {
6640 text: "next line".into(),
6641 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6642 ..Default::default()
6643 }],
6644 })
6645 });
6646
6647 editor.update(cx, |editor, cx| {
6648 editor.change_selections(None, cx, |selections| {
6649 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
6650 });
6651 editor.next_copilot_suggestion(&Default::default(), cx);
6652 });
6653
6654 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6655 assert!(copilot_requests.try_next().is_err());
6656
6657 editor.update(cx, |editor, cx| {
6658 editor.change_selections(None, cx, |s| {
6659 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
6660 });
6661 editor.next_copilot_suggestion(&Default::default(), cx);
6662 });
6663
6664 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6665 assert!(copilot_requests.try_next().is_ok());
6666}
6667
6668fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
6669 let point = DisplayPoint::new(row as u32, column as u32);
6670 point..point
6671}
6672
6673fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
6674 let (text, ranges) = marked_text_ranges(marked_text, true);
6675 assert_eq!(view.text(cx), text);
6676 assert_eq!(
6677 view.selections.ranges(cx),
6678 ranges,
6679 "Assert selections are {}",
6680 marked_text
6681 );
6682}
6683
6684/// Handle completion request passing a marked string specifying where the completion
6685/// should be triggered from using '|' character, what range should be replaced, and what completions
6686/// should be returned using '<' and '>' to delimit the range
6687fn handle_completion_request<'a>(
6688 cx: &mut EditorLspTestContext<'a>,
6689 marked_string: &str,
6690 completions: Vec<&'static str>,
6691) -> impl Future<Output = ()> {
6692 let complete_from_marker: TextRangeMarker = '|'.into();
6693 let replace_range_marker: TextRangeMarker = ('<', '>').into();
6694 let (_, mut marked_ranges) = marked_text_ranges_by(
6695 marked_string,
6696 vec![complete_from_marker.clone(), replace_range_marker.clone()],
6697 );
6698
6699 let complete_from_position =
6700 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
6701 let replace_range =
6702 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
6703
6704 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
6705 let completions = completions.clone();
6706 async move {
6707 assert_eq!(params.text_document_position.text_document.uri, url.clone());
6708 assert_eq!(
6709 params.text_document_position.position,
6710 complete_from_position
6711 );
6712 Ok(Some(lsp::CompletionResponse::Array(
6713 completions
6714 .iter()
6715 .map(|completion_text| lsp::CompletionItem {
6716 label: completion_text.to_string(),
6717 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6718 range: replace_range,
6719 new_text: completion_text.to_string(),
6720 })),
6721 ..Default::default()
6722 })
6723 .collect(),
6724 )))
6725 }
6726 });
6727
6728 async move {
6729 request.next().await;
6730 }
6731}
6732
6733fn handle_resolve_completion_request<'a>(
6734 cx: &mut EditorLspTestContext<'a>,
6735 edits: Option<Vec<(&'static str, &'static str)>>,
6736) -> impl Future<Output = ()> {
6737 let edits = edits.map(|edits| {
6738 edits
6739 .iter()
6740 .map(|(marked_string, new_text)| {
6741 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
6742 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
6743 lsp::TextEdit::new(replace_range, new_text.to_string())
6744 })
6745 .collect::<Vec<_>>()
6746 });
6747
6748 let mut request =
6749 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
6750 let edits = edits.clone();
6751 async move {
6752 Ok(lsp::CompletionItem {
6753 additional_text_edits: edits,
6754 ..Default::default()
6755 })
6756 }
6757 });
6758
6759 async move {
6760 request.next().await;
6761 }
6762}
6763
6764fn handle_copilot_completion_request(
6765 lsp: &lsp::FakeLanguageServer,
6766 completions: Vec<copilot::request::Completion>,
6767 completions_cycling: Vec<copilot::request::Completion>,
6768) {
6769 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
6770 let completions = completions.clone();
6771 async move {
6772 Ok(copilot::request::GetCompletionsResult {
6773 completions: completions.clone(),
6774 })
6775 }
6776 });
6777 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
6778 let completions_cycling = completions_cycling.clone();
6779 async move {
6780 Ok(copilot::request::GetCompletionsResult {
6781 completions: completions_cycling.clone(),
6782 })
6783 }
6784 });
6785}
6786
6787pub(crate) fn update_test_settings(
6788 cx: &mut TestAppContext,
6789 f: impl Fn(&mut AllLanguageSettingsContent),
6790) {
6791 cx.update(|cx| {
6792 cx.update_global::<SettingsStore, _, _>(|store, cx| {
6793 store.update_user_settings::<AllLanguageSettings>(cx, f);
6794 });
6795 });
6796}
6797
6798pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
6799 cx.foreground().forbid_parking();
6800
6801 cx.update(|cx| {
6802 cx.set_global(SettingsStore::test(cx));
6803 theme::init((), cx);
6804 client::init_settings(cx);
6805 language::init(cx);
6806 Project::init_settings(cx);
6807 workspace::init_settings(cx);
6808 crate::init(cx);
6809 });
6810
6811 update_test_settings(cx, f);
6812}