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