Skip to main content

fresh/input/
commands.rs

1//! Command palette system for executing editor actions by name
2
3use crate::input::keybindings::{Action, KeyContext};
4use crate::types::context_keys;
5use rust_i18n::t;
6
7/// Source of a command (builtin or from a plugin)
8#[derive(Debug, Clone, PartialEq)]
9pub enum CommandSource {
10    /// Built-in editor command
11    Builtin,
12    /// Command registered by a plugin (contains plugin filename without extension)
13    Plugin(String),
14}
15
16/// A command that can be executed from the command palette
17#[derive(Debug, Clone)]
18pub struct Command {
19    /// Command name (e.g., "Open File")
20    pub name: String,
21    /// Command description
22    pub description: String,
23    /// The action to trigger
24    pub action: Action,
25    /// Contexts where this command is available (empty = available in all contexts)
26    pub contexts: Vec<KeyContext>,
27    /// Custom contexts required for this command (plugin-defined contexts like "config-editor")
28    /// If non-empty, all custom contexts must be active for the command to be available
29    pub custom_contexts: Vec<String>,
30    /// Source of the command (builtin or plugin)
31    pub source: CommandSource,
32    /// When `true`, a key bound to this command bypasses terminal
33    /// keyboard capture: the action fires even while a terminal pane
34    /// owns the keyboard. Plugins set this via
35    /// `editor.registerCommand(..., { terminalBypass: true })` so
36    /// commands the user must always reach (orchestrator picker /
37    /// new-session form / panic-exit) aren't trapped by a focused
38    /// PTY. Built-in editor commands like `CommandPalette` rely on
39    /// `KeybindingResolver::is_terminal_ui_action` instead, so the
40    /// flag stays `false` for them and they still bypass the
41    /// existing way.
42    pub terminal_bypass: bool,
43}
44
45impl Command {
46    /// Get the localized name of the command
47    pub fn get_localized_name(&self) -> String {
48        if self.name.starts_with('%') {
49            if let CommandSource::Plugin(ref plugin_name) = self.source {
50                return crate::i18n::translate_plugin_string(
51                    plugin_name,
52                    &self.name[1..],
53                    &std::collections::HashMap::new(),
54                );
55            }
56        }
57        self.name.clone()
58    }
59
60    /// Get the localized description of the command
61    pub fn get_localized_description(&self) -> String {
62        if self.description.starts_with('%') {
63            if let CommandSource::Plugin(ref plugin_name) = self.source {
64                return crate::i18n::translate_plugin_string(
65                    plugin_name,
66                    &self.description[1..],
67                    &std::collections::HashMap::new(),
68                );
69            }
70        }
71        self.description.clone()
72    }
73}
74
75/// A single suggestion item for autocomplete
76#[derive(Debug, Clone, PartialEq)]
77pub struct Suggestion {
78    /// The text to display
79    pub text: String,
80    /// Optional description
81    pub description: Option<String>,
82    /// The value to use when selected (defaults to text if None)
83    pub value: Option<String>,
84    /// Whether this suggestion is disabled (greyed out)
85    pub disabled: bool,
86    /// Optional keyboard shortcut
87    pub keybinding: Option<String>,
88    /// Source of the command (for command palette)
89    pub source: Option<CommandSource>,
90}
91
92impl Suggestion {
93    /// Create an active (selectable) suggestion
94    pub fn new(text: String) -> Self {
95        Self {
96            text,
97            description: None,
98            value: None,
99            disabled: false,
100            keybinding: None,
101            source: None,
102        }
103    }
104
105    /// Create a disabled (greyed-out) suggestion used for hints or errors
106    pub fn disabled(text: String) -> Self {
107        Self {
108            text,
109            description: None,
110            value: None,
111            disabled: true,
112            keybinding: None,
113            source: None,
114        }
115    }
116
117    pub fn with_description(mut self, description: String) -> Self {
118        self.description = Some(description);
119        self
120    }
121
122    pub fn with_value(mut self, value: String) -> Self {
123        self.value = Some(value);
124        self
125    }
126
127    pub fn with_keybinding(mut self, keybinding: Option<String>) -> Self {
128        self.keybinding = keybinding;
129        self
130    }
131
132    pub fn with_source(mut self, source: Option<CommandSource>) -> Self {
133        self.source = source;
134        self
135    }
136
137    pub fn set_disabled(mut self, disabled: bool) -> Self {
138        self.disabled = disabled;
139        self
140    }
141
142    pub fn get_value(&self) -> &str {
143        self.value.as_ref().unwrap_or(&self.text)
144    }
145}
146
147/// Static definition of a builtin command (all data except translated strings)
148struct CommandDef {
149    name_key: &'static str,
150    desc_key: &'static str,
151    action: fn() -> Action,
152    contexts: &'static [KeyContext],
153    custom_contexts: &'static [&'static str],
154}
155
156use KeyContext::{FileExplorer, Normal, Terminal};
157
158/// All builtin command definitions as static data.
159/// Translation happens at runtime via the loop in get_all_commands().
160static COMMAND_DEFS: &[CommandDef] = &[
161    // File operations
162    CommandDef {
163        name_key: "cmd.open_file",
164        desc_key: "cmd.open_file_desc",
165        action: || Action::Open,
166        contexts: &[],
167        custom_contexts: &[],
168    },
169    CommandDef {
170        name_key: "cmd.switch_project",
171        desc_key: "cmd.switch_project_desc",
172        action: || Action::SwitchProject,
173        contexts: &[],
174        custom_contexts: &[],
175    },
176    CommandDef {
177        name_key: "cmd.save_file",
178        desc_key: "cmd.save_file_desc",
179        action: || Action::Save,
180        contexts: &[Normal],
181        custom_contexts: &[],
182    },
183    CommandDef {
184        name_key: "cmd.save_file_as",
185        desc_key: "cmd.save_file_as_desc",
186        action: || Action::SaveAs,
187        contexts: &[Normal],
188        custom_contexts: &[],
189    },
190    CommandDef {
191        name_key: "cmd.new_file",
192        desc_key: "cmd.new_file_desc",
193        action: || Action::New,
194        contexts: &[],
195        custom_contexts: &[],
196    },
197    CommandDef {
198        name_key: "cmd.close_buffer",
199        desc_key: "cmd.close_buffer_desc",
200        action: || Action::Close,
201        contexts: &[Normal, Terminal],
202        custom_contexts: &[],
203    },
204    CommandDef {
205        name_key: "cmd.close_tab",
206        desc_key: "cmd.close_tab_desc",
207        action: || Action::CloseTab,
208        contexts: &[Normal, Terminal],
209        custom_contexts: &[],
210    },
211    CommandDef {
212        name_key: "cmd.revert_file",
213        desc_key: "cmd.revert_file_desc",
214        action: || Action::Revert,
215        contexts: &[Normal],
216        custom_contexts: &[],
217    },
218    CommandDef {
219        name_key: "cmd.toggle_auto_revert",
220        desc_key: "cmd.toggle_auto_revert_desc",
221        action: || Action::ToggleAutoRevert,
222        contexts: &[],
223        custom_contexts: &[],
224    },
225    CommandDef {
226        name_key: "cmd.format_buffer",
227        desc_key: "cmd.format_buffer_desc",
228        action: || Action::FormatBuffer,
229        contexts: &[Normal],
230        custom_contexts: &[],
231    },
232    CommandDef {
233        name_key: "cmd.trim_trailing_whitespace",
234        desc_key: "cmd.trim_trailing_whitespace_desc",
235        action: || Action::TrimTrailingWhitespace,
236        contexts: &[Normal],
237        custom_contexts: &[],
238    },
239    CommandDef {
240        name_key: "cmd.ensure_final_newline",
241        desc_key: "cmd.ensure_final_newline_desc",
242        action: || Action::EnsureFinalNewline,
243        contexts: &[Normal],
244        custom_contexts: &[],
245    },
246    CommandDef {
247        name_key: "cmd.quit",
248        desc_key: "cmd.quit_desc",
249        action: || Action::Quit,
250        contexts: &[],
251        custom_contexts: &[],
252    },
253    CommandDef {
254        name_key: "cmd.detach",
255        desc_key: "cmd.detach_desc",
256        action: || Action::Detach,
257        contexts: &[],
258        custom_contexts: &[context_keys::SESSION_MODE],
259    },
260    // Quick Open variants
261    CommandDef {
262        name_key: "cmd.quick_open_buffers",
263        desc_key: "cmd.quick_open_buffers_desc",
264        action: || Action::QuickOpenBuffers,
265        contexts: &[],
266        custom_contexts: &[],
267    },
268    CommandDef {
269        name_key: "cmd.quick_open_files",
270        desc_key: "cmd.quick_open_files_desc",
271        action: || Action::QuickOpenFiles,
272        contexts: &[],
273        custom_contexts: &[],
274    },
275    // Edit operations
276    CommandDef {
277        name_key: "cmd.undo",
278        desc_key: "cmd.undo_desc",
279        action: || Action::Undo,
280        contexts: &[Normal],
281        custom_contexts: &[],
282    },
283    CommandDef {
284        name_key: "cmd.redo",
285        desc_key: "cmd.redo_desc",
286        action: || Action::Redo,
287        contexts: &[Normal],
288        custom_contexts: &[],
289    },
290    CommandDef {
291        name_key: "cmd.copy",
292        desc_key: "cmd.copy_desc",
293        action: || Action::Copy,
294        contexts: &[Normal],
295        custom_contexts: &[],
296    },
297    CommandDef {
298        name_key: "cmd.copy_with_formatting",
299        desc_key: "cmd.copy_with_formatting_desc",
300        action: || Action::CopyWithTheme(String::new()),
301        contexts: &[Normal],
302        custom_contexts: &[],
303    },
304    CommandDef {
305        name_key: "cmd.copy_file_path",
306        desc_key: "cmd.copy_file_path_desc",
307        action: || Action::CopyFilePath,
308        contexts: &[Normal],
309        custom_contexts: &[],
310    },
311    CommandDef {
312        name_key: "cmd.copy_relative_file_path",
313        desc_key: "cmd.copy_relative_file_path_desc",
314        action: || Action::CopyRelativeFilePath,
315        contexts: &[Normal],
316        custom_contexts: &[],
317    },
318    CommandDef {
319        name_key: "cmd.cut",
320        desc_key: "cmd.cut_desc",
321        action: || Action::Cut,
322        contexts: &[Normal],
323        custom_contexts: &[],
324    },
325    CommandDef {
326        name_key: "cmd.paste",
327        desc_key: "cmd.paste_desc",
328        action: || Action::Paste,
329        contexts: &[Normal],
330        custom_contexts: &[],
331    },
332    CommandDef {
333        name_key: "cmd.delete_line",
334        desc_key: "cmd.delete_line_desc",
335        action: || Action::DeleteLine,
336        contexts: &[Normal],
337        custom_contexts: &[],
338    },
339    CommandDef {
340        name_key: "cmd.delete_word_backward",
341        desc_key: "cmd.delete_word_backward_desc",
342        action: || Action::DeleteWordBackward,
343        contexts: &[Normal],
344        custom_contexts: &[],
345    },
346    CommandDef {
347        name_key: "cmd.delete_word_forward",
348        desc_key: "cmd.delete_word_forward_desc",
349        action: || Action::DeleteWordForward,
350        contexts: &[Normal],
351        custom_contexts: &[],
352    },
353    CommandDef {
354        name_key: "cmd.delete_to_end_of_line",
355        desc_key: "cmd.delete_to_end_of_line_desc",
356        action: || Action::DeleteToLineEnd,
357        contexts: &[Normal],
358        custom_contexts: &[],
359    },
360    CommandDef {
361        name_key: "cmd.transpose_characters",
362        desc_key: "cmd.transpose_characters_desc",
363        action: || Action::TransposeChars,
364        contexts: &[Normal],
365        custom_contexts: &[],
366    },
367    CommandDef {
368        name_key: "cmd.transform_uppercase",
369        desc_key: "cmd.transform_uppercase_desc",
370        action: || Action::ToUpperCase,
371        contexts: &[Normal],
372        custom_contexts: &[],
373    },
374    CommandDef {
375        name_key: "cmd.transform_lowercase",
376        desc_key: "cmd.transform_lowercase_desc",
377        action: || Action::ToLowerCase,
378        contexts: &[Normal],
379        custom_contexts: &[],
380    },
381    CommandDef {
382        name_key: "cmd.sort_lines",
383        desc_key: "cmd.sort_lines_desc",
384        action: || Action::SortLines,
385        contexts: &[Normal],
386        custom_contexts: &[],
387    },
388    CommandDef {
389        name_key: "cmd.open_line",
390        desc_key: "cmd.open_line_desc",
391        action: || Action::OpenLine,
392        contexts: &[Normal],
393        custom_contexts: &[],
394    },
395    CommandDef {
396        name_key: "cmd.duplicate_line",
397        desc_key: "cmd.duplicate_line_desc",
398        action: || Action::DuplicateLine,
399        contexts: &[Normal],
400        custom_contexts: &[],
401    },
402    CommandDef {
403        name_key: "cmd.recenter",
404        desc_key: "cmd.recenter_desc",
405        action: || Action::Recenter,
406        contexts: &[Normal],
407        custom_contexts: &[],
408    },
409    CommandDef {
410        name_key: "cmd.set_mark",
411        desc_key: "cmd.set_mark_desc",
412        action: || Action::SetMark,
413        contexts: &[Normal],
414        custom_contexts: &[],
415    },
416    // Selection
417    CommandDef {
418        name_key: "cmd.select_all",
419        desc_key: "cmd.select_all_desc",
420        action: || Action::SelectAll,
421        contexts: &[Normal],
422        custom_contexts: &[],
423    },
424    CommandDef {
425        name_key: "cmd.select_word",
426        desc_key: "cmd.select_word_desc",
427        action: || Action::SelectWord,
428        contexts: &[Normal],
429        custom_contexts: &[],
430    },
431    CommandDef {
432        name_key: "cmd.select_line",
433        desc_key: "cmd.select_line_desc",
434        action: || Action::SelectLine,
435        contexts: &[Normal],
436        custom_contexts: &[],
437    },
438    CommandDef {
439        name_key: "cmd.expand_selection",
440        desc_key: "cmd.expand_selection_desc",
441        action: || Action::ExpandSelection,
442        contexts: &[Normal],
443        custom_contexts: &[],
444    },
445    // Multi-cursor
446    CommandDef {
447        name_key: "cmd.add_cursor_above",
448        desc_key: "cmd.add_cursor_above_desc",
449        action: || Action::AddCursorAbove,
450        contexts: &[Normal],
451        custom_contexts: &[],
452    },
453    CommandDef {
454        name_key: "cmd.add_cursor_below",
455        desc_key: "cmd.add_cursor_below_desc",
456        action: || Action::AddCursorBelow,
457        contexts: &[Normal],
458        custom_contexts: &[],
459    },
460    CommandDef {
461        name_key: "cmd.add_cursor_next_match",
462        desc_key: "cmd.add_cursor_next_match_desc",
463        action: || Action::AddCursorNextMatch,
464        contexts: &[Normal],
465        custom_contexts: &[],
466    },
467    CommandDef {
468        name_key: "cmd.add_cursors_to_line_ends",
469        desc_key: "cmd.add_cursors_to_line_ends_desc",
470        action: || Action::AddCursorsToLineEnds,
471        contexts: &[Normal],
472        custom_contexts: &[],
473    },
474    CommandDef {
475        name_key: "cmd.remove_secondary_cursors",
476        desc_key: "cmd.remove_secondary_cursors_desc",
477        action: || Action::RemoveSecondaryCursors,
478        contexts: &[Normal],
479        custom_contexts: &[],
480    },
481    // Buffer navigation
482    CommandDef {
483        name_key: "cmd.next_buffer",
484        desc_key: "cmd.next_buffer_desc",
485        action: || Action::NextBuffer,
486        contexts: &[Normal, Terminal],
487        custom_contexts: &[],
488    },
489    CommandDef {
490        name_key: "cmd.previous_buffer",
491        desc_key: "cmd.previous_buffer_desc",
492        action: || Action::PrevBuffer,
493        contexts: &[Normal, Terminal],
494        custom_contexts: &[],
495    },
496    CommandDef {
497        name_key: "cmd.switch_to_previous_tab",
498        desc_key: "cmd.switch_to_previous_tab_desc",
499        action: || Action::SwitchToPreviousTab,
500        contexts: &[Normal, Terminal],
501        custom_contexts: &[],
502    },
503    CommandDef {
504        name_key: "cmd.switch_to_tab_by_name",
505        desc_key: "cmd.switch_to_tab_by_name_desc",
506        action: || Action::SwitchToTabByName,
507        contexts: &[Normal, Terminal],
508        custom_contexts: &[],
509    },
510    // Split operations
511    CommandDef {
512        name_key: "cmd.split_horizontal",
513        desc_key: "cmd.split_horizontal_desc",
514        action: || Action::SplitHorizontal,
515        contexts: &[Normal, Terminal],
516        custom_contexts: &[],
517    },
518    CommandDef {
519        name_key: "cmd.split_vertical",
520        desc_key: "cmd.split_vertical_desc",
521        action: || Action::SplitVertical,
522        contexts: &[Normal, Terminal],
523        custom_contexts: &[],
524    },
525    CommandDef {
526        name_key: "cmd.close_split",
527        desc_key: "cmd.close_split_desc",
528        action: || Action::CloseSplit,
529        contexts: &[Normal, Terminal],
530        custom_contexts: &[],
531    },
532    CommandDef {
533        name_key: "cmd.next_split",
534        desc_key: "cmd.next_split_desc",
535        action: || Action::NextSplit,
536        contexts: &[Normal, Terminal],
537        custom_contexts: &[],
538    },
539    CommandDef {
540        name_key: "cmd.previous_split",
541        desc_key: "cmd.previous_split_desc",
542        action: || Action::PrevSplit,
543        contexts: &[Normal, Terminal],
544        custom_contexts: &[],
545    },
546    CommandDef {
547        name_key: "cmd.increase_split_size",
548        desc_key: "cmd.increase_split_size_desc",
549        action: || Action::IncreaseSplitSize,
550        contexts: &[Normal, Terminal],
551        custom_contexts: &[],
552    },
553    CommandDef {
554        name_key: "cmd.decrease_split_size",
555        desc_key: "cmd.decrease_split_size_desc",
556        action: || Action::DecreaseSplitSize,
557        contexts: &[Normal, Terminal],
558        custom_contexts: &[],
559    },
560    CommandDef {
561        name_key: "cmd.toggle_maximize_split",
562        desc_key: "cmd.toggle_maximize_split_desc",
563        action: || Action::ToggleMaximizeSplit,
564        contexts: &[Normal, Terminal],
565        custom_contexts: &[],
566    },
567    // View toggles
568    CommandDef {
569        name_key: "cmd.toggle_line_numbers",
570        desc_key: "cmd.toggle_line_numbers_desc",
571        action: || Action::ToggleLineNumbers,
572        contexts: &[Normal],
573        custom_contexts: &[],
574    },
575    CommandDef {
576        name_key: "cmd.toggle_scroll_sync",
577        desc_key: "cmd.toggle_scroll_sync_desc",
578        action: || Action::ToggleScrollSync,
579        contexts: &[Normal],
580        custom_contexts: &[],
581    },
582    CommandDef {
583        name_key: "cmd.toggle_fold",
584        desc_key: "cmd.toggle_fold_desc",
585        action: || Action::ToggleFold,
586        contexts: &[Normal],
587        custom_contexts: &[],
588    },
589    CommandDef {
590        name_key: "cmd.debug_toggle_highlight",
591        desc_key: "cmd.debug_toggle_highlight_desc",
592        action: || Action::ToggleDebugHighlights,
593        contexts: &[Normal],
594        custom_contexts: &[],
595    },
596    // Rulers
597    CommandDef {
598        name_key: "cmd.add_ruler",
599        desc_key: "cmd.add_ruler_desc",
600        action: || Action::AddRuler,
601        contexts: &[Normal],
602        custom_contexts: &[],
603    },
604    CommandDef {
605        name_key: "cmd.remove_ruler",
606        desc_key: "cmd.remove_ruler_desc",
607        action: || Action::RemoveRuler,
608        contexts: &[Normal],
609        custom_contexts: &[],
610    },
611    // Buffer settings
612    CommandDef {
613        name_key: "cmd.set_tab_size",
614        desc_key: "cmd.set_tab_size_desc",
615        action: || Action::SetTabSize,
616        contexts: &[Normal],
617        custom_contexts: &[],
618    },
619    CommandDef {
620        name_key: "cmd.set_line_ending",
621        desc_key: "cmd.set_line_ending_desc",
622        action: || Action::SetLineEnding,
623        contexts: &[Normal],
624        custom_contexts: &[],
625    },
626    CommandDef {
627        name_key: "cmd.set_encoding",
628        desc_key: "cmd.set_encoding_desc",
629        action: || Action::SetEncoding,
630        contexts: &[Normal],
631        custom_contexts: &[],
632    },
633    CommandDef {
634        name_key: "cmd.reload_with_encoding",
635        desc_key: "cmd.reload_with_encoding_desc",
636        action: || Action::ReloadWithEncoding,
637        contexts: &[Normal],
638        custom_contexts: &[],
639    },
640    CommandDef {
641        name_key: "cmd.set_language",
642        desc_key: "cmd.set_language_desc",
643        action: || Action::SetLanguage,
644        contexts: &[Normal],
645        custom_contexts: &[],
646    },
647    CommandDef {
648        name_key: "cmd.toggle_indentation",
649        desc_key: "cmd.toggle_indentation_desc",
650        action: || Action::ToggleIndentationStyle,
651        contexts: &[Normal],
652        custom_contexts: &[],
653    },
654    CommandDef {
655        name_key: "cmd.toggle_tab_indicators",
656        desc_key: "cmd.toggle_tab_indicators_desc",
657        action: || Action::ToggleTabIndicators,
658        contexts: &[Normal],
659        custom_contexts: &[],
660    },
661    CommandDef {
662        name_key: "cmd.toggle_whitespace_indicators",
663        desc_key: "cmd.toggle_whitespace_indicators_desc",
664        action: || Action::ToggleWhitespaceIndicators,
665        contexts: &[Normal],
666        custom_contexts: &[],
667    },
668    CommandDef {
669        name_key: "cmd.reset_buffer_settings",
670        desc_key: "cmd.reset_buffer_settings_desc",
671        action: || Action::ResetBufferSettings,
672        contexts: &[Normal],
673        custom_contexts: &[],
674    },
675    CommandDef {
676        name_key: "cmd.scroll_up",
677        desc_key: "cmd.scroll_up_desc",
678        action: || Action::ScrollUp,
679        contexts: &[Normal],
680        custom_contexts: &[],
681    },
682    CommandDef {
683        name_key: "cmd.scroll_down",
684        desc_key: "cmd.scroll_down_desc",
685        action: || Action::ScrollDown,
686        contexts: &[Normal],
687        custom_contexts: &[],
688    },
689    CommandDef {
690        name_key: "cmd.scroll_tabs_left",
691        desc_key: "cmd.scroll_tabs_left_desc",
692        action: || Action::ScrollTabsLeft,
693        contexts: &[Normal, Terminal],
694        custom_contexts: &[],
695    },
696    CommandDef {
697        name_key: "cmd.scroll_tabs_right",
698        desc_key: "cmd.scroll_tabs_right_desc",
699        action: || Action::ScrollTabsRight,
700        contexts: &[Normal, Terminal],
701        custom_contexts: &[],
702    },
703    CommandDef {
704        name_key: "cmd.toggle_mouse_support",
705        desc_key: "cmd.toggle_mouse_support_desc",
706        action: || Action::ToggleMouseCapture,
707        contexts: &[Normal, Terminal],
708        custom_contexts: &[],
709    },
710    // File explorer
711    CommandDef {
712        name_key: "cmd.toggle_file_explorer",
713        desc_key: "cmd.toggle_file_explorer_desc",
714        action: || Action::ToggleFileExplorer,
715        contexts: &[Normal, FileExplorer, Terminal],
716        custom_contexts: &[],
717    },
718    CommandDef {
719        name_key: "cmd.toggle_menu_bar",
720        desc_key: "cmd.toggle_menu_bar_desc",
721        action: || Action::ToggleMenuBar,
722        contexts: &[Normal, FileExplorer, Terminal],
723        custom_contexts: &[],
724    },
725    CommandDef {
726        name_key: "cmd.toggle_tab_bar",
727        desc_key: "cmd.toggle_tab_bar_desc",
728        action: || Action::ToggleTabBar,
729        contexts: &[Normal, FileExplorer, Terminal],
730        custom_contexts: &[],
731    },
732    CommandDef {
733        name_key: "cmd.toggle_status_bar",
734        desc_key: "cmd.toggle_status_bar_desc",
735        action: || Action::ToggleStatusBar,
736        contexts: &[Normal, FileExplorer, Terminal],
737        custom_contexts: &[],
738    },
739    CommandDef {
740        name_key: "cmd.toggle_prompt_line",
741        desc_key: "cmd.toggle_prompt_line_desc",
742        action: || Action::TogglePromptLine,
743        contexts: &[Normal, FileExplorer, Terminal],
744        custom_contexts: &[],
745    },
746    CommandDef {
747        name_key: "cmd.toggle_vertical_scrollbar",
748        desc_key: "cmd.toggle_vertical_scrollbar_desc",
749        action: || Action::ToggleVerticalScrollbar,
750        contexts: &[Normal, FileExplorer, Terminal],
751        custom_contexts: &[],
752    },
753    CommandDef {
754        name_key: "cmd.toggle_horizontal_scrollbar",
755        desc_key: "cmd.toggle_horizontal_scrollbar_desc",
756        action: || Action::ToggleHorizontalScrollbar,
757        contexts: &[Normal, FileExplorer, Terminal],
758        custom_contexts: &[],
759    },
760    CommandDef {
761        name_key: "cmd.focus_file_explorer",
762        desc_key: "cmd.focus_file_explorer_desc",
763        action: || Action::FocusFileExplorer,
764        contexts: &[Normal, Terminal],
765        custom_contexts: &[],
766    },
767    CommandDef {
768        name_key: "cmd.focus_editor",
769        desc_key: "cmd.focus_editor_desc",
770        action: || Action::FocusEditor,
771        contexts: &[FileExplorer],
772        custom_contexts: &[],
773    },
774    CommandDef {
775        name_key: "cmd.explorer_refresh",
776        desc_key: "cmd.explorer_refresh_desc",
777        action: || Action::FileExplorerRefresh,
778        contexts: &[FileExplorer],
779        custom_contexts: &[],
780    },
781    CommandDef {
782        name_key: "cmd.explorer_new_file",
783        desc_key: "cmd.explorer_new_file_desc",
784        action: || Action::FileExplorerNewFile,
785        contexts: &[FileExplorer],
786        custom_contexts: &[],
787    },
788    CommandDef {
789        name_key: "cmd.explorer_new_directory",
790        desc_key: "cmd.explorer_new_directory_desc",
791        action: || Action::FileExplorerNewDirectory,
792        contexts: &[FileExplorer],
793        custom_contexts: &[],
794    },
795    CommandDef {
796        name_key: "cmd.explorer_delete",
797        desc_key: "cmd.explorer_delete_desc",
798        action: || Action::FileExplorerDelete,
799        contexts: &[FileExplorer],
800        custom_contexts: &[],
801    },
802    CommandDef {
803        name_key: "cmd.explorer_rename",
804        desc_key: "cmd.explorer_rename_desc",
805        action: || Action::FileExplorerRename,
806        contexts: &[FileExplorer],
807        custom_contexts: &[],
808    },
809    CommandDef {
810        name_key: "cmd.toggle_hidden_files",
811        desc_key: "cmd.toggle_hidden_files_desc",
812        action: || Action::FileExplorerToggleHidden,
813        contexts: &[FileExplorer],
814        custom_contexts: &[],
815    },
816    CommandDef {
817        name_key: "cmd.toggle_gitignored_files",
818        desc_key: "cmd.toggle_gitignored_files_desc",
819        action: || Action::FileExplorerToggleGitignored,
820        contexts: &[FileExplorer],
821        custom_contexts: &[],
822    },
823    // View
824    CommandDef {
825        name_key: "cmd.toggle_line_wrap",
826        desc_key: "cmd.toggle_line_wrap_desc",
827        action: || Action::ToggleLineWrap,
828        contexts: &[Normal],
829        custom_contexts: &[],
830    },
831    CommandDef {
832        name_key: "cmd.toggle_current_line_highlight",
833        desc_key: "cmd.toggle_current_line_highlight_desc",
834        action: || Action::ToggleCurrentLineHighlight,
835        contexts: &[Normal],
836        custom_contexts: &[],
837    },
838    CommandDef {
839        name_key: "cmd.toggle_page_view",
840        desc_key: "cmd.toggle_page_view_desc",
841        action: || Action::TogglePageView,
842        contexts: &[Normal],
843        custom_contexts: &[],
844    },
845    CommandDef {
846        name_key: "cmd.set_page_width",
847        desc_key: "cmd.set_page_width_desc",
848        action: || Action::SetPageWidth,
849        contexts: &[Normal],
850        custom_contexts: &[],
851    },
852    CommandDef {
853        name_key: "cmd.toggle_read_only",
854        desc_key: "cmd.toggle_read_only_desc",
855        action: || Action::ToggleReadOnly,
856        contexts: &[Normal],
857        custom_contexts: &[],
858    },
859    CommandDef {
860        name_key: "cmd.set_background",
861        desc_key: "cmd.set_background_desc",
862        action: || Action::SetBackground,
863        contexts: &[Normal],
864        custom_contexts: &[],
865    },
866    CommandDef {
867        name_key: "cmd.set_background_blend",
868        desc_key: "cmd.set_background_blend_desc",
869        action: || Action::SetBackgroundBlend,
870        contexts: &[Normal],
871        custom_contexts: &[],
872    },
873    // Search and replace
874    CommandDef {
875        name_key: "cmd.search",
876        desc_key: "cmd.search_desc",
877        action: || Action::Search,
878        contexts: &[Normal],
879        custom_contexts: &[],
880    },
881    CommandDef {
882        name_key: "cmd.find_in_selection",
883        desc_key: "cmd.find_in_selection_desc",
884        action: || Action::FindInSelection,
885        contexts: &[Normal],
886        custom_contexts: &[],
887    },
888    CommandDef {
889        name_key: "cmd.find_next",
890        desc_key: "cmd.find_next_desc",
891        action: || Action::FindNext,
892        contexts: &[Normal],
893        custom_contexts: &[],
894    },
895    CommandDef {
896        name_key: "cmd.find_previous",
897        desc_key: "cmd.find_previous_desc",
898        action: || Action::FindPrevious,
899        contexts: &[Normal],
900        custom_contexts: &[],
901    },
902    CommandDef {
903        name_key: "cmd.find_selection_next",
904        desc_key: "cmd.find_selection_next_desc",
905        action: || Action::FindSelectionNext,
906        contexts: &[Normal],
907        custom_contexts: &[],
908    },
909    CommandDef {
910        name_key: "cmd.find_selection_previous",
911        desc_key: "cmd.find_selection_previous_desc",
912        action: || Action::FindSelectionPrevious,
913        contexts: &[Normal],
914        custom_contexts: &[],
915    },
916    CommandDef {
917        name_key: "cmd.replace",
918        desc_key: "cmd.replace_desc",
919        action: || Action::Replace,
920        contexts: &[Normal],
921        custom_contexts: &[],
922    },
923    CommandDef {
924        name_key: "cmd.query_replace",
925        desc_key: "cmd.query_replace_desc",
926        action: || Action::QueryReplace,
927        contexts: &[Normal],
928        custom_contexts: &[],
929    },
930    // Navigation
931    CommandDef {
932        name_key: "cmd.goto_line",
933        desc_key: "cmd.goto_line_desc",
934        action: || Action::GotoLine,
935        contexts: &[Normal],
936        custom_contexts: &[],
937    },
938    CommandDef {
939        name_key: "cmd.scan_line_index",
940        desc_key: "cmd.scan_line_index_desc",
941        action: || Action::ScanLineIndex,
942        contexts: &[Normal],
943        custom_contexts: &[],
944    },
945    CommandDef {
946        name_key: "cmd.smart_home",
947        desc_key: "cmd.smart_home_desc",
948        action: || Action::SmartHome,
949        contexts: &[Normal],
950        custom_contexts: &[],
951    },
952    CommandDef {
953        name_key: "cmd.show_completions",
954        desc_key: "cmd.show_completions_desc",
955        action: || Action::LspCompletion,
956        contexts: &[Normal],
957        custom_contexts: &[],
958    },
959    CommandDef {
960        name_key: "cmd.goto_definition",
961        desc_key: "cmd.goto_definition_desc",
962        action: || Action::LspGotoDefinition,
963        contexts: &[Normal],
964        custom_contexts: &[],
965    },
966    CommandDef {
967        name_key: "cmd.show_hover_info",
968        desc_key: "cmd.show_hover_info_desc",
969        action: || Action::LspHover,
970        contexts: &[Normal],
971        custom_contexts: &[],
972    },
973    CommandDef {
974        name_key: "cmd.find_references",
975        desc_key: "cmd.find_references_desc",
976        action: || Action::LspReferences,
977        contexts: &[Normal],
978        custom_contexts: &[],
979    },
980    CommandDef {
981        name_key: "cmd.show_signature_help",
982        desc_key: "cmd.show_signature_help_desc",
983        action: || Action::LspSignatureHelp,
984        contexts: &[Normal],
985        custom_contexts: &[],
986    },
987    CommandDef {
988        name_key: "cmd.code_actions",
989        desc_key: "cmd.code_actions_desc",
990        action: || Action::LspCodeActions,
991        contexts: &[Normal],
992        custom_contexts: &[],
993    },
994    CommandDef {
995        name_key: "cmd.start_restart_lsp",
996        desc_key: "cmd.start_restart_lsp_desc",
997        action: || Action::LspRestart,
998        contexts: &[Normal],
999        custom_contexts: &[],
1000    },
1001    CommandDef {
1002        name_key: "cmd.stop_lsp",
1003        desc_key: "cmd.stop_lsp_desc",
1004        action: || Action::LspStop,
1005        contexts: &[Normal],
1006        custom_contexts: &[],
1007    },
1008    CommandDef {
1009        name_key: "cmd.toggle_lsp_for_buffer",
1010        desc_key: "cmd.toggle_lsp_for_buffer_desc",
1011        action: || Action::LspToggleForBuffer,
1012        contexts: &[Normal],
1013        custom_contexts: &[],
1014    },
1015    CommandDef {
1016        name_key: "cmd.toggle_mouse_hover",
1017        desc_key: "cmd.toggle_mouse_hover_desc",
1018        action: || Action::ToggleMouseHover,
1019        contexts: &[],
1020        custom_contexts: &[],
1021    },
1022    CommandDef {
1023        name_key: "cmd.navigate_back",
1024        desc_key: "cmd.navigate_back_desc",
1025        action: || Action::NavigateBack,
1026        contexts: &[Normal],
1027        custom_contexts: &[],
1028    },
1029    CommandDef {
1030        name_key: "cmd.navigate_forward",
1031        desc_key: "cmd.navigate_forward_desc",
1032        action: || Action::NavigateForward,
1033        contexts: &[Normal],
1034        custom_contexts: &[],
1035    },
1036    // Smart editing
1037    CommandDef {
1038        name_key: "cmd.toggle_comment",
1039        desc_key: "cmd.toggle_comment_desc",
1040        action: || Action::ToggleComment,
1041        contexts: &[Normal],
1042        custom_contexts: &[],
1043    },
1044    CommandDef {
1045        name_key: "cmd.dedent_selection",
1046        desc_key: "cmd.dedent_selection_desc",
1047        action: || Action::DedentSelection,
1048        contexts: &[Normal],
1049        custom_contexts: &[],
1050    },
1051    CommandDef {
1052        name_key: "cmd.goto_matching_bracket",
1053        desc_key: "cmd.goto_matching_bracket_desc",
1054        action: || Action::GoToMatchingBracket,
1055        contexts: &[Normal],
1056        custom_contexts: &[],
1057    },
1058    // Error navigation
1059    CommandDef {
1060        name_key: "cmd.jump_to_next_error",
1061        desc_key: "cmd.jump_to_next_error_desc",
1062        action: || Action::JumpToNextError,
1063        contexts: &[Normal],
1064        custom_contexts: &[],
1065    },
1066    CommandDef {
1067        name_key: "cmd.jump_to_previous_error",
1068        desc_key: "cmd.jump_to_previous_error_desc",
1069        action: || Action::JumpToPreviousError,
1070        contexts: &[Normal],
1071        custom_contexts: &[],
1072    },
1073    // LSP
1074    CommandDef {
1075        name_key: "cmd.rename_symbol",
1076        desc_key: "cmd.rename_symbol_desc",
1077        action: || Action::LspRename,
1078        contexts: &[Normal],
1079        custom_contexts: &[],
1080    },
1081    // Bookmarks and Macros
1082    CommandDef {
1083        name_key: "cmd.list_bookmarks",
1084        desc_key: "cmd.list_bookmarks_desc",
1085        action: || Action::ListBookmarks,
1086        contexts: &[Normal],
1087        custom_contexts: &[],
1088    },
1089    CommandDef {
1090        name_key: "cmd.list_macros",
1091        desc_key: "cmd.list_macros_desc",
1092        action: || Action::ListMacros,
1093        contexts: &[Normal],
1094        custom_contexts: &[],
1095    },
1096    CommandDef {
1097        name_key: "cmd.record_macro",
1098        desc_key: "cmd.record_macro_desc",
1099        action: || Action::PromptRecordMacro,
1100        contexts: &[Normal],
1101        custom_contexts: &[],
1102    },
1103    CommandDef {
1104        name_key: "cmd.stop_recording_macro",
1105        desc_key: "cmd.stop_recording_macro_desc",
1106        action: || Action::StopMacroRecording,
1107        contexts: &[Normal],
1108        custom_contexts: &[],
1109    },
1110    CommandDef {
1111        name_key: "cmd.play_macro",
1112        desc_key: "cmd.play_macro_desc",
1113        action: || Action::PromptPlayMacro,
1114        contexts: &[Normal],
1115        custom_contexts: &[],
1116    },
1117    CommandDef {
1118        name_key: "cmd.play_last_macro",
1119        desc_key: "cmd.play_last_macro_desc",
1120        action: || Action::PlayLastMacro,
1121        contexts: &[Normal],
1122        custom_contexts: &[],
1123    },
1124    CommandDef {
1125        name_key: "cmd.set_bookmark",
1126        desc_key: "cmd.set_bookmark_desc",
1127        action: || Action::PromptSetBookmark,
1128        contexts: &[Normal],
1129        custom_contexts: &[],
1130    },
1131    CommandDef {
1132        name_key: "cmd.jump_to_bookmark",
1133        desc_key: "cmd.jump_to_bookmark_desc",
1134        action: || Action::PromptJumpToBookmark,
1135        contexts: &[Normal],
1136        custom_contexts: &[],
1137    },
1138    // Help
1139    CommandDef {
1140        name_key: "cmd.show_manual",
1141        desc_key: "cmd.show_manual_desc",
1142        action: || Action::ShowHelp,
1143        contexts: &[],
1144        custom_contexts: &[],
1145    },
1146    CommandDef {
1147        name_key: "cmd.show_keyboard_shortcuts",
1148        desc_key: "cmd.show_keyboard_shortcuts_desc",
1149        action: || Action::ShowKeyboardShortcuts,
1150        contexts: &[],
1151        custom_contexts: &[],
1152    },
1153    CommandDef {
1154        name_key: "cmd.show_warnings",
1155        desc_key: "cmd.show_warnings_desc",
1156        action: || Action::ShowWarnings,
1157        contexts: &[],
1158        custom_contexts: &[],
1159    },
1160    CommandDef {
1161        name_key: "cmd.show_lsp_status",
1162        desc_key: "cmd.show_lsp_status_desc",
1163        action: || Action::ShowLspStatus,
1164        contexts: &[],
1165        custom_contexts: &[],
1166    },
1167    CommandDef {
1168        name_key: "cmd.show_remote_indicator_menu",
1169        desc_key: "cmd.show_remote_indicator_menu_desc",
1170        action: || Action::ShowRemoteIndicatorMenu,
1171        contexts: &[],
1172        custom_contexts: &[],
1173    },
1174    CommandDef {
1175        name_key: "cmd.clear_warnings",
1176        desc_key: "cmd.clear_warnings_desc",
1177        action: || Action::ClearWarnings,
1178        contexts: &[],
1179        custom_contexts: &[],
1180    },
1181    // Config
1182    CommandDef {
1183        name_key: "cmd.dump_config",
1184        desc_key: "cmd.dump_config_desc",
1185        action: || Action::DumpConfig,
1186        contexts: &[],
1187        custom_contexts: &[],
1188    },
1189    CommandDef {
1190        name_key: "cmd.redraw_screen",
1191        desc_key: "cmd.redraw_screen_desc",
1192        action: || Action::RedrawScreen,
1193        contexts: &[],
1194        custom_contexts: &[],
1195    },
1196    CommandDef {
1197        name_key: "cmd.toggle_inlay_hints",
1198        desc_key: "cmd.toggle_inlay_hints_desc",
1199        action: || Action::ToggleInlayHints,
1200        contexts: &[Normal],
1201        custom_contexts: &[],
1202    },
1203    // Theme selection
1204    CommandDef {
1205        name_key: "cmd.select_theme",
1206        desc_key: "cmd.select_theme_desc",
1207        action: || Action::SelectTheme,
1208        contexts: &[],
1209        custom_contexts: &[],
1210    },
1211    // Theme inspection
1212    CommandDef {
1213        name_key: "cmd.inspect_theme_at_cursor",
1214        desc_key: "cmd.inspect_theme_at_cursor_desc",
1215        action: || Action::InspectThemeAtCursor,
1216        contexts: &[Normal],
1217        custom_contexts: &[],
1218    },
1219    // Keybinding map selection
1220    CommandDef {
1221        name_key: "cmd.select_keybinding_map",
1222        desc_key: "cmd.select_keybinding_map_desc",
1223        action: || Action::SelectKeybindingMap,
1224        contexts: &[],
1225        custom_contexts: &[],
1226    },
1227    // Cursor style selection
1228    CommandDef {
1229        name_key: "cmd.select_cursor_style",
1230        desc_key: "cmd.select_cursor_style_desc",
1231        action: || Action::SelectCursorStyle,
1232        contexts: &[],
1233        custom_contexts: &[],
1234    },
1235    // Locale selection
1236    CommandDef {
1237        name_key: "cmd.select_locale",
1238        desc_key: "cmd.select_locale_desc",
1239        action: || Action::SelectLocale,
1240        contexts: &[],
1241        custom_contexts: &[],
1242    },
1243    // Settings
1244    CommandDef {
1245        name_key: "cmd.open_settings",
1246        desc_key: "cmd.open_settings_desc",
1247        action: || Action::OpenSettings,
1248        contexts: &[],
1249        custom_contexts: &[],
1250    },
1251    // Keybinding editor
1252    CommandDef {
1253        name_key: "cmd.open_keybinding_editor",
1254        desc_key: "cmd.open_keybinding_editor_desc",
1255        action: || Action::OpenKeybindingEditor,
1256        contexts: &[],
1257        custom_contexts: &[],
1258    },
1259    // Input calibration
1260    CommandDef {
1261        name_key: "cmd.calibrate_input",
1262        desc_key: "cmd.calibrate_input_desc",
1263        action: || Action::CalibrateInput,
1264        contexts: &[],
1265        custom_contexts: &[],
1266    },
1267    // Terminal commands
1268    CommandDef {
1269        name_key: "cmd.open_terminal",
1270        desc_key: "cmd.open_terminal_desc",
1271        action: || Action::OpenTerminal,
1272        contexts: &[],
1273        custom_contexts: &[],
1274    },
1275    CommandDef {
1276        name_key: "cmd.focus_terminal",
1277        desc_key: "cmd.focus_terminal_desc",
1278        action: || Action::FocusTerminal,
1279        contexts: &[Normal],
1280        custom_contexts: &[],
1281    },
1282    CommandDef {
1283        name_key: "cmd.exit_terminal_mode",
1284        desc_key: "cmd.exit_terminal_mode_desc",
1285        action: || Action::TerminalEscape,
1286        contexts: &[Terminal],
1287        custom_contexts: &[],
1288    },
1289    CommandDef {
1290        name_key: "cmd.toggle_keyboard_capture",
1291        desc_key: "cmd.toggle_keyboard_capture_desc",
1292        action: || Action::ToggleKeyboardCapture,
1293        contexts: &[Terminal],
1294        custom_contexts: &[],
1295    },
1296    // Shell command operations
1297    CommandDef {
1298        name_key: "cmd.shell_command",
1299        desc_key: "cmd.shell_command_desc",
1300        action: || Action::ShellCommand,
1301        contexts: &[Normal],
1302        custom_contexts: &[],
1303    },
1304    CommandDef {
1305        name_key: "cmd.shell_command_replace",
1306        desc_key: "cmd.shell_command_replace_desc",
1307        action: || Action::ShellCommandReplace,
1308        contexts: &[Normal],
1309        custom_contexts: &[],
1310    },
1311    // Debugging
1312    CommandDef {
1313        name_key: "cmd.event_debug",
1314        desc_key: "cmd.event_debug_desc",
1315        action: || Action::EventDebug,
1316        contexts: &[],
1317        custom_contexts: &[],
1318    },
1319    // Process control (Unix job-control suspend)
1320    CommandDef {
1321        name_key: "cmd.suspend_process",
1322        desc_key: "cmd.suspend_process_desc",
1323        action: || Action::SuspendProcess,
1324        contexts: &[Normal, FileExplorer, Terminal],
1325        custom_contexts: &[],
1326    },
1327    // Plugin development
1328    CommandDef {
1329        name_key: "cmd.load_plugin_from_buffer",
1330        desc_key: "cmd.load_plugin_from_buffer_desc",
1331        action: || Action::LoadPluginFromBuffer,
1332        contexts: &[Normal],
1333        custom_contexts: &[],
1334    },
1335    // User init.ts
1336    CommandDef {
1337        name_key: "cmd.init_reload",
1338        desc_key: "cmd.init_reload_desc",
1339        action: || Action::InitReload,
1340        contexts: &[Normal],
1341        custom_contexts: &[],
1342    },
1343    CommandDef {
1344        name_key: "cmd.init_edit",
1345        desc_key: "cmd.init_edit_desc",
1346        action: || Action::InitEdit,
1347        contexts: &[Normal],
1348        custom_contexts: &[],
1349    },
1350    CommandDef {
1351        name_key: "cmd.init_check",
1352        desc_key: "cmd.init_check_desc",
1353        action: || Action::InitCheck,
1354        contexts: &[Normal],
1355        custom_contexts: &[],
1356    },
1357    // Live Grep (issue #1796) — `cmd.live_grep` itself is registered
1358    // by the live_grep plugin (palette title is plugin-controlled);
1359    // these are the editor-side actions that should also be palette-
1360    // discoverable so the user can find them by name.
1361    CommandDef {
1362        name_key: "cmd.resume_live_grep",
1363        desc_key: "cmd.resume_live_grep_desc",
1364        action: || Action::ResumeLiveGrep,
1365        contexts: &[Normal],
1366        custom_contexts: &[],
1367    },
1368    CommandDef {
1369        name_key: "cmd.toggle_utility_dock",
1370        desc_key: "cmd.toggle_utility_dock_desc",
1371        action: || Action::ToggleUtilityDock,
1372        contexts: &[Normal],
1373        custom_contexts: &[],
1374    },
1375    CommandDef {
1376        name_key: "cmd.open_terminal_in_dock",
1377        desc_key: "cmd.open_terminal_in_dock_desc",
1378        action: || Action::OpenTerminalInDock,
1379        contexts: &[Normal],
1380        custom_contexts: &[],
1381    },
1382];
1383
1384/// Get all available commands for the command palette
1385pub fn get_all_commands() -> Vec<Command> {
1386    COMMAND_DEFS
1387        .iter()
1388        .map(|def| Command {
1389            name: t!(def.name_key).to_string(),
1390            description: t!(def.desc_key).to_string(),
1391            action: (def.action)(),
1392            contexts: def.contexts.to_vec(),
1393            custom_contexts: def.custom_contexts.iter().map(|s| s.to_string()).collect(),
1394            source: CommandSource::Builtin,
1395            // Built-in commands use the legacy `is_terminal_ui_action`
1396            // path; the plugin-driven `terminal_bypass` flag isn't
1397            // wired into them.
1398            terminal_bypass: false,
1399        })
1400        .collect()
1401}
1402
1403/// Filter commands by fuzzy matching the query, with context awareness
1404pub fn filter_commands(
1405    query: &str,
1406    current_context: KeyContext,
1407    keybinding_resolver: &crate::input::keybindings::KeybindingResolver,
1408) -> Vec<Suggestion> {
1409    let query_lower = query.to_lowercase();
1410    let commands = get_all_commands();
1411
1412    // Helper function to check if command is available in current context
1413    let is_available = |cmd: &Command| -> bool {
1414        // Empty contexts means available in all contexts
1415        cmd.contexts.is_empty() || cmd.contexts.contains(&current_context)
1416    };
1417
1418    // Helper function for fuzzy matching
1419    let matches_query = |cmd: &Command| -> bool {
1420        if query.is_empty() {
1421            return true;
1422        }
1423
1424        let name_lower = cmd.name.to_lowercase();
1425        let mut query_chars = query_lower.chars();
1426        let mut current_char = query_chars.next();
1427
1428        for name_char in name_lower.chars() {
1429            if let Some(qc) = current_char {
1430                if qc == name_char {
1431                    current_char = query_chars.next();
1432                }
1433            } else {
1434                break;
1435            }
1436        }
1437
1438        current_char.is_none() // All query characters matched
1439    };
1440
1441    // Filter and convert to suggestions
1442    let current_context_ref = &current_context;
1443    let mut suggestions: Vec<Suggestion> = commands
1444        .into_iter()
1445        .filter(|cmd| matches_query(cmd))
1446        .map(|cmd| {
1447            let available = is_available(&cmd);
1448            let keybinding = keybinding_resolver
1449                .get_keybinding_for_action(&cmd.action, current_context_ref.clone());
1450            Suggestion::new(cmd.name.clone())
1451                .with_description(cmd.description)
1452                .set_disabled(!available)
1453                .with_keybinding(keybinding)
1454        })
1455        .collect();
1456
1457    // Sort: available commands first, then disabled ones
1458    suggestions.sort_by_key(|s| s.disabled);
1459
1460    suggestions
1461}