use crate::{
model::settings::Shortcuts,
view::{
article::{self, TogglePlainText},
board,
component::{composer, ribbon},
create, info, menubar,
root::{
self, CloseProject, NewArticle, NewBoard, NewSession, NewTable, NextEntry, OpenProject,
OpenSettings, PrevEntry, ToggleSidebar,
},
table,
},
};
use bezel::{
gpui::{App, DummyKeyboardMapper, KeyBinding, KeybindingKeystroke, Keystroke, SharedString},
ui::{focus, input, keys},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Command {
OpenSettings,
NewSession,
NewBoard,
NewArticle,
NewTable,
OpenProject,
CloseProject,
ToggleSidebar,
NextEntry,
PrevEntry,
PlainText,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Menu {
App,
File,
View,
}
impl Menu {
pub const ALL: [Self; 3] = [Self::App, Self::File, Self::View];
pub fn title(self) -> &'static str {
match self {
Self::App => "cydonia",
Self::File => "File",
Self::View => "View",
}
}
}
impl Command {
pub const ALL: [Self; 11] = [
Self::OpenSettings,
Self::NewSession,
Self::NewBoard,
Self::NewArticle,
Self::NewTable,
Self::OpenProject,
Self::CloseProject,
Self::ToggleSidebar,
Self::NextEntry,
Self::PrevEntry,
Self::PlainText,
];
pub fn key(self) -> &'static str {
match self {
Self::OpenSettings => "open_settings",
Self::NewSession => "new_session",
Self::NewBoard => "new_board",
Self::NewArticle => "new_article",
Self::NewTable => "new_table",
Self::OpenProject => "open_project",
Self::CloseProject => "close_project",
Self::ToggleSidebar => "toggle_sidebar",
Self::NextEntry => "next_entry",
Self::PrevEntry => "prev_entry",
Self::PlainText => "plain_text",
}
}
pub fn title(self) -> &'static str {
match self {
Self::OpenSettings => "Settings…",
Self::NewSession => "New Session",
Self::NewBoard => "New Board",
Self::NewArticle => "New Article",
Self::NewTable => "New Table",
Self::OpenProject => "Open Project…",
Self::CloseProject => "Close Project",
Self::ToggleSidebar => "Toggle Sidebar",
Self::NextEntry => "Next Entry",
Self::PrevEntry => "Previous Entry",
Self::PlainText => "Plain Text",
}
}
pub fn menu(self) -> Menu {
match self {
Self::OpenSettings => Menu::App,
Self::NewSession
| Self::NewBoard
| Self::NewArticle
| Self::NewTable
| Self::OpenProject
| Self::CloseProject => Menu::File,
Self::ToggleSidebar | Self::NextEntry | Self::PrevEntry | Self::PlainText => Menu::View,
}
}
pub fn default(self) -> Option<&'static str> {
Some(match self {
Self::OpenSettings => "cmd-,",
Self::NewSession => "cmd-n",
Self::NewBoard | Self::NewArticle | Self::NewTable | Self::CloseProject => return None,
Self::OpenProject => "cmd-o",
Self::ToggleSidebar => "cmd-b",
Self::NextEntry => "alt-cmd-right",
Self::PrevEntry => "alt-cmd-left",
Self::PlainText => "cmd-e",
})
}
fn binding(self, chord: Option<&str>) -> Option<KeyBinding> {
let chord = chord?;
Some(match self {
Self::OpenSettings => KeyBinding::new(chord, OpenSettings, None),
Self::NewSession => KeyBinding::new(chord, NewSession, None),
Self::NewBoard => KeyBinding::new(chord, NewBoard, None),
Self::NewArticle => KeyBinding::new(chord, NewArticle, None),
Self::NewTable => KeyBinding::new(chord, NewTable, None),
Self::OpenProject => KeyBinding::new(chord, OpenProject, None),
Self::CloseProject => KeyBinding::new(chord, CloseProject, None),
Self::ToggleSidebar => KeyBinding::new(chord, ToggleSidebar, None),
Self::NextEntry => KeyBinding::new(chord, NextEntry, None),
Self::PrevEntry => KeyBinding::new(chord, PrevEntry, None),
Self::PlainText => KeyBinding::new(chord, TogglePlainText, None),
})
}
}
pub fn chord(command: Command, shortcuts: &Shortcuts) -> Option<&str> {
match shortcuts.get(command.key()) {
Some("") => None,
Some(chord) if parses(chord) => Some(chord),
Some(_) | None => command.default(),
}
}
pub fn parses(chord: &str) -> bool {
let mut strokes = chord.split_whitespace().peekable();
strokes.peek().is_some() && strokes.all(|stroke| Keystroke::parse(stroke).is_ok())
}
pub fn glyphs(chord: &str) -> Option<SharedString> {
let strokes = chord
.split_whitespace()
.map(|stroke| {
Keystroke::parse(stroke).ok().map(|stroke| {
KeybindingKeystroke::new_with_mapper(stroke, false, &DummyKeyboardMapper)
})
})
.collect::<Option<Vec<_>>>()?;
(!strokes.is_empty()).then(|| keys::format(&strokes))
}
pub fn label(command: Command, shortcuts: &Shortcuts) -> Option<SharedString> {
glyphs(chord(command, shortcuts)?)
}
pub fn claimed(wanted: &str, mine: Command, shortcuts: &Shortcuts) -> Vec<Command> {
Command::ALL
.into_iter()
.filter(|&command| command != mine && chord(command, shortcuts) == Some(wanted))
.collect()
}
pub fn bind_all(shortcuts: &Shortcuts, cx: &mut App) {
cx.clear_key_bindings();
cx.bind_keys(input::bindings());
cx.bind_keys(focus::bindings());
cx.bind_keys(editor::keys::bindings());
cx.bind_keys(article::bindings());
cx.bind_keys(board::bindings());
cx.bind_keys(composer::bindings());
cx.bind_keys(create::bindings());
cx.bind_keys(info::bindings());
cx.bind_keys(ribbon::bindings());
cx.bind_keys(table::bindings());
cx.bind_keys(root::bindings());
cx.bind_keys(menubar::bindings());
cx.bind_keys(
Command::ALL
.into_iter()
.filter_map(|command| command.binding(chord(command, shortcuts)))
.collect::<Vec<_>>(),
);
}
pub fn rebind(shortcuts: &Shortcuts, cx: &mut App) {
bind_all(shortcuts, cx);
menubar::refresh(cx);
}