1use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt};
2use gpui::{impl_actions, Context, Window};
3use language::{Bias, SelectionGoal};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use std::cmp;
7
8use crate::{
9 state::{Mode, Register},
10 Vim,
11};
12
13#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
14#[serde(rename_all = "camelCase")]
15pub struct Paste {
16 #[serde(default)]
17 before: bool,
18 #[serde(default)]
19 preserve_clipboard: bool,
20}
21
22impl_actions!(vim, [Paste]);
23
24impl Vim {
25 pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context<Self>) {
26 self.record_current_action(cx);
27 self.store_visual_marks(window, cx);
28 let count = Vim::take_count(cx).unwrap_or(1);
29
30 self.update_editor(window, cx, |vim, editor, window, cx| {
31 let text_layout_details = editor.text_layout_details(window);
32 editor.transact(window, cx, |editor, window, cx| {
33 editor.set_clip_at_line_ends(false, cx);
34
35 let selected_register = vim.selected_register.take();
36
37 let Some(Register {
38 text,
39 clipboard_selections,
40 }) = Vim::update_globals(cx, |globals, cx| {
41 globals.read_register(selected_register, Some(editor), cx)
42 })
43 .filter(|reg| !reg.text.is_empty())
44 else {
45 return;
46 };
47 let clipboard_selections = clipboard_selections
48 .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
49
50 if !action.preserve_clipboard && vim.mode.is_visual() {
51 vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, cx);
52 }
53
54 let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
55
56 // unlike zed, if you have a multi-cursor selection from vim block mode,
57 // pasting it will paste it on subsequent lines, even if you don't yet
58 // have a cursor there.
59 let mut selections_to_process = Vec::new();
60 let mut i = 0;
61 while i < current_selections.len() {
62 selections_to_process
63 .push((current_selections[i].start..current_selections[i].end, true));
64 i += 1;
65 }
66 if let Some(clipboard_selections) = clipboard_selections.as_ref() {
67 let left = current_selections
68 .iter()
69 .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
70 .min()
71 .unwrap();
72 let mut row = current_selections.last().unwrap().end.row().next_row();
73 while i < clipboard_selections.len() {
74 let cursor =
75 display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
76 selections_to_process.push((cursor..cursor, false));
77 i += 1;
78 row.0 += 1;
79 }
80 }
81
82 let first_selection_indent_column =
83 clipboard_selections.as_ref().and_then(|zed_selections| {
84 zed_selections
85 .first()
86 .map(|selection| selection.first_line_indent)
87 });
88 let before = action.before || vim.mode == Mode::VisualLine;
89
90 let mut edits = Vec::new();
91 let mut new_selections = Vec::new();
92 let mut original_indent_columns = Vec::new();
93 let mut start_offset = 0;
94
95 for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
96 let (mut to_insert, original_indent_column) =
97 if let Some(clipboard_selections) = &clipboard_selections {
98 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
99 let end_offset = start_offset + clipboard_selection.len;
100 let text = text[start_offset..end_offset].to_string();
101 start_offset = end_offset + 1;
102 (text, Some(clipboard_selection.first_line_indent))
103 } else {
104 ("".to_string(), first_selection_indent_column)
105 }
106 } else {
107 (text.to_string(), first_selection_indent_column)
108 };
109 let line_mode = to_insert.ends_with('\n');
110 let is_multiline = to_insert.contains('\n');
111
112 if line_mode && !before {
113 if selection.is_empty() {
114 to_insert =
115 "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
116 } else {
117 to_insert = "\n".to_owned() + &to_insert;
118 }
119 } else if !line_mode && vim.mode == Mode::VisualLine {
120 to_insert += "\n";
121 }
122
123 let display_range = if !selection.is_empty() {
124 selection.start..selection.end
125 } else if line_mode {
126 let point = if before {
127 movement::line_beginning(&display_map, selection.start, false)
128 } else {
129 movement::line_end(&display_map, selection.start, false)
130 };
131 point..point
132 } else {
133 let point = if before {
134 selection.start
135 } else {
136 movement::saturating_right(&display_map, selection.start)
137 };
138 point..point
139 };
140
141 let point_range = display_range.start.to_point(&display_map)
142 ..display_range.end.to_point(&display_map);
143 let anchor = if is_multiline || vim.mode == Mode::VisualLine {
144 display_map.buffer_snapshot.anchor_before(point_range.start)
145 } else {
146 display_map.buffer_snapshot.anchor_after(point_range.end)
147 };
148
149 if *preserve {
150 new_selections.push((anchor, line_mode, is_multiline));
151 }
152 edits.push((point_range, to_insert.repeat(count)));
153 original_indent_columns.extend(original_indent_column);
154 }
155
156 editor.edit_with_block_indent(edits, original_indent_columns, cx);
157
158 // in line_mode vim will insert the new text on the next (or previous if before) line
159 // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
160 // otherwise vim will insert the next text at (or before) the current cursor position,
161 // the cursor will go to the last (or first, if is_multiline) inserted character.
162 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
163 s.replace_cursors_with(|map| {
164 let mut cursors = Vec::new();
165 for (anchor, line_mode, is_multiline) in &new_selections {
166 let mut cursor = anchor.to_display_point(map);
167 if *line_mode {
168 if !before {
169 cursor = movement::down(
170 map,
171 cursor,
172 SelectionGoal::None,
173 false,
174 &text_layout_details,
175 )
176 .0;
177 }
178 cursor = movement::indented_line_beginning(map, cursor, true);
179 } else if !is_multiline && !vim.temp_mode {
180 cursor = movement::saturating_left(map, cursor)
181 }
182 cursors.push(cursor);
183 if vim.mode == Mode::VisualBlock {
184 break;
185 }
186 }
187
188 cursors
189 });
190 })
191 });
192 });
193 self.switch_mode(Mode::Normal, true, window, cx);
194 }
195}
196
197#[cfg(test)]
198mod test {
199 use crate::{
200 state::Mode,
201 test::{NeovimBackedTestContext, VimTestContext},
202 UseSystemClipboard, VimSettings,
203 };
204 use gpui::ClipboardItem;
205 use indoc::indoc;
206 use settings::SettingsStore;
207
208 #[gpui::test]
209 async fn test_paste(cx: &mut gpui::TestAppContext) {
210 let mut cx = NeovimBackedTestContext::new(cx).await;
211
212 // single line
213 cx.set_shared_state(indoc! {"
214 The quick brown
215 fox ˇjumps over
216 the lazy dog"})
217 .await;
218 cx.simulate_shared_keystrokes("v w y").await;
219 cx.shared_clipboard().await.assert_eq("jumps o");
220 cx.set_shared_state(indoc! {"
221 The quick brown
222 fox jumps oveˇr
223 the lazy dog"})
224 .await;
225 cx.simulate_shared_keystrokes("p").await;
226 cx.shared_state().await.assert_eq(indoc! {"
227 The quick brown
228 fox jumps overjumps ˇo
229 the lazy dog"});
230
231 cx.set_shared_state(indoc! {"
232 The quick brown
233 fox jumps oveˇr
234 the lazy dog"})
235 .await;
236 cx.simulate_shared_keystrokes("shift-p").await;
237 cx.shared_state().await.assert_eq(indoc! {"
238 The quick brown
239 fox jumps ovejumps ˇor
240 the lazy dog"});
241
242 // line mode
243 cx.set_shared_state(indoc! {"
244 The quick brown
245 fox juˇmps over
246 the lazy dog"})
247 .await;
248 cx.simulate_shared_keystrokes("d d").await;
249 cx.shared_clipboard().await.assert_eq("fox jumps over\n");
250 cx.shared_state().await.assert_eq(indoc! {"
251 The quick brown
252 the laˇzy dog"});
253 cx.simulate_shared_keystrokes("p").await;
254 cx.shared_state().await.assert_eq(indoc! {"
255 The quick brown
256 the lazy dog
257 ˇfox jumps over"});
258 cx.simulate_shared_keystrokes("k shift-p").await;
259 cx.shared_state().await.assert_eq(indoc! {"
260 The quick brown
261 ˇfox jumps over
262 the lazy dog
263 fox jumps over"});
264
265 // multiline, cursor to first character of pasted text.
266 cx.set_shared_state(indoc! {"
267 The quick brown
268 fox jumps ˇover
269 the lazy dog"})
270 .await;
271 cx.simulate_shared_keystrokes("v j y").await;
272 cx.shared_clipboard().await.assert_eq("over\nthe lazy do");
273
274 cx.simulate_shared_keystrokes("p").await;
275 cx.shared_state().await.assert_eq(indoc! {"
276 The quick brown
277 fox jumps oˇover
278 the lazy dover
279 the lazy dog"});
280 cx.simulate_shared_keystrokes("u shift-p").await;
281 cx.shared_state().await.assert_eq(indoc! {"
282 The quick brown
283 fox jumps ˇover
284 the lazy doover
285 the lazy dog"});
286 }
287
288 #[gpui::test]
289 async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) {
290 let mut cx = VimTestContext::new(cx, true).await;
291
292 cx.update_global(|store: &mut SettingsStore, cx| {
293 store.update_user_settings::<VimSettings>(cx, |s| {
294 s.use_system_clipboard = Some(UseSystemClipboard::Never)
295 });
296 });
297
298 cx.set_state(
299 indoc! {"
300 The quick brown
301 fox jˇumps over
302 the lazy dog"},
303 Mode::Normal,
304 );
305 cx.simulate_keystrokes("v i w y");
306 cx.assert_state(
307 indoc! {"
308 The quick brown
309 fox ˇjumps over
310 the lazy dog"},
311 Mode::Normal,
312 );
313 cx.simulate_keystrokes("p");
314 cx.assert_state(
315 indoc! {"
316 The quick brown
317 fox jjumpˇsumps over
318 the lazy dog"},
319 Mode::Normal,
320 );
321 assert_eq!(cx.read_from_clipboard(), None);
322 }
323
324 #[gpui::test]
325 async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) {
326 let mut cx = VimTestContext::new(cx, true).await;
327
328 cx.update_global(|store: &mut SettingsStore, cx| {
329 store.update_user_settings::<VimSettings>(cx, |s| {
330 s.use_system_clipboard = Some(UseSystemClipboard::OnYank)
331 });
332 });
333
334 // copy in visual mode
335 cx.set_state(
336 indoc! {"
337 The quick brown
338 fox jˇumps over
339 the lazy dog"},
340 Mode::Normal,
341 );
342 cx.simulate_keystrokes("v i w y");
343 cx.assert_state(
344 indoc! {"
345 The quick brown
346 fox ˇjumps over
347 the lazy dog"},
348 Mode::Normal,
349 );
350 cx.simulate_keystrokes("p");
351 cx.assert_state(
352 indoc! {"
353 The quick brown
354 fox jjumpˇsumps over
355 the lazy dog"},
356 Mode::Normal,
357 );
358 assert_eq!(
359 cx.read_from_clipboard()
360 .map(|item| item.text().unwrap().to_string()),
361 Some("jumps".into())
362 );
363 cx.simulate_keystrokes("d d p");
364 cx.assert_state(
365 indoc! {"
366 The quick brown
367 the lazy dog
368 ˇfox jjumpsumps over"},
369 Mode::Normal,
370 );
371 assert_eq!(
372 cx.read_from_clipboard()
373 .map(|item| item.text().unwrap().to_string()),
374 Some("jumps".into())
375 );
376 cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string()));
377 cx.simulate_keystrokes("shift-p");
378 cx.assert_state(
379 indoc! {"
380 The quick brown
381 the lazy dog
382 test-copˇyfox jjumpsumps over"},
383 Mode::Normal,
384 );
385 }
386
387 #[gpui::test]
388 async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
389 let mut cx = NeovimBackedTestContext::new(cx).await;
390
391 // copy in visual mode
392 cx.set_shared_state(indoc! {"
393 The quick brown
394 fox jˇumps over
395 the lazy dog"})
396 .await;
397 cx.simulate_shared_keystrokes("v i w y").await;
398 cx.shared_state().await.assert_eq(indoc! {"
399 The quick brown
400 fox ˇjumps over
401 the lazy dog"});
402 // paste in visual mode
403 cx.simulate_shared_keystrokes("w v i w p").await;
404 cx.shared_state().await.assert_eq(indoc! {"
405 The quick brown
406 fox jumps jumpˇs
407 the lazy dog"});
408 cx.shared_clipboard().await.assert_eq("over");
409 // paste in visual line mode
410 cx.simulate_shared_keystrokes("up shift-v shift-p").await;
411 cx.shared_state().await.assert_eq(indoc! {"
412 ˇover
413 fox jumps jumps
414 the lazy dog"});
415 cx.shared_clipboard().await.assert_eq("over");
416 // paste in visual block mode
417 cx.simulate_shared_keystrokes("ctrl-v down down p").await;
418 cx.shared_state().await.assert_eq(indoc! {"
419 oveˇrver
420 overox jumps jumps
421 overhe lazy dog"});
422
423 // copy in visual line mode
424 cx.set_shared_state(indoc! {"
425 The quick brown
426 fox juˇmps over
427 the lazy dog"})
428 .await;
429 cx.simulate_shared_keystrokes("shift-v d").await;
430 cx.shared_state().await.assert_eq(indoc! {"
431 The quick brown
432 the laˇzy dog"});
433 // paste in visual mode
434 cx.simulate_shared_keystrokes("v i w p").await;
435 cx.shared_state().await.assert_eq(indoc! {"
436 The quick brown
437 the•
438 ˇfox jumps over
439 dog"});
440 cx.shared_clipboard().await.assert_eq("lazy");
441 cx.set_shared_state(indoc! {"
442 The quick brown
443 fox juˇmps over
444 the lazy dog"})
445 .await;
446 cx.simulate_shared_keystrokes("shift-v d").await;
447 cx.shared_state().await.assert_eq(indoc! {"
448 The quick brown
449 the laˇzy dog"});
450 // paste in visual line mode
451 cx.simulate_shared_keystrokes("k shift-v p").await;
452 cx.shared_state().await.assert_eq(indoc! {"
453 ˇfox jumps over
454 the lazy dog"});
455 cx.shared_clipboard().await.assert_eq("The quick brown\n");
456 }
457
458 #[gpui::test]
459 async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
460 let mut cx = NeovimBackedTestContext::new(cx).await;
461 // copy in visual block mode
462 cx.set_shared_state(indoc! {"
463 The ˇquick brown
464 fox jumps over
465 the lazy dog"})
466 .await;
467 cx.simulate_shared_keystrokes("ctrl-v 2 j y").await;
468 cx.shared_clipboard().await.assert_eq("q\nj\nl");
469 cx.simulate_shared_keystrokes("p").await;
470 cx.shared_state().await.assert_eq(indoc! {"
471 The qˇquick brown
472 fox jjumps over
473 the llazy dog"});
474 cx.simulate_shared_keystrokes("v i w shift-p").await;
475 cx.shared_state().await.assert_eq(indoc! {"
476 The ˇq brown
477 fox jjjumps over
478 the lllazy dog"});
479 cx.simulate_shared_keystrokes("v i w shift-p").await;
480
481 cx.set_shared_state(indoc! {"
482 The ˇquick brown
483 fox jumps over
484 the lazy dog"})
485 .await;
486 cx.simulate_shared_keystrokes("ctrl-v j y").await;
487 cx.shared_clipboard().await.assert_eq("q\nj");
488 cx.simulate_shared_keystrokes("l ctrl-v 2 j shift-p").await;
489 cx.shared_state().await.assert_eq(indoc! {"
490 The qˇqick brown
491 fox jjmps over
492 the lzy dog"});
493
494 cx.simulate_shared_keystrokes("shift-v p").await;
495 cx.shared_state().await.assert_eq(indoc! {"
496 ˇq
497 j
498 fox jjmps over
499 the lzy dog"});
500 }
501
502 #[gpui::test]
503 async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
504 let mut cx = VimTestContext::new_typescript(cx).await;
505
506 cx.set_state(
507 indoc! {"
508 class A {ˇ
509 }
510 "},
511 Mode::Normal,
512 );
513 cx.simulate_keystrokes("o a ( ) { escape");
514 cx.assert_state(
515 indoc! {"
516 class A {
517 a()ˇ{}
518 }
519 "},
520 Mode::Normal,
521 );
522 // cursor goes to the first non-blank character in the line;
523 cx.simulate_keystrokes("y y p");
524 cx.assert_state(
525 indoc! {"
526 class A {
527 a(){}
528 ˇa(){}
529 }
530 "},
531 Mode::Normal,
532 );
533 // indentation is preserved when pasting
534 cx.simulate_keystrokes("u shift-v up y shift-p");
535 cx.assert_state(
536 indoc! {"
537 ˇclass A {
538 a(){}
539 class A {
540 a(){}
541 }
542 "},
543 Mode::Normal,
544 );
545 }
546
547 #[gpui::test]
548 async fn test_paste_count(cx: &mut gpui::TestAppContext) {
549 let mut cx = NeovimBackedTestContext::new(cx).await;
550
551 cx.set_shared_state(indoc! {"
552 onˇe
553 two
554 three
555 "})
556 .await;
557 cx.simulate_shared_keystrokes("y y 3 p").await;
558 cx.shared_state().await.assert_eq(indoc! {"
559 one
560 ˇone
561 one
562 one
563 two
564 three
565 "});
566
567 cx.set_shared_state(indoc! {"
568 one
569 ˇtwo
570 three
571 "})
572 .await;
573 cx.simulate_shared_keystrokes("y $ $ 3 p").await;
574 cx.shared_state().await.assert_eq(indoc! {"
575 one
576 twotwotwotwˇo
577 three
578 "});
579 }
580
581 #[gpui::test]
582 async fn test_numbered_registers(cx: &mut gpui::TestAppContext) {
583 let mut cx = NeovimBackedTestContext::new(cx).await;
584
585 cx.update_global(|store: &mut SettingsStore, cx| {
586 store.update_user_settings::<VimSettings>(cx, |s| {
587 s.use_system_clipboard = Some(UseSystemClipboard::Never)
588 });
589 });
590
591 cx.set_shared_state(indoc! {"
592 The quick brown
593 fox jˇumps over
594 the lazy dog"})
595 .await;
596 cx.simulate_shared_keystrokes("y y \" 0 p").await;
597 cx.shared_register('0').await.assert_eq("fox jumps over\n");
598 cx.shared_register('"').await.assert_eq("fox jumps over\n");
599
600 cx.shared_state().await.assert_eq(indoc! {"
601 The quick brown
602 fox jumps over
603 ˇfox jumps over
604 the lazy dog"});
605 cx.simulate_shared_keystrokes("k k d d").await;
606 cx.shared_register('0').await.assert_eq("fox jumps over\n");
607 cx.shared_register('1').await.assert_eq("The quick brown\n");
608 cx.shared_register('"').await.assert_eq("The quick brown\n");
609
610 cx.simulate_shared_keystrokes("d d shift-g d d").await;
611 cx.shared_register('0').await.assert_eq("fox jumps over\n");
612 cx.shared_register('3').await.assert_eq("The quick brown\n");
613 cx.shared_register('2').await.assert_eq("fox jumps over\n");
614 cx.shared_register('1').await.assert_eq("the lazy dog\n");
615
616 cx.shared_state().await.assert_eq(indoc! {"
617 ˇfox jumps over"});
618
619 cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await;
620 cx.set_shared_state(indoc! {"
621 The quick brown
622 fox jumps over
623 ˇthe lazy dog"})
624 .await;
625 }
626
627 #[gpui::test]
628 async fn test_named_registers(cx: &mut gpui::TestAppContext) {
629 let mut cx = NeovimBackedTestContext::new(cx).await;
630
631 cx.update_global(|store: &mut SettingsStore, cx| {
632 store.update_user_settings::<VimSettings>(cx, |s| {
633 s.use_system_clipboard = Some(UseSystemClipboard::Never)
634 });
635 });
636
637 cx.set_shared_state(indoc! {"
638 The quick brown
639 fox jˇumps over
640 the lazy dog"})
641 .await;
642 cx.simulate_shared_keystrokes("\" a d a w").await;
643 cx.shared_register('a').await.assert_eq("jumps ");
644 cx.simulate_shared_keystrokes("\" shift-a d i w").await;
645 cx.shared_register('a').await.assert_eq("jumps over");
646 cx.shared_register('"').await.assert_eq("jumps over");
647 cx.simulate_shared_keystrokes("\" a p").await;
648 cx.shared_state().await.assert_eq(indoc! {"
649 The quick brown
650 fox jumps oveˇr
651 the lazy dog"});
652 cx.simulate_shared_keystrokes("\" a d a w").await;
653 cx.shared_register('a').await.assert_eq(" over");
654 }
655
656 #[gpui::test]
657 async fn test_special_registers(cx: &mut gpui::TestAppContext) {
658 let mut cx = NeovimBackedTestContext::new(cx).await;
659
660 cx.update_global(|store: &mut SettingsStore, cx| {
661 store.update_user_settings::<VimSettings>(cx, |s| {
662 s.use_system_clipboard = Some(UseSystemClipboard::Never)
663 });
664 });
665
666 cx.set_shared_state(indoc! {"
667 The quick brown
668 fox jˇumps over
669 the lazy dog"})
670 .await;
671 cx.simulate_shared_keystrokes("d i w").await;
672 cx.shared_register('-').await.assert_eq("jumps");
673 cx.simulate_shared_keystrokes("\" _ d d").await;
674 cx.shared_register('_').await.assert_eq("");
675
676 cx.simulate_shared_keystrokes("shift-v \" _ y w").await;
677 cx.shared_register('"').await.assert_eq("jumps");
678
679 cx.shared_state().await.assert_eq(indoc! {"
680 The quick brown
681 the ˇlazy dog"});
682 cx.simulate_shared_keystrokes("\" \" d ^").await;
683 cx.shared_register('0').await.assert_eq("the ");
684 cx.shared_register('"').await.assert_eq("the ");
685
686 cx.simulate_shared_keystrokes("^ \" + d $").await;
687 cx.shared_clipboard().await.assert_eq("lazy dog");
688 cx.shared_register('"').await.assert_eq("lazy dog");
689
690 cx.simulate_shared_keystrokes("/ d o g enter").await;
691 cx.shared_register('/').await.assert_eq("dog");
692 cx.simulate_shared_keystrokes("\" / shift-p").await;
693 cx.shared_state().await.assert_eq(indoc! {"
694 The quick brown
695 doˇg"});
696
697 // not testing nvim as it doesn't have a filename
698 cx.simulate_keystrokes("\" % p");
699 cx.assert_state(
700 indoc! {"
701 The quick brown
702 dogdir/file.rˇs"},
703 Mode::Normal,
704 );
705 }
706
707 #[gpui::test]
708 async fn test_multicursor_paste(cx: &mut gpui::TestAppContext) {
709 let mut cx = VimTestContext::new(cx, true).await;
710
711 cx.update_global(|store: &mut SettingsStore, cx| {
712 store.update_user_settings::<VimSettings>(cx, |s| {
713 s.use_system_clipboard = Some(UseSystemClipboard::Never)
714 });
715 });
716
717 cx.set_state(
718 indoc! {"
719 ˇfish one
720 fish two
721 fish red
722 fish blue
723 "},
724 Mode::Normal,
725 );
726 cx.simulate_keystrokes("4 g l w escape d i w 0 shift-p");
727 cx.assert_state(
728 indoc! {"
729 onˇefish•
730 twˇofish•
731 reˇdfish•
732 bluˇefish•
733 "},
734 Mode::Normal,
735 );
736 }
737}