Skip to main content

basalt_tui/
command.rs

1use ratatui::{
2    crossterm::{
3        terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
4        ExecutableCommand,
5    },
6    DefaultTerminal,
7};
8use serde::{Deserialize, Deserializer};
9use std::{io::stdout, process};
10use tracing::error;
11
12use crate::{
13    app::{Message, ScrollAmount},
14    debug_log, explorer, help_modal, input, note_editor,
15    note_editor::state::Operator,
16    note_editor::Direction,
17    outline, splash_modal, theme_selector_modal, vault_selector_modal,
18};
19
20trait ReplaceVar {
21    fn replace_var(&self, variable: &str, content: &str) -> Self;
22}
23
24impl ReplaceVar for String {
25    fn replace_var(&self, variable: &str, content: &str) -> Self {
26        self.replace(variable, content)
27    }
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub(crate) enum Command {
32    Quit,
33
34    SplashUp,
35    SplashDown,
36    SplashOpen,
37
38    ExplorerUp,
39    ExplorerDown,
40    ExplorerOpen,
41    ExplorerSort,
42    ExplorerToggle,
43    ExplorerNewUntitledNote,
44    ExplorerNewUntitledFolder,
45    ExplorerToggleInputRename,
46    ExplorerToggleOutline,
47    ExplorerSwitchPaneNext,
48    ExplorerSwitchPanePrevious,
49    ExplorerHidePane,
50    ExplorerExpandPane,
51    ExplorerScrollUpOne,
52    ExplorerScrollDownOne,
53    ExplorerScrollUpHalfPage,
54    ExplorerScrollDownHalfPage,
55
56    OutlineUp,
57    OutlineDown,
58    OutlineSelect,
59    OutlineExpand,
60    OutlineToggle,
61    OutlineToggleExplorer,
62    OutlineSwitchPaneNext,
63    OutlineSwitchPanePrevious,
64
65    HelpModalScrollUpOne,
66    HelpModalScrollDownOne,
67    HelpModalScrollUpHalfPage,
68    HelpModalScrollDownHalfPage,
69    HelpModalToggle,
70    HelpModalClose,
71
72    NoteEditorScrollUpOne,
73    NoteEditorScrollDownOne,
74    NoteEditorScrollUpHalfPage,
75    NoteEditorScrollDownHalfPage,
76    NoteEditorSwitchPaneNext,
77    NoteEditorSwitchPanePrevious,
78    NoteEditorToggleExplorer,
79    NoteEditorToggleOutline,
80    NoteEditorCursorUp,
81    NoteEditorCursorDown,
82    NoteEditorScrollToTop,
83    NoteEditorScrollToBottom,
84
85    ExplorerScrollToTop,
86    ExplorerScrollToBottom,
87
88    TabNext,
89    TabPrevious,
90    TabClose,
91
92    NoteEditorExperimentalCursorWordForward,
93    NoteEditorExperimentalCursorWordBackward,
94    NoteEditorExperimentalToggleView,
95    NoteEditorExperimentalSetEditView,
96    NoteEditorExperimentalSetReadView,
97    NoteEditorExperimentalSave,
98    NoteEditorExperimentalExit,
99    NoteEditorExperimentalCursorLeft,
100    NoteEditorExperimentalCursorRight,
101    NoteEditorInsertMode,
102    NoteEditorAppend,
103    NoteEditorReplaceChar,
104    NoteEditorVisualMode,
105    NoteEditorVisualLineMode,
106    NoteEditorCursorLineStart,
107    NoteEditorCursorLineEnd,
108    NoteEditorCursorFirstNonblank,
109    NoteEditorCursorWordEnd,
110    NoteEditorCursorWordForwardBig,
111    NoteEditorCursorWordBackwardBig,
112    NoteEditorCursorWordEndBig,
113    NoteEditorParagraphForward,
114    NoteEditorParagraphBackward,
115    NoteEditorMatchingPair,
116    NoteEditorCursorDocStart,
117    NoteEditorCursorDocEnd,
118    NoteEditorFindForward,
119    NoteEditorFindBackward,
120    NoteEditorTillForward,
121    NoteEditorTillBackward,
122    NoteEditorRepeatFind,
123    NoteEditorRepeatFindReverse,
124    NoteEditorDelete,
125    NoteEditorChange,
126    NoteEditorYankOperator,
127    NoteEditorDeleteUnderCursor,
128    NoteEditorDeleteToLineEnd,
129    NoteEditorChangeToLineEnd,
130    NoteEditorSubstituteChar,
131    NoteEditorPasteAfter,
132    NoteEditorPasteBefore,
133    NoteEditorUndo,
134    NoteEditorRedo,
135
136    VaultSelectorModalUp,
137    VaultSelectorModalDown,
138    VaultSelectorModalClose,
139    VaultSelectorModalOpen,
140    VaultSelectorModalToggle,
141
142    DebugLogToggle,
143    DebugLogClose,
144    DebugLogClear,
145    DebugLogCycleLevel,
146    DebugLogScrollUpOne,
147    DebugLogScrollDownOne,
148    DebugLogScrollUpHalfPage,
149    DebugLogScrollDownHalfPage,
150
151    ThemeSelectorModalUp,
152    ThemeSelectorModalDown,
153    ThemeSelectorModalClose,
154    ThemeSelectorModalOpen,
155    ThemeSelectorModalToggle,
156
157    InputModalWordForward,
158    InputModalWordBackward,
159    InputModalLeft,
160    InputModalRight,
161    InputModalCancel,
162    InputModalAccept,
163    InputModalEditMode,
164
165    Exec(String),
166    Spawn(String),
167}
168
169fn str_to_command(s: &str) -> Option<Command> {
170    match s {
171        "quit" => Some(Command::Quit),
172
173        "splash_up" => Some(Command::SplashUp),
174        "splash_down" => Some(Command::SplashDown),
175        "splash_open" => Some(Command::SplashOpen),
176
177        "explorer_up" => Some(Command::ExplorerUp),
178        "explorer_down" => Some(Command::ExplorerDown),
179        "explorer_open" => Some(Command::ExplorerOpen),
180        "explorer_sort" => Some(Command::ExplorerSort),
181        "explorer_toggle" => Some(Command::ExplorerToggle),
182        "explorer_new_untitled_note" => Some(Command::ExplorerNewUntitledNote),
183        "explorer_new_untitled_folder" => Some(Command::ExplorerNewUntitledFolder),
184        "explorer_toggle_outline" => Some(Command::ExplorerToggleOutline),
185        "explorer_toggle_input_rename" => Some(Command::ExplorerToggleInputRename),
186        "explorer_switch_pane_next" => Some(Command::ExplorerSwitchPaneNext),
187        "explorer_hide_pane" => Some(Command::ExplorerHidePane),
188        "explorer_expand_pane" => Some(Command::ExplorerExpandPane),
189        "explorer_switch_pane_previous" => Some(Command::ExplorerSwitchPanePrevious),
190        "explorer_scroll_up_one" => Some(Command::ExplorerScrollUpOne),
191        "explorer_scroll_down_one" => Some(Command::ExplorerScrollDownOne),
192        "explorer_scroll_up_half_page" => Some(Command::ExplorerScrollUpHalfPage),
193        "explorer_scroll_down_half_page" => Some(Command::ExplorerScrollDownHalfPage),
194
195        "input_modal_word_forward" => Some(Command::InputModalWordForward),
196        "input_modal_word_backward" => Some(Command::InputModalWordBackward),
197        "input_modal_left" => Some(Command::InputModalLeft),
198        "input_modal_right" => Some(Command::InputModalRight),
199        "input_modal_cancel" => Some(Command::InputModalCancel),
200        "input_modal_accept" => Some(Command::InputModalAccept),
201        "input_modal_edit_mode" => Some(Command::InputModalEditMode),
202
203        "outline_up" => Some(Command::OutlineUp),
204        "outline_down" => Some(Command::OutlineDown),
205        "outline_select" => Some(Command::OutlineSelect),
206        "outline_expand" => Some(Command::OutlineExpand),
207        "outline_toggle" => Some(Command::OutlineToggle),
208        "outline_toggle_explorer" => Some(Command::OutlineToggleExplorer),
209        "outline_switch_pane_next" => Some(Command::OutlineSwitchPaneNext),
210        "outline_switch_pane_previous" => Some(Command::OutlineSwitchPanePrevious),
211
212        "help_modal_scroll_up_one" => Some(Command::HelpModalScrollUpOne),
213        "help_modal_scroll_down_one" => Some(Command::HelpModalScrollDownOne),
214        "help_modal_scroll_up_half_page" => Some(Command::HelpModalScrollUpHalfPage),
215        "help_modal_scroll_down_half_page" => Some(Command::HelpModalScrollDownHalfPage),
216        "help_modal_toggle" => Some(Command::HelpModalToggle),
217        "help_modal_close" => Some(Command::HelpModalClose),
218
219        "note_editor_scroll_up_one" => Some(Command::NoteEditorScrollUpOne),
220        "note_editor_scroll_down_one" => Some(Command::NoteEditorScrollDownOne),
221        "note_editor_scroll_up_half_page" => Some(Command::NoteEditorScrollUpHalfPage),
222        "note_editor_scroll_down_half_page" => Some(Command::NoteEditorScrollDownHalfPage),
223        "note_editor_switch_pane_next" => Some(Command::NoteEditorSwitchPaneNext),
224        "note_editor_switch_pane_previous" => Some(Command::NoteEditorSwitchPanePrevious),
225        "note_editor_toggle_explorer" => Some(Command::NoteEditorToggleExplorer),
226        "note_editor_toggle_outline" => Some(Command::NoteEditorToggleOutline),
227        "note_editor_cursor_up" => Some(Command::NoteEditorCursorUp),
228        "note_editor_cursor_down" => Some(Command::NoteEditorCursorDown),
229        "note_editor_scroll_to_top" => Some(Command::NoteEditorScrollToTop),
230        "note_editor_scroll_to_bottom" => Some(Command::NoteEditorScrollToBottom),
231
232        "explorer_scroll_to_top" => Some(Command::ExplorerScrollToTop),
233        "explorer_scroll_to_bottom" => Some(Command::ExplorerScrollToBottom),
234
235        "tab_next" => Some(Command::TabNext),
236        "tab_previous" => Some(Command::TabPrevious),
237        "tab_close" => Some(Command::TabClose),
238
239        "note_editor_experimental_cursor_word_forward" => {
240            Some(Command::NoteEditorExperimentalCursorWordForward)
241        }
242        "note_editor_experimental_cursor_word_backward" => {
243            Some(Command::NoteEditorExperimentalCursorWordBackward)
244        }
245        "note_editor_experimental_set_edit_view" => {
246            Some(Command::NoteEditorExperimentalSetEditView)
247        }
248        "note_editor_experimental_toggle_view" => Some(Command::NoteEditorExperimentalToggleView),
249        "note_editor_experimental_set_read_view" => {
250            Some(Command::NoteEditorExperimentalSetReadView)
251        }
252        "note_editor_experimental_save" => Some(Command::NoteEditorExperimentalSave),
253        "note_editor_experimental_exit" => Some(Command::NoteEditorExperimentalExit),
254        "note_editor_experimental_cursor_left" => Some(Command::NoteEditorExperimentalCursorLeft),
255        "note_editor_experimental_cursor_right" => Some(Command::NoteEditorExperimentalCursorRight),
256        "note_editor_insert_mode" => Some(Command::NoteEditorInsertMode),
257        "note_editor_append" => Some(Command::NoteEditorAppend),
258        "note_editor_replace_char" => Some(Command::NoteEditorReplaceChar),
259        "note_editor_visual_mode" => Some(Command::NoteEditorVisualMode),
260        "note_editor_visual_line_mode" => Some(Command::NoteEditorVisualLineMode),
261        "note_editor_delete" => Some(Command::NoteEditorDelete),
262        "note_editor_change" => Some(Command::NoteEditorChange),
263        "note_editor_yank" => Some(Command::NoteEditorYankOperator),
264        "note_editor_delete_under_cursor" => Some(Command::NoteEditorDeleteUnderCursor),
265        "note_editor_delete_to_line_end" => Some(Command::NoteEditorDeleteToLineEnd),
266        "note_editor_change_to_line_end" => Some(Command::NoteEditorChangeToLineEnd),
267        "note_editor_substitute_char" => Some(Command::NoteEditorSubstituteChar),
268        "note_editor_paste_after" => Some(Command::NoteEditorPasteAfter),
269        "note_editor_paste_before" => Some(Command::NoteEditorPasteBefore),
270        "note_editor_undo" => Some(Command::NoteEditorUndo),
271        "note_editor_redo" => Some(Command::NoteEditorRedo),
272        "note_editor_cursor_line_start" => Some(Command::NoteEditorCursorLineStart),
273        "note_editor_cursor_line_end" => Some(Command::NoteEditorCursorLineEnd),
274        "note_editor_cursor_first_non_blank" => Some(Command::NoteEditorCursorFirstNonblank),
275        "note_editor_cursor_word_end" => Some(Command::NoteEditorCursorWordEnd),
276        "note_editor_cursor_word_forward_big" => Some(Command::NoteEditorCursorWordForwardBig),
277        "note_editor_cursor_word_backward_big" => Some(Command::NoteEditorCursorWordBackwardBig),
278        "note_editor_cursor_word_end_big" => Some(Command::NoteEditorCursorWordEndBig),
279        "note_editor_paragraph_forward" => Some(Command::NoteEditorParagraphForward),
280        "note_editor_paragraph_backward" => Some(Command::NoteEditorParagraphBackward),
281        "note_editor_matching_pair" => Some(Command::NoteEditorMatchingPair),
282        "note_editor_cursor_doc_start" => Some(Command::NoteEditorCursorDocStart),
283        "note_editor_cursor_doc_end" => Some(Command::NoteEditorCursorDocEnd),
284        "note_editor_find_forward" => Some(Command::NoteEditorFindForward),
285        "note_editor_find_backward" => Some(Command::NoteEditorFindBackward),
286        "note_editor_till_forward" => Some(Command::NoteEditorTillForward),
287        "note_editor_till_backward" => Some(Command::NoteEditorTillBackward),
288        "note_editor_repeat_find" => Some(Command::NoteEditorRepeatFind),
289        "note_editor_repeat_find_reverse" => Some(Command::NoteEditorRepeatFindReverse),
290
291        "vault_selector_modal_up" => Some(Command::VaultSelectorModalUp),
292        "vault_selector_modal_down" => Some(Command::VaultSelectorModalDown),
293        "vault_selector_modal_close" => Some(Command::VaultSelectorModalClose),
294        "vault_selector_modal_open" => Some(Command::VaultSelectorModalOpen),
295        "vault_selector_modal_toggle" => Some(Command::VaultSelectorModalToggle),
296
297        "debug_log_toggle" => Some(Command::DebugLogToggle),
298        "debug_log_close" => Some(Command::DebugLogClose),
299        "debug_log_clear" => Some(Command::DebugLogClear),
300        "debug_log_cycle_level" => Some(Command::DebugLogCycleLevel),
301        "debug_log_scroll_up_one" => Some(Command::DebugLogScrollUpOne),
302        "debug_log_scroll_down_one" => Some(Command::DebugLogScrollDownOne),
303        "debug_log_scroll_up_half_page" => Some(Command::DebugLogScrollUpHalfPage),
304        "debug_log_scroll_down_half_page" => Some(Command::DebugLogScrollDownHalfPage),
305
306        "theme_selector_modal_up" => Some(Command::ThemeSelectorModalUp),
307        "theme_selector_modal_down" => Some(Command::ThemeSelectorModalDown),
308        "theme_selector_modal_close" => Some(Command::ThemeSelectorModalClose),
309        "theme_selector_modal_open" => Some(Command::ThemeSelectorModalOpen),
310        "theme_selector_modal_toggle" => Some(Command::ThemeSelectorModalToggle),
311
312        // TODO: Remove deprecations in the next major version
313        // Deprecated
314        "note_editor_experimental_set_edit_mode" => {
315            Some(Command::NoteEditorExperimentalSetEditView)
316        }
317        // Deprecated
318        "note_editor_experimental_set_read_mode" => {
319            Some(Command::NoteEditorExperimentalSetReadView)
320        }
321        // Deprecated
322        "note_editor_experimental_exit_mode" => Some(Command::NoteEditorExperimentalExit),
323        _ => None,
324    }
325}
326
327impl<'de> Deserialize<'de> for Command {
328    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
329    where
330        D: Deserializer<'de>,
331    {
332        let s = String::deserialize(deserializer)?;
333
334        if let Some(command) = s
335            .strip_prefix("exec:")
336            .map(|command| Command::Exec(command.to_string()))
337            .or(s
338                .strip_prefix("spawn:")
339                .map(|command| Command::Spawn(command.to_string())))
340        {
341            return Ok(command);
342        }
343
344        str_to_command(&s).ok_or(serde::de::Error::custom(format!(
345            "{s} is not a valid command"
346        )))
347    }
348}
349
350impl From<Command> for Message<'_> {
351    fn from(value: Command) -> Self {
352        match value {
353            Command::Quit => Message::Quit,
354
355            Command::SplashUp => Message::Splash(splash_modal::Message::Up),
356            Command::SplashDown => Message::Splash(splash_modal::Message::Down),
357            Command::SplashOpen => Message::Splash(splash_modal::Message::Open),
358
359            Command::ExplorerUp => Message::Explorer(explorer::Message::Up),
360            Command::ExplorerDown => Message::Explorer(explorer::Message::Down),
361            Command::ExplorerOpen => Message::Explorer(explorer::Message::Select),
362            Command::ExplorerSort => Message::Explorer(explorer::Message::Sort),
363            Command::ExplorerToggle => Message::Explorer(explorer::Message::Toggle),
364            Command::ExplorerToggleOutline => Message::Explorer(explorer::Message::ToggleOutline),
365            Command::ExplorerToggleInputRename => {
366                Message::Explorer(explorer::Message::ToggleInputRename)
367            }
368            Command::ExplorerHidePane => Message::Explorer(explorer::Message::HidePane),
369            Command::ExplorerExpandPane => Message::Explorer(explorer::Message::ExpandPane),
370            Command::ExplorerSwitchPaneNext => Message::Explorer(explorer::Message::SwitchPaneNext),
371            Command::ExplorerSwitchPanePrevious => {
372                Message::Explorer(explorer::Message::SwitchPanePrevious)
373            }
374            Command::ExplorerScrollUpOne => {
375                Message::Explorer(explorer::Message::ScrollUp(ScrollAmount::One))
376            }
377            Command::ExplorerScrollDownOne => {
378                Message::Explorer(explorer::Message::ScrollDown(ScrollAmount::One))
379            }
380            Command::ExplorerScrollUpHalfPage => {
381                Message::Explorer(explorer::Message::ScrollUp(ScrollAmount::HalfPage))
382            }
383            Command::ExplorerScrollDownHalfPage => {
384                Message::Explorer(explorer::Message::ScrollDown(ScrollAmount::HalfPage))
385            }
386            Command::ExplorerNewUntitledNote => Message::CreateUntitledNote,
387            Command::ExplorerNewUntitledFolder => Message::CreateUntitledFolder,
388
389            Command::InputModalEditMode => Message::Input(input::Message::EditMode),
390            Command::InputModalAccept => Message::Input(input::Message::Accept),
391            Command::InputModalCancel => Message::Input(input::Message::Cancel),
392            Command::InputModalLeft => Message::Input(input::Message::CursorLeft),
393            Command::InputModalRight => Message::Input(input::Message::CursorRight),
394            Command::InputModalWordForward => Message::Input(input::Message::CursorWordForward),
395            Command::InputModalWordBackward => Message::Input(input::Message::CursorWordBackward),
396
397            Command::OutlineUp => Message::Outline(outline::Message::Up),
398            Command::OutlineDown => Message::Outline(outline::Message::Down),
399            Command::OutlineSelect => Message::Outline(outline::Message::Select),
400            Command::OutlineExpand => Message::Outline(outline::Message::Expand),
401            Command::OutlineToggle => Message::Outline(outline::Message::Toggle),
402            Command::OutlineToggleExplorer => Message::Outline(outline::Message::ToggleExplorer),
403            Command::OutlineSwitchPaneNext => Message::Outline(outline::Message::SwitchPaneNext),
404            Command::OutlineSwitchPanePrevious => {
405                Message::Outline(outline::Message::SwitchPanePrevious)
406            }
407
408            Command::HelpModalScrollUpOne => {
409                Message::HelpModal(help_modal::Message::ScrollUp(ScrollAmount::One))
410            }
411            Command::HelpModalScrollDownOne => {
412                Message::HelpModal(help_modal::Message::ScrollDown(ScrollAmount::One))
413            }
414            Command::HelpModalScrollUpHalfPage => {
415                Message::HelpModal(help_modal::Message::ScrollUp(ScrollAmount::HalfPage))
416            }
417            Command::HelpModalScrollDownHalfPage => {
418                Message::HelpModal(help_modal::Message::ScrollDown(ScrollAmount::HalfPage))
419            }
420            Command::HelpModalToggle => Message::HelpModal(help_modal::Message::Toggle),
421            Command::HelpModalClose => Message::HelpModal(help_modal::Message::Close),
422
423            Command::NoteEditorScrollUpOne => {
424                Message::NoteEditor(note_editor::Message::ScrollUp(ScrollAmount::One))
425            }
426            Command::NoteEditorScrollDownOne => {
427                Message::NoteEditor(note_editor::Message::ScrollDown(ScrollAmount::One))
428            }
429            Command::NoteEditorScrollUpHalfPage => {
430                Message::NoteEditor(note_editor::Message::ScrollUp(ScrollAmount::HalfPage))
431            }
432            Command::NoteEditorScrollDownHalfPage => {
433                Message::NoteEditor(note_editor::Message::ScrollDown(ScrollAmount::HalfPage))
434            }
435            Command::NoteEditorSwitchPaneNext => {
436                Message::NoteEditor(note_editor::Message::SwitchPaneNext)
437            }
438            Command::NoteEditorSwitchPanePrevious => {
439                Message::NoteEditor(note_editor::Message::SwitchPanePrevious)
440            }
441            Command::NoteEditorCursorUp => Message::NoteEditor(note_editor::Message::CursorUp),
442            Command::NoteEditorCursorDown => Message::NoteEditor(note_editor::Message::CursorDown),
443            Command::NoteEditorScrollToTop => {
444                Message::NoteEditor(note_editor::Message::ScrollToTop)
445            }
446            Command::NoteEditorScrollToBottom => {
447                Message::NoteEditor(note_editor::Message::ScrollToBottom)
448            }
449            Command::ExplorerScrollToTop => Message::Explorer(explorer::Message::ScrollToTop),
450            Command::ExplorerScrollToBottom => Message::Explorer(explorer::Message::ScrollToBottom),
451
452            Command::TabNext => Message::TabNext,
453            Command::TabPrevious => Message::TabPrevious,
454            Command::TabClose => Message::CloseTab,
455            Command::NoteEditorToggleExplorer => {
456                Message::NoteEditor(note_editor::Message::ToggleExplorer)
457            }
458            Command::NoteEditorToggleOutline => {
459                Message::NoteEditor(note_editor::Message::ToggleOutline)
460            }
461
462            // Experimental
463            Command::NoteEditorExperimentalToggleView => {
464                Message::NoteEditor(note_editor::Message::ToggleView)
465            }
466            Command::NoteEditorExperimentalSetEditView => {
467                Message::NoteEditor(note_editor::Message::EditView)
468            }
469            Command::NoteEditorExperimentalSetReadView => {
470                Message::NoteEditor(note_editor::Message::ReadView)
471            }
472            Command::NoteEditorExperimentalSave => Message::NoteEditor(note_editor::Message::Save),
473            Command::NoteEditorExperimentalExit => Message::NoteEditor(note_editor::Message::Exit),
474            Command::NoteEditorExperimentalCursorWordForward => {
475                Message::NoteEditor(note_editor::Message::CursorWordForward)
476            }
477            Command::NoteEditorExperimentalCursorWordBackward => {
478                Message::NoteEditor(note_editor::Message::CursorWordBackward)
479            }
480            Command::NoteEditorExperimentalCursorLeft => {
481                Message::NoteEditor(note_editor::Message::CursorLeft)
482            }
483            Command::NoteEditorExperimentalCursorRight => {
484                Message::NoteEditor(note_editor::Message::CursorRight)
485            }
486            Command::NoteEditorInsertMode => Message::NoteEditor(note_editor::Message::InsertMode),
487            Command::NoteEditorAppend => Message::NoteEditor(note_editor::Message::Append),
488            Command::NoteEditorReplaceChar => {
489                Message::NoteEditor(note_editor::Message::ReplaceChar)
490            }
491            Command::NoteEditorVisualMode => Message::NoteEditor(note_editor::Message::VisualMode),
492            Command::NoteEditorVisualLineMode => {
493                Message::NoteEditor(note_editor::Message::VisualLineMode)
494            }
495            Command::NoteEditorDelete => {
496                Message::NoteEditor(note_editor::Message::Operator(Operator::Delete))
497            }
498            Command::NoteEditorChange => {
499                Message::NoteEditor(note_editor::Message::Operator(Operator::Change))
500            }
501            Command::NoteEditorYankOperator => {
502                Message::NoteEditor(note_editor::Message::Operator(Operator::Yank))
503            }
504            Command::NoteEditorDeleteUnderCursor => {
505                Message::NoteEditor(note_editor::Message::DeleteUnderCursor)
506            }
507            Command::NoteEditorDeleteToLineEnd => {
508                Message::NoteEditor(note_editor::Message::DeleteToLineEnd)
509            }
510            Command::NoteEditorChangeToLineEnd => {
511                Message::NoteEditor(note_editor::Message::ChangeToLineEnd)
512            }
513            Command::NoteEditorSubstituteChar => {
514                Message::NoteEditor(note_editor::Message::SubstituteChar)
515            }
516            Command::NoteEditorPasteAfter => Message::NoteEditor(note_editor::Message::PasteAfter),
517            Command::NoteEditorPasteBefore => {
518                Message::NoteEditor(note_editor::Message::PasteBefore)
519            }
520            Command::NoteEditorUndo => Message::NoteEditor(note_editor::Message::Undo),
521            Command::NoteEditorRedo => Message::NoteEditor(note_editor::Message::Redo),
522            Command::NoteEditorCursorLineStart => {
523                Message::NoteEditor(note_editor::Message::CursorLineStart)
524            }
525            Command::NoteEditorCursorLineEnd => {
526                Message::NoteEditor(note_editor::Message::CursorLineEnd)
527            }
528            Command::NoteEditorCursorFirstNonblank => {
529                Message::NoteEditor(note_editor::Message::CursorFirstNonblank)
530            }
531            Command::NoteEditorCursorWordEnd => {
532                Message::NoteEditor(note_editor::Message::CursorWordEnd)
533            }
534            Command::NoteEditorCursorWordForwardBig => {
535                Message::NoteEditor(note_editor::Message::CursorWordForwardBig)
536            }
537            Command::NoteEditorCursorWordBackwardBig => {
538                Message::NoteEditor(note_editor::Message::CursorWordBackwardBig)
539            }
540            Command::NoteEditorCursorWordEndBig => {
541                Message::NoteEditor(note_editor::Message::CursorWordEndBig)
542            }
543            Command::NoteEditorParagraphForward => {
544                Message::NoteEditor(note_editor::Message::ParagraphForward)
545            }
546            Command::NoteEditorParagraphBackward => {
547                Message::NoteEditor(note_editor::Message::ParagraphBackward)
548            }
549            Command::NoteEditorMatchingPair => {
550                Message::NoteEditor(note_editor::Message::MatchingPair)
551            }
552            Command::NoteEditorCursorDocStart => {
553                Message::NoteEditor(note_editor::Message::CursorDocStart)
554            }
555            Command::NoteEditorCursorDocEnd => {
556                Message::NoteEditor(note_editor::Message::CursorDocEnd)
557            }
558            Command::NoteEditorFindForward => Message::NoteEditor(note_editor::Message::FindChar {
559                direction: Direction::Forward,
560                till: false,
561            }),
562            Command::NoteEditorFindBackward => {
563                Message::NoteEditor(note_editor::Message::FindChar {
564                    direction: Direction::Backward,
565                    till: false,
566                })
567            }
568            Command::NoteEditorTillForward => Message::NoteEditor(note_editor::Message::FindChar {
569                direction: Direction::Forward,
570                till: true,
571            }),
572            Command::NoteEditorTillBackward => {
573                Message::NoteEditor(note_editor::Message::FindChar {
574                    direction: Direction::Backward,
575                    till: true,
576                })
577            }
578            Command::NoteEditorRepeatFind => {
579                Message::NoteEditor(note_editor::Message::RepeatFind { reverse: false })
580            }
581            Command::NoteEditorRepeatFindReverse => {
582                Message::NoteEditor(note_editor::Message::RepeatFind { reverse: true })
583            }
584
585            Command::VaultSelectorModalClose => {
586                Message::VaultSelectorModal(vault_selector_modal::Message::Close)
587            }
588            Command::VaultSelectorModalToggle => {
589                Message::VaultSelectorModal(vault_selector_modal::Message::Toggle)
590            }
591            Command::VaultSelectorModalUp => {
592                Message::VaultSelectorModal(vault_selector_modal::Message::Up)
593            }
594            Command::VaultSelectorModalDown => {
595                Message::VaultSelectorModal(vault_selector_modal::Message::Down)
596            }
597            Command::VaultSelectorModalOpen => {
598                Message::VaultSelectorModal(vault_selector_modal::Message::Select)
599            }
600
601            Command::DebugLogToggle => Message::DebugLog(debug_log::Message::Toggle),
602            Command::DebugLogClose => Message::DebugLog(debug_log::Message::Close),
603            Command::DebugLogClear => Message::DebugLog(debug_log::Message::Clear),
604            Command::DebugLogCycleLevel => Message::DebugLog(debug_log::Message::CycleLevel),
605            Command::DebugLogScrollUpOne => {
606                Message::DebugLog(debug_log::Message::ScrollUp(ScrollAmount::One))
607            }
608            Command::DebugLogScrollDownOne => {
609                Message::DebugLog(debug_log::Message::ScrollDown(ScrollAmount::One))
610            }
611            Command::DebugLogScrollUpHalfPage => {
612                Message::DebugLog(debug_log::Message::ScrollUp(ScrollAmount::HalfPage))
613            }
614            Command::DebugLogScrollDownHalfPage => {
615                Message::DebugLog(debug_log::Message::ScrollDown(ScrollAmount::HalfPage))
616            }
617            Command::ThemeSelectorModalClose => {
618                Message::ThemeSelectorModal(theme_selector_modal::Message::Close)
619            }
620            Command::ThemeSelectorModalToggle => {
621                Message::ThemeSelectorModal(theme_selector_modal::Message::Toggle)
622            }
623            Command::ThemeSelectorModalUp => {
624                Message::ThemeSelectorModal(theme_selector_modal::Message::Up)
625            }
626            Command::ThemeSelectorModalDown => {
627                Message::ThemeSelectorModal(theme_selector_modal::Message::Down)
628            }
629            Command::ThemeSelectorModalOpen => {
630                Message::ThemeSelectorModal(theme_selector_modal::Message::Select)
631            }
632
633            Command::Exec(command) => Message::Exec(command),
634            Command::Spawn(command) => Message::Spawn(command),
635        }
636    }
637}
638
639pub fn run_command<'a>(
640    command: String,
641    vault_name: &str,
642    note_name: &str,
643    note_path: &str,
644    mut callback: impl FnMut(&str, &[&str]) -> Option<Message<'a>>,
645) -> Option<Message<'a>> {
646    let expanded = command
647        .replace_var("%vault", vault_name)
648        // Order matters, otherwise all mentions of %note_path would be replaced with %note value
649        .replace_var("%note_path", note_path)
650        .replace_var("%note", note_name);
651
652    let args = expanded.split_whitespace().collect::<Vec<_>>();
653
654    match args.as_slice() {
655        [command, args @ ..] => callback(command, args),
656        [] => None,
657    }
658}
659
660pub fn sync_command<'a>(
661    terminal: &mut DefaultTerminal,
662    command: String,
663    vault_name: &str,
664    note_name: &str,
665    note_path: &str,
666) -> Option<Message<'a>> {
667    fn enter_alternate_screen(terminal: &mut DefaultTerminal) -> Result<(), std::io::Error> {
668        disable_raw_mode()?;
669        stdout().execute(LeaveAlternateScreen)?;
670        stdout().execute(EnterAlternateScreen)?;
671        enable_raw_mode()?;
672        terminal.clear()
673    }
674
675    run_command(
676        command,
677        vault_name,
678        note_name,
679        note_path,
680        |command, args| {
681            if let Err(error) = process::Command::new(command).arg(args.join(" ")).status() {
682                error!(?error, command, "exec command failed");
683                return None;
684            }
685            enter_alternate_screen(terminal)
686                .map(|_| Message::Explorer(explorer::Message::Select))
687                .ok()
688        },
689    )
690}
691
692pub fn spawn_command<'a>(
693    command: String,
694    vault_name: &str,
695    note_name: &str,
696    note_path: &str,
697) -> Option<Message<'a>> {
698    run_command(
699        command,
700        vault_name,
701        note_name,
702        note_path,
703        |command, args| {
704            if let Err(error) = process::Command::new(command).arg(args.join(" ")).spawn() {
705                error!(?error, command, "spawn command failed");
706            }
707            None
708        },
709    )
710}