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