1use crate::action::Action;
2use crate::keybindings::{
3 action_from_name, all_bindings_for_context, default_keybindings, format_keybinding, ViewContext,
4};
5use crate::ui::command_palette::default_commands;
6use crossterm::event::KeyCode;
7use serde::Serialize;
8
9#[derive(Debug, Clone, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct DesktopManifest {
12 pub bindings: DesktopBindings,
13 pub commands: Vec<DesktopCommand>,
14}
15
16#[derive(Debug, Clone, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct DesktopBindings {
19 pub mail_list: Vec<DesktopBinding>,
20 pub message_view: Vec<DesktopBinding>,
21 pub thread_view: Vec<DesktopBinding>,
22}
23
24#[derive(Debug, Clone, Serialize)]
25#[serde(rename_all = "camelCase")]
26pub struct DesktopBinding {
27 pub action: String,
28 pub label: String,
29 pub display: String,
30 pub tokens: Vec<String>,
31}
32
33#[derive(Debug, Clone, Serialize)]
34#[serde(rename_all = "camelCase")]
35pub struct DesktopCommand {
36 pub label: String,
37 pub shortcut: String,
38 pub action: String,
39 pub category: String,
40}
41
42pub fn desktop_manifest() -> DesktopManifest {
43 let config = default_keybindings();
44
45 DesktopManifest {
46 bindings: DesktopBindings {
47 mail_list: bindings_for_context(&config.mail_list, ViewContext::MailList),
48 message_view: bindings_for_context(&config.message_view, ViewContext::MessageView),
49 thread_view: bindings_for_context(&config.thread_view, ViewContext::ThreadView),
50 },
51 commands: default_commands()
52 .into_iter()
53 .filter_map(|command| {
54 action_name(&command.action).map(|action| DesktopCommand {
55 label: command.label,
56 shortcut: command.shortcut,
57 action: action.to_string(),
58 category: command.category,
59 })
60 })
61 .collect(),
62 }
63}
64
65fn bindings_for_context(
66 bindings: &std::collections::HashMap<crate::keybindings::KeyBinding, String>,
67 context: ViewContext,
68) -> Vec<DesktopBinding> {
69 let display_names = all_bindings_for_context(context)
70 .into_iter()
71 .collect::<std::collections::HashMap<_, _>>();
72
73 let mut entries = bindings
74 .iter()
75 .filter_map(|(binding, action)| {
76 if action_from_name(action).is_none() {
77 return None;
78 }
79 Some(DesktopBinding {
80 action: action.clone(),
81 label: display_names
82 .get(&format_keybinding(binding))
83 .cloned()
84 .unwrap_or_else(|| action.clone()),
85 display: format_keybinding(binding),
86 tokens: binding.keys.iter().map(key_press_token).collect(),
87 })
88 })
89 .collect::<Vec<_>>();
90
91 entries.sort_by(|left, right| {
92 left.display
93 .cmp(&right.display)
94 .then_with(|| left.action.cmp(&right.action))
95 });
96 entries
97}
98
99fn key_press_token(key: &crate::keybindings::KeyPress) -> String {
100 if key
101 .modifiers
102 .contains(crossterm::event::KeyModifiers::CONTROL)
103 {
104 if let KeyCode::Char(c) = key.code {
105 return format!("Ctrl-{}", c.to_ascii_lowercase());
106 }
107 }
108
109 match key.code {
110 KeyCode::Enter => "Enter".to_string(),
111 KeyCode::Esc => "Esc".to_string(),
112 KeyCode::Tab => "Tab".to_string(),
113 KeyCode::Char(c) => c.to_string(),
114 _ => "?".to_string(),
115 }
116}
117
118fn action_name(action: &Action) -> Option<&'static str> {
119 match action {
120 Action::MoveDown => Some("move_down"),
121 Action::MoveUp => Some("move_up"),
122 Action::JumpTop => Some("jump_top"),
123 Action::JumpBottom => Some("jump_bottom"),
124 Action::PageDown => Some("page_down"),
125 Action::PageUp => Some("page_up"),
126 Action::ViewportTop => Some("visible_top"),
127 Action::ViewportMiddle => Some("visible_middle"),
128 Action::ViewportBottom => Some("visible_bottom"),
129 Action::CenterCurrent => Some("center_current"),
130 Action::SwitchPane => Some("switch_panes"),
131 Action::OpenSelected => Some("open"),
132 Action::Back => Some("back"),
133 Action::QuitView => Some("quit_view"),
134 Action::ClearSelection => Some("clear_selection"),
135 Action::OpenMailboxScreen => Some("open_mailbox_screen"),
136 Action::OpenSearchScreen => Some("open_search_screen"),
137 Action::OpenRulesScreen => Some("open_rules_screen"),
138 Action::OpenDiagnosticsScreen => Some("open_diagnostics_screen"),
139 Action::OpenAccountsScreen => Some("open_accounts_screen"),
140 Action::OpenTab1 => Some("open_tab_1"),
141 Action::OpenTab2 => Some("open_tab_2"),
142 Action::OpenTab3 => Some("open_tab_3"),
143 Action::OpenTab4 => Some("open_tab_4"),
144 Action::OpenTab5 => Some("open_tab_5"),
145 Action::OpenSearch => Some("search"),
146 Action::SubmitSearch => Some("submit_search"),
147 Action::CloseSearch => Some("close_search"),
148 Action::CycleSearchMode => Some("cycle_search_mode"),
149 Action::NextSearchResult => Some("next_search_result"),
150 Action::PrevSearchResult => Some("prev_search_result"),
151 Action::GoToInbox => Some("go_inbox"),
152 Action::GoToStarred => Some("go_starred"),
153 Action::GoToSent => Some("go_sent"),
154 Action::GoToDrafts => Some("go_drafts"),
155 Action::GoToAllMail => Some("go_all_mail"),
156 Action::OpenSubscriptions => Some("open_subscriptions"),
157 Action::GoToLabel => Some("go_label"),
158 Action::OpenCommandPalette => Some("command_palette"),
159 Action::CloseCommandPalette => Some("close_command_palette"),
160 Action::SyncNow => Some("sync"),
161 Action::Compose => Some("compose"),
162 Action::Reply => Some("reply"),
163 Action::ReplyAll => Some("reply_all"),
164 Action::Forward => Some("forward"),
165 Action::Archive => Some("archive"),
166 Action::MarkReadAndArchive => Some("mark_read_archive"),
167 Action::Trash => Some("trash"),
168 Action::Spam => Some("spam"),
169 Action::Star => Some("star"),
170 Action::MarkRead => Some("mark_read"),
171 Action::MarkUnread => Some("mark_unread"),
172 Action::ApplyLabel => Some("apply_label"),
173 Action::MoveToLabel => Some("move_to_label"),
174 Action::Unsubscribe => Some("unsubscribe"),
175 Action::Snooze => Some("snooze"),
176 Action::OpenInBrowser => Some("open_in_browser"),
177 Action::ToggleReaderMode => Some("toggle_reader_mode"),
178 Action::ToggleSignature => Some("toggle_signature"),
179 Action::ToggleSelect => Some("toggle_select"),
180 Action::VisualLineMode => Some("visual_line_mode"),
181 Action::AttachmentList => Some("attachment_list"),
182 Action::OpenLinks => Some("open_links"),
183 Action::ToggleFullscreen => Some("toggle_fullscreen"),
184 Action::ExportThread => Some("export_thread"),
185 Action::Help => Some("help"),
186 Action::ToggleMailListMode => Some("toggle_mail_list_mode"),
187 Action::RefreshRules => Some("refresh_rules"),
188 Action::RefreshDiagnostics => Some("refresh_diagnostics"),
189 Action::RefreshAccounts => Some("refresh_accounts"),
190 Action::GenerateBugReport => Some("generate_bug_report"),
191 Action::OpenRuleFormNew => Some("open_rule_form_new"),
192 Action::OpenRuleFormEdit => Some("open_rule_form_edit"),
193 Action::ToggleRuleEnabled => Some("toggle_rule_enabled"),
194 Action::ShowRuleDryRun => Some("show_rule_dry_run"),
195 Action::ShowRuleHistory => Some("show_rule_history"),
196 Action::DeleteRule => Some("delete_rule"),
197 Action::OpenAccountFormNew => Some("open_account_form_new"),
198 Action::TestAccountForm => Some("test_account_form"),
199 Action::SetDefaultAccount => Some("set_default_account"),
200 Action::Noop => Some("noop"),
201 Action::OpenMessageView
202 | Action::CloseMessageView
203 | Action::SelectLabel(_)
204 | Action::SelectSavedSearch(_, _)
205 | Action::ClearFilter
206 | Action::SaveRuleForm
207 | Action::SaveAccountForm
208 | Action::ReauthorizeAccountForm
209 | Action::ConfirmUnsubscribeOnly
210 | Action::ConfirmUnsubscribeAndArchiveSender
211 | Action::CancelUnsubscribe
212 | Action::PatternSelect(_) => None,
213 }
214}