1use crate::command::command_interceptor;
2use crate::normal::repeat::Replayer;
3use crate::surrounds::SurroundsType;
4use crate::{motion::Motion, object::Object};
5use crate::{ToggleRegistersView, UseSystemClipboard, Vim, VimSettings};
6use collections::HashMap;
7use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
8use editor::display_map::{is_invisible, replacement};
9use editor::{Anchor, ClipboardSelection, Editor};
10use gpui::{
11 Action, App, BorrowAppContext, ClipboardEntry, ClipboardItem, Entity, Global, HighlightStyle,
12 StyledText, Task, TextStyle, WeakEntity,
13};
14use language::Point;
15use picker::{Picker, PickerDelegate};
16use serde::{Deserialize, Serialize};
17use settings::{Settings, SettingsStore};
18use std::borrow::BorrowMut;
19use std::{fmt::Display, ops::Range, sync::Arc};
20use theme::ThemeSettings;
21use ui::{
22 h_flex, rems, ActiveTheme, Context, Div, FluentBuilder, KeyBinding, ParentElement,
23 SharedString, Styled, StyledTypography, Window,
24};
25use workspace::searchable::Direction;
26use workspace::Workspace;
27
28#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
29pub enum Mode {
30 Normal,
31 Insert,
32 Replace,
33 Visual,
34 VisualLine,
35 VisualBlock,
36 HelixNormal,
37}
38
39impl Display for Mode {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Mode::Normal => write!(f, "NORMAL"),
43 Mode::Insert => write!(f, "INSERT"),
44 Mode::Replace => write!(f, "REPLACE"),
45 Mode::Visual => write!(f, "VISUAL"),
46 Mode::VisualLine => write!(f, "VISUAL LINE"),
47 Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
48 Mode::HelixNormal => write!(f, "HELIX NORMAL"),
49 }
50 }
51}
52
53impl Mode {
54 pub fn is_visual(&self) -> bool {
55 match self {
56 Self::Visual | Self::VisualLine | Self::VisualBlock => true,
57 Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
58 }
59 }
60}
61
62impl Default for Mode {
63 fn default() -> Self {
64 Self::Normal
65 }
66}
67
68#[derive(Clone, Debug, PartialEq)]
69pub enum Operator {
70 Change,
71 Delete,
72 Yank,
73 Replace,
74 Object {
75 around: bool,
76 },
77 FindForward {
78 before: bool,
79 },
80 FindBackward {
81 after: bool,
82 },
83 Sneak {
84 first_char: Option<char>,
85 },
86 SneakBackward {
87 first_char: Option<char>,
88 },
89 AddSurrounds {
90 // Typically no need to configure this as `SendKeystrokes` can be used - see #23088.
91 target: Option<SurroundsType>,
92 },
93 ChangeSurrounds {
94 target: Option<Object>,
95 },
96 DeleteSurrounds,
97 Mark,
98 Jump {
99 line: bool,
100 },
101 Indent,
102 Outdent,
103 AutoIndent,
104 Rewrap,
105 ShellCommand,
106 Lowercase,
107 Uppercase,
108 OppositeCase,
109 Digraph {
110 first_char: Option<char>,
111 },
112 Literal {
113 prefix: Option<String>,
114 },
115 Register,
116 RecordRegister,
117 ReplayRegister,
118 ToggleComments,
119 ReplaceWithRegister,
120 Exchange,
121}
122
123#[derive(Default, Clone, Debug)]
124pub enum RecordedSelection {
125 #[default]
126 None,
127 Visual {
128 rows: u32,
129 cols: u32,
130 },
131 SingleLine {
132 cols: u32,
133 },
134 VisualBlock {
135 rows: u32,
136 cols: u32,
137 },
138 VisualLine {
139 rows: u32,
140 },
141}
142
143#[derive(Default, Clone, Debug)]
144pub struct Register {
145 pub(crate) text: SharedString,
146 pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>,
147}
148
149impl From<Register> for ClipboardItem {
150 fn from(register: Register) -> Self {
151 if let Some(clipboard_selections) = register.clipboard_selections {
152 ClipboardItem::new_string_with_json_metadata(register.text.into(), clipboard_selections)
153 } else {
154 ClipboardItem::new_string(register.text.into())
155 }
156 }
157}
158
159impl From<ClipboardItem> for Register {
160 fn from(item: ClipboardItem) -> Self {
161 // For now, we don't store metadata for multiple entries.
162 match item.entries().first() {
163 Some(ClipboardEntry::String(value)) if item.entries().len() == 1 => Register {
164 text: value.text().to_owned().into(),
165 clipboard_selections: value.metadata_json::<Vec<ClipboardSelection>>(),
166 },
167 // For now, registers can't store images. This could change in the future.
168 _ => Register::default(),
169 }
170 }
171}
172
173impl From<String> for Register {
174 fn from(text: String) -> Self {
175 Register {
176 text: text.into(),
177 clipboard_selections: None,
178 }
179 }
180}
181
182#[derive(Default, Clone)]
183pub struct VimGlobals {
184 pub last_find: Option<Motion>,
185
186 pub dot_recording: bool,
187 pub dot_replaying: bool,
188
189 /// pre_count is the number before an operator is specified (3 in 3d2d)
190 pub pre_count: Option<usize>,
191 /// post_count is the number after an operator is specified (2 in 3d2d)
192 pub post_count: Option<usize>,
193
194 pub stop_recording_after_next_action: bool,
195 pub ignore_current_insertion: bool,
196 pub recorded_count: Option<usize>,
197 pub recording_actions: Vec<ReplayableAction>,
198 pub recorded_actions: Vec<ReplayableAction>,
199 pub recorded_selection: RecordedSelection,
200
201 pub recording_register: Option<char>,
202 pub last_recorded_register: Option<char>,
203 pub last_replayed_register: Option<char>,
204 pub replayer: Option<Replayer>,
205
206 pub last_yank: Option<SharedString>,
207 pub registers: HashMap<char, Register>,
208 pub recordings: HashMap<char, Vec<ReplayableAction>>,
209
210 pub focused_vim: Option<WeakEntity<Vim>>,
211}
212impl Global for VimGlobals {}
213
214impl VimGlobals {
215 pub(crate) fn register(cx: &mut App) {
216 cx.set_global(VimGlobals::default());
217
218 cx.observe_keystrokes(|event, _, cx| {
219 let Some(action) = event.action.as_ref().map(|action| action.boxed_clone()) else {
220 return;
221 };
222 Vim::globals(cx).observe_action(action.boxed_clone())
223 })
224 .detach();
225
226 cx.observe_new(|workspace: &mut Workspace, window, _| {
227 RegistersView::register(workspace, window);
228 })
229 .detach();
230
231 cx.observe_global::<SettingsStore>(move |cx| {
232 if Vim::enabled(cx) {
233 KeyBinding::set_vim_mode(cx, true);
234 CommandPaletteFilter::update_global(cx, |filter, _| {
235 filter.show_namespace(Vim::NAMESPACE);
236 });
237 CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
238 interceptor.set(Box::new(command_interceptor));
239 });
240 } else {
241 KeyBinding::set_vim_mode(cx, false);
242 *Vim::globals(cx) = VimGlobals::default();
243 CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
244 interceptor.clear();
245 });
246 CommandPaletteFilter::update_global(cx, |filter, _| {
247 filter.hide_namespace(Vim::NAMESPACE);
248 });
249 }
250 })
251 .detach();
252 }
253
254 pub(crate) fn write_registers(
255 &mut self,
256 content: Register,
257 register: Option<char>,
258 is_yank: bool,
259 linewise: bool,
260 cx: &mut Context<Editor>,
261 ) {
262 if let Some(register) = register {
263 let lower = register.to_lowercase().next().unwrap_or(register);
264 if lower != register {
265 let current = self.registers.entry(lower).or_default();
266 current.text = (current.text.to_string() + &content.text).into();
267 // not clear how to support appending to registers with multiple cursors
268 current.clipboard_selections.take();
269 let yanked = current.clone();
270 self.registers.insert('"', yanked);
271 } else {
272 match lower {
273 '_' | ':' | '.' | '%' | '#' | '=' | '/' => {}
274 '+' => {
275 self.registers.insert('"', content.clone());
276 cx.write_to_clipboard(content.into());
277 }
278 '*' => {
279 self.registers.insert('"', content.clone());
280 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
281 cx.write_to_primary(content.into());
282 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
283 cx.write_to_clipboard(content.into());
284 }
285 '"' => {
286 self.registers.insert('"', content.clone());
287 self.registers.insert('0', content);
288 }
289 _ => {
290 self.registers.insert('"', content.clone());
291 self.registers.insert(lower, content);
292 }
293 }
294 }
295 } else {
296 let setting = VimSettings::get_global(cx).use_system_clipboard;
297 if setting == UseSystemClipboard::Always
298 || setting == UseSystemClipboard::OnYank && is_yank
299 {
300 self.last_yank.replace(content.text.clone());
301 cx.write_to_clipboard(content.clone().into());
302 } else {
303 self.last_yank = cx
304 .read_from_clipboard()
305 .and_then(|item| item.text().map(|string| string.into()));
306 }
307
308 self.registers.insert('"', content.clone());
309 if is_yank {
310 self.registers.insert('0', content);
311 } else {
312 let contains_newline = content.text.contains('\n');
313 if !contains_newline {
314 self.registers.insert('-', content.clone());
315 }
316 if linewise || contains_newline {
317 let mut content = content;
318 for i in '1'..'8' {
319 if let Some(moved) = self.registers.insert(i, content) {
320 content = moved;
321 } else {
322 break;
323 }
324 }
325 }
326 }
327 }
328 }
329
330 pub(crate) fn read_register(
331 &self,
332 register: Option<char>,
333 editor: Option<&mut Editor>,
334 cx: &mut App,
335 ) -> Option<Register> {
336 let Some(register) = register.filter(|reg| *reg != '"') else {
337 let setting = VimSettings::get_global(cx).use_system_clipboard;
338 return match setting {
339 UseSystemClipboard::Always => cx.read_from_clipboard().map(|item| item.into()),
340 UseSystemClipboard::OnYank if self.system_clipboard_is_newer(cx) => {
341 cx.read_from_clipboard().map(|item| item.into())
342 }
343 _ => self.registers.get(&'"').cloned(),
344 };
345 };
346 let lower = register.to_lowercase().next().unwrap_or(register);
347 match lower {
348 '_' | ':' | '.' | '#' | '=' => None,
349 '+' => cx.read_from_clipboard().map(|item| item.into()),
350 '*' => {
351 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
352 {
353 cx.read_from_primary().map(|item| item.into())
354 }
355 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
356 {
357 cx.read_from_clipboard().map(|item| item.into())
358 }
359 }
360 '%' => editor.and_then(|editor| {
361 let selection = editor.selections.newest::<Point>(cx);
362 if let Some((_, buffer, _)) = editor
363 .buffer()
364 .read(cx)
365 .excerpt_containing(selection.head(), cx)
366 {
367 buffer
368 .read(cx)
369 .file()
370 .map(|file| file.path().to_string_lossy().to_string().into())
371 } else {
372 None
373 }
374 }),
375 _ => self.registers.get(&lower).cloned(),
376 }
377 }
378
379 fn system_clipboard_is_newer(&self, cx: &App) -> bool {
380 cx.read_from_clipboard().is_some_and(|item| {
381 if let Some(last_state) = &self.last_yank {
382 Some(last_state.as_ref()) != item.text().as_deref()
383 } else {
384 true
385 }
386 })
387 }
388
389 pub fn observe_action(&mut self, action: Box<dyn Action>) {
390 if self.dot_recording {
391 self.recording_actions
392 .push(ReplayableAction::Action(action.boxed_clone()));
393
394 if self.stop_recording_after_next_action {
395 self.dot_recording = false;
396 self.recorded_actions = std::mem::take(&mut self.recording_actions);
397 self.stop_recording_after_next_action = false;
398 }
399 }
400 if self.replayer.is_none() {
401 if let Some(recording_register) = self.recording_register {
402 self.recordings
403 .entry(recording_register)
404 .or_default()
405 .push(ReplayableAction::Action(action));
406 }
407 }
408 }
409
410 pub fn observe_insertion(&mut self, text: &Arc<str>, range_to_replace: Option<Range<isize>>) {
411 if self.ignore_current_insertion {
412 self.ignore_current_insertion = false;
413 return;
414 }
415 if self.dot_recording {
416 self.recording_actions.push(ReplayableAction::Insertion {
417 text: text.clone(),
418 utf16_range_to_replace: range_to_replace.clone(),
419 });
420 if self.stop_recording_after_next_action {
421 self.dot_recording = false;
422 self.recorded_actions = std::mem::take(&mut self.recording_actions);
423 self.stop_recording_after_next_action = false;
424 }
425 }
426 if let Some(recording_register) = self.recording_register {
427 self.recordings.entry(recording_register).or_default().push(
428 ReplayableAction::Insertion {
429 text: text.clone(),
430 utf16_range_to_replace: range_to_replace,
431 },
432 );
433 }
434 }
435
436 pub fn focused_vim(&self) -> Option<Entity<Vim>> {
437 self.focused_vim.as_ref().and_then(|vim| vim.upgrade())
438 }
439}
440
441impl Vim {
442 pub fn globals(cx: &mut App) -> &mut VimGlobals {
443 cx.global_mut::<VimGlobals>()
444 }
445
446 pub fn update_globals<C, R>(cx: &mut C, f: impl FnOnce(&mut VimGlobals, &mut C) -> R) -> R
447 where
448 C: BorrowMut<App>,
449 {
450 cx.update_global(f)
451 }
452}
453
454#[derive(Debug)]
455pub enum ReplayableAction {
456 Action(Box<dyn Action>),
457 Insertion {
458 text: Arc<str>,
459 utf16_range_to_replace: Option<Range<isize>>,
460 },
461}
462
463impl Clone for ReplayableAction {
464 fn clone(&self) -> Self {
465 match self {
466 Self::Action(action) => Self::Action(action.boxed_clone()),
467 Self::Insertion {
468 text,
469 utf16_range_to_replace,
470 } => Self::Insertion {
471 text: text.clone(),
472 utf16_range_to_replace: utf16_range_to_replace.clone(),
473 },
474 }
475 }
476}
477
478#[derive(Clone, Default, Debug)]
479pub struct SearchState {
480 pub direction: Direction,
481 pub count: usize,
482
483 pub prior_selections: Vec<Range<Anchor>>,
484 pub prior_operator: Option<Operator>,
485 pub prior_mode: Mode,
486}
487
488impl Operator {
489 pub fn id(&self) -> &'static str {
490 match self {
491 Operator::Object { around: false } => "i",
492 Operator::Object { around: true } => "a",
493 Operator::Change => "c",
494 Operator::Delete => "d",
495 Operator::Yank => "y",
496 Operator::Replace => "r",
497 Operator::Digraph { .. } => "^K",
498 Operator::Literal { .. } => "^V",
499 Operator::FindForward { before: false } => "f",
500 Operator::FindForward { before: true } => "t",
501 Operator::Sneak { .. } => "s",
502 Operator::SneakBackward { .. } => "S",
503 Operator::FindBackward { after: false } => "F",
504 Operator::FindBackward { after: true } => "T",
505 Operator::AddSurrounds { .. } => "ys",
506 Operator::ChangeSurrounds { .. } => "cs",
507 Operator::DeleteSurrounds => "ds",
508 Operator::Mark => "m",
509 Operator::Jump { line: true } => "'",
510 Operator::Jump { line: false } => "`",
511 Operator::Indent => ">",
512 Operator::AutoIndent => "eq",
513 Operator::ShellCommand => "sh",
514 Operator::Rewrap => "gq",
515 Operator::ReplaceWithRegister => "gr",
516 Operator::Exchange => "cx",
517 Operator::Outdent => "<",
518 Operator::Uppercase => "gU",
519 Operator::Lowercase => "gu",
520 Operator::OppositeCase => "g~",
521 Operator::Register => "\"",
522 Operator::RecordRegister => "q",
523 Operator::ReplayRegister => "@",
524 Operator::ToggleComments => "gc",
525 }
526 }
527
528 pub fn status(&self) -> String {
529 match self {
530 Operator::Digraph {
531 first_char: Some(first_char),
532 } => format!("^K{first_char}"),
533 Operator::Literal {
534 prefix: Some(prefix),
535 } => format!("^V{prefix}"),
536 Operator::AutoIndent => "=".to_string(),
537 Operator::ShellCommand => "=".to_string(),
538 _ => self.id().to_string(),
539 }
540 }
541
542 pub fn is_waiting(&self, mode: Mode) -> bool {
543 match self {
544 Operator::AddSurrounds { target } => target.is_some() || mode.is_visual(),
545 Operator::FindForward { .. }
546 | Operator::Mark
547 | Operator::Jump { .. }
548 | Operator::FindBackward { .. }
549 | Operator::Sneak { .. }
550 | Operator::SneakBackward { .. }
551 | Operator::Register
552 | Operator::RecordRegister
553 | Operator::ReplayRegister
554 | Operator::Replace
555 | Operator::Digraph { .. }
556 | Operator::Literal { .. }
557 | Operator::ChangeSurrounds { target: Some(_) }
558 | Operator::DeleteSurrounds => true,
559 Operator::Change
560 | Operator::Delete
561 | Operator::Yank
562 | Operator::Rewrap
563 | Operator::Indent
564 | Operator::Outdent
565 | Operator::AutoIndent
566 | Operator::ShellCommand
567 | Operator::Lowercase
568 | Operator::Uppercase
569 | Operator::ReplaceWithRegister
570 | Operator::Exchange
571 | Operator::Object { .. }
572 | Operator::ChangeSurrounds { target: None }
573 | Operator::OppositeCase
574 | Operator::ToggleComments => false,
575 }
576 }
577
578 pub fn starts_dot_recording(&self) -> bool {
579 match self {
580 Operator::Change
581 | Operator::Delete
582 | Operator::Replace
583 | Operator::Indent
584 | Operator::Outdent
585 | Operator::AutoIndent
586 | Operator::Lowercase
587 | Operator::Uppercase
588 | Operator::OppositeCase
589 | Operator::ToggleComments
590 | Operator::ReplaceWithRegister
591 | Operator::Rewrap
592 | Operator::ShellCommand
593 | Operator::AddSurrounds { target: None }
594 | Operator::ChangeSurrounds { target: None }
595 | Operator::DeleteSurrounds
596 | Operator::Exchange => true,
597 Operator::Yank
598 | Operator::Object { .. }
599 | Operator::FindForward { .. }
600 | Operator::FindBackward { .. }
601 | Operator::Sneak { .. }
602 | Operator::SneakBackward { .. }
603 | Operator::Mark
604 | Operator::Digraph { .. }
605 | Operator::Literal { .. }
606 | Operator::AddSurrounds { .. }
607 | Operator::ChangeSurrounds { .. }
608 | Operator::Jump { .. }
609 | Operator::Register
610 | Operator::RecordRegister
611 | Operator::ReplayRegister => false,
612 }
613 }
614}
615
616struct RegisterMatch {
617 name: char,
618 contents: SharedString,
619}
620
621pub struct RegistersViewDelegate {
622 selected_index: usize,
623 matches: Vec<RegisterMatch>,
624}
625
626impl PickerDelegate for RegistersViewDelegate {
627 type ListItem = Div;
628
629 fn match_count(&self) -> usize {
630 self.matches.len()
631 }
632
633 fn selected_index(&self) -> usize {
634 self.selected_index
635 }
636
637 fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
638 self.selected_index = ix;
639 cx.notify();
640 }
641
642 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
643 Arc::default()
644 }
645
646 fn update_matches(
647 &mut self,
648 _: String,
649 _: &mut Window,
650 _: &mut Context<Picker<Self>>,
651 ) -> gpui::Task<()> {
652 Task::ready(())
653 }
654
655 fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {}
656
657 fn dismissed(&mut self, _: &mut Window, _: &mut Context<Picker<Self>>) {}
658
659 fn render_match(
660 &self,
661 ix: usize,
662 selected: bool,
663 _: &mut Window,
664 cx: &mut Context<Picker<Self>>,
665 ) -> Option<Self::ListItem> {
666 let register_match = self
667 .matches
668 .get(ix)
669 .expect("Invalid matches state: no element for index {ix}");
670
671 let mut output = String::new();
672 let mut runs = Vec::new();
673 output.push('"');
674 output.push(register_match.name);
675 runs.push((
676 0..output.len(),
677 HighlightStyle::color(cx.theme().colors().text_accent),
678 ));
679 output.push(' ');
680 output.push(' ');
681 let mut base = output.len();
682 for (ix, c) in register_match.contents.char_indices() {
683 if ix > 100 {
684 break;
685 }
686 let replace = match c {
687 '\t' => Some("\\t".to_string()),
688 '\n' => Some("\\n".to_string()),
689 '\r' => Some("\\r".to_string()),
690 c if is_invisible(c) => {
691 if c <= '\x1f' {
692 replacement(c).map(|s| s.to_string())
693 } else {
694 Some(format!("\\u{:04X}", c as u32))
695 }
696 }
697 _ => None,
698 };
699 let Some(replace) = replace else {
700 output.push(c);
701 continue;
702 };
703 output.push_str(&replace);
704 runs.push((
705 base + ix..base + ix + replace.len(),
706 HighlightStyle::color(cx.theme().colors().text_muted),
707 ));
708 base += replace.len() - c.len_utf8();
709 }
710
711 let theme = ThemeSettings::get_global(cx);
712 let text_style = TextStyle {
713 color: cx.theme().colors().editor_foreground,
714 font_family: theme.buffer_font.family.clone(),
715 font_features: theme.buffer_font.features.clone(),
716 font_fallbacks: theme.buffer_font.fallbacks.clone(),
717 font_size: theme.buffer_font_size(cx).into(),
718 line_height: (theme.line_height() * theme.buffer_font_size(cx)).into(),
719 font_weight: theme.buffer_font.weight,
720 font_style: theme.buffer_font.style,
721 ..Default::default()
722 };
723
724 Some(
725 h_flex()
726 .when(selected, |el| el.bg(cx.theme().colors().element_selected))
727 .font_buffer(cx)
728 .text_buffer(cx)
729 .h(theme.buffer_font_size(cx) * theme.line_height())
730 .px_2()
731 .gap_1()
732 .child(StyledText::new(output).with_default_highlights(&text_style, runs)),
733 )
734 }
735}
736
737pub struct RegistersView {}
738
739impl RegistersView {
740 fn register(workspace: &mut Workspace, _window: Option<&mut Window>) {
741 workspace.register_action(|workspace, _: &ToggleRegistersView, window, cx| {
742 Self::toggle(workspace, window, cx);
743 });
744 }
745
746 pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
747 let editor = workspace
748 .active_item(cx)
749 .and_then(|item| item.act_as::<Editor>(cx));
750 workspace.toggle_modal(window, cx, move |window, cx| {
751 RegistersView::new(editor, window, cx)
752 });
753 }
754
755 fn new(
756 editor: Option<Entity<Editor>>,
757 window: &mut Window,
758 cx: &mut Context<Picker<RegistersViewDelegate>>,
759 ) -> Picker<RegistersViewDelegate> {
760 let mut matches = Vec::default();
761 cx.update_global(|globals: &mut VimGlobals, cx| {
762 for name in ['"', '+', '*'] {
763 if let Some(register) = globals.read_register(Some(name), None, cx) {
764 matches.push(RegisterMatch {
765 name,
766 contents: register.text.clone(),
767 })
768 }
769 }
770 if let Some(editor) = editor {
771 let register = editor.update(cx, |editor, cx| {
772 globals.read_register(Some('%'), Some(editor), cx)
773 });
774 if let Some(register) = register {
775 matches.push(RegisterMatch {
776 name: '%',
777 contents: register.text.clone(),
778 })
779 }
780 }
781 for (name, register) in globals.registers.iter() {
782 if ['"', '+', '*', '%'].contains(name) {
783 continue;
784 };
785 matches.push(RegisterMatch {
786 name: *name,
787 contents: register.text.clone(),
788 })
789 }
790 });
791 matches.sort_by(|a, b| a.name.cmp(&b.name));
792 let delegate = RegistersViewDelegate {
793 selected_index: 0,
794 matches,
795 };
796
797 Picker::nonsearchable_uniform_list(delegate, window, cx)
798 .width(rems(36.))
799 .modal(true)
800 }
801}