reovim_plugin_pickers/
keymaps.rs

1//! Keymaps picker implementation
2
3use std::{future::Future, pin::Pin};
4
5use {
6    reovim_core::bind::{CommandRef, KeyMap, KeymapScope},
7    reovim_plugin_microscope::{
8        MicroscopeAction, MicroscopeData, MicroscopeItem, Picker, PickerContext, PreviewContent,
9    },
10};
11
12/// Keymap entry for display
13#[derive(Debug, Clone)]
14pub struct KeymapEntry {
15    /// Mode name
16    pub mode: String,
17    /// Key sequence
18    pub key: String,
19    /// Command name
20    pub command: String,
21    /// Description
22    pub description: Option<String>,
23}
24
25/// Picker for viewing keymaps
26pub struct KeymapsPicker {
27    /// Available keymaps
28    keymaps: Vec<KeymapEntry>,
29}
30
31impl KeymapsPicker {
32    /// Create a new keymaps picker populated with default keymaps
33    #[must_use]
34    pub fn new() -> Self {
35        let keymap = KeyMap::with_defaults();
36        let keymaps = Self::extract_keymaps(&keymap);
37        Self { keymaps }
38    }
39
40    /// Extract all keymaps from a `KeyMap` into `KeymapEntry` items
41    fn extract_keymaps(keymap: &KeyMap) -> Vec<KeymapEntry> {
42        let mut entries = Vec::new();
43
44        for (scope, map) in keymap.iter_scopes() {
45            let mode_name = Self::scope_to_mode_name(scope);
46            for (key, inner) in map {
47                if let Some(cmd_ref) = &inner.command {
48                    let command = match cmd_ref {
49                        CommandRef::Registered(id) => id.as_str().to_string(),
50                        CommandRef::Inline(cmd) => cmd.name().to_string(),
51                    };
52                    entries.push(KeymapEntry {
53                        mode: mode_name.to_string(),
54                        key: key.to_string(),
55                        command,
56                        description: inner.description.clone(),
57                    });
58                }
59            }
60        }
61
62        // Sort by mode, then by key
63        entries.sort_by(|a, b| a.mode.cmp(&b.mode).then_with(|| a.key.cmp(&b.key)));
64
65        entries
66    }
67
68    /// Convert a `KeymapScope` to a display mode name
69    ///
70    /// Note: This uses static mode names. Plugin-provided display names
71    /// should be queried from `DisplayRegistry` when available.
72    const fn scope_to_mode_name(scope: &KeymapScope) -> &'static str {
73        use reovim_core::bind::{EditModeKind, SubModeKind};
74
75        match scope {
76            KeymapScope::Component { id: _, mode } => {
77                // Generic mode names - plugins register their own display names
78                // via DisplayRegistry. Core editor and plugin components use same names.
79                match mode {
80                    EditModeKind::Normal => "Normal",
81                    EditModeKind::Insert => "Insert",
82                    EditModeKind::Visual => "Visual",
83                }
84            }
85            KeymapScope::SubMode(submode) => match submode {
86                SubModeKind::Command => "Command",
87                SubModeKind::OperatorPending => "Operator",
88                // Generic interactor handling - plugins register their own display names
89                SubModeKind::Interactor(_) => "Interactor",
90            },
91            KeymapScope::DefaultNormal => "Default (Normal)",
92        }
93    }
94
95    /// Set keymaps from bind module
96    pub fn set_keymaps(&mut self, keymaps: Vec<KeymapEntry>) {
97        self.keymaps = keymaps;
98    }
99}
100
101impl Default for KeymapsPicker {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107impl Picker for KeymapsPicker {
108    fn name(&self) -> &'static str {
109        "keymaps"
110    }
111
112    fn title(&self) -> &'static str {
113        "Keymaps"
114    }
115
116    fn prompt(&self) -> &'static str {
117        "Keymaps> "
118    }
119
120    fn fetch(
121        &self,
122        _ctx: &PickerContext,
123    ) -> Pin<Box<dyn Future<Output = Vec<MicroscopeItem>> + Send + '_>> {
124        Box::pin(async move {
125            self.keymaps
126                .iter()
127                .map(|km| {
128                    let display = format!("[{}] {} -> {}", km.mode, km.key, km.command);
129                    MicroscopeItem::new(
130                        &display,
131                        &display,
132                        MicroscopeData::Keymap {
133                            mode: km.mode.clone(),
134                            key: km.key.clone(),
135                            command: km.command.clone(),
136                        },
137                        "keymaps",
138                    )
139                    .with_detail(km.description.as_deref().unwrap_or(""))
140                })
141                .collect()
142        })
143    }
144
145    fn on_select(&self, _item: &MicroscopeItem) -> MicroscopeAction {
146        // Keymaps are informational, just close on select
147        MicroscopeAction::Close
148    }
149
150    fn preview(
151        &self,
152        item: &MicroscopeItem,
153        _ctx: &PickerContext,
154    ) -> Pin<Box<dyn Future<Output = Option<PreviewContent>> + Send + '_>> {
155        let data = item.data.clone();
156        let description = item.detail.clone();
157
158        Box::pin(async move {
159            if let MicroscopeData::Keymap { mode, key, command } = data {
160                let mut lines = vec![
161                    "Keymap Details".to_string(),
162                    "==============".to_string(),
163                    String::new(),
164                    format!("Mode:    {mode}"),
165                    format!("Key:     {key}"),
166                    format!("Command: {command}"),
167                ];
168
169                if let Some(desc) = description {
170                    lines.push(String::new());
171                    lines.push(format!("Description: {desc}"));
172                }
173
174                Some(PreviewContent::new(lines))
175            } else {
176                None
177            }
178        })
179    }
180}