state.rs

  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}