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