mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-08 12:35:26 +00:00
refactor(config)!: simplify keybindings configuration syntax (#404)
BREAKING CHANGE: mode keybindings dropped in favor of a global table **What this means in practice:** ```toml [keybindings.Channel] quit = ["esc", "ctrl-c"] # ... [keybindings.RemoteControl] quit = ["esc", "ctrl-c"] # ... [keybindings.SendToChannel] quit = ["esc", "ctrl-c"] # ... ``` are being replaced with ```toml [keybindings] quit = ["esc", "ctrl-c"] # ... ``` Mode keybindings were I believe a premature optimization which only brought additional complexity and redundancy to the code and did not provide any real functionality in the current state of things for end users.
This commit is contained in:
parent
47ea5a2b68
commit
7a85728da6
@ -72,7 +72,7 @@ theme = "TwoDark"
|
|||||||
#
|
#
|
||||||
# Channel mode
|
# Channel mode
|
||||||
# ------------------------
|
# ------------------------
|
||||||
[keybindings.Channel]
|
[keybindings]
|
||||||
# Quit the application
|
# Quit the application
|
||||||
quit = ["esc", "ctrl-c"]
|
quit = ["esc", "ctrl-c"]
|
||||||
# Scrolling through entries
|
# Scrolling through entries
|
||||||
@ -101,46 +101,6 @@ toggle_help = "ctrl-g"
|
|||||||
toggle_preview = "ctrl-o"
|
toggle_preview = "ctrl-o"
|
||||||
|
|
||||||
|
|
||||||
# Remote control mode
|
|
||||||
# -------------------------------
|
|
||||||
[keybindings.RemoteControl]
|
|
||||||
# Quit the application
|
|
||||||
quit = "esc"
|
|
||||||
# Scrolling through entries
|
|
||||||
select_next_entry = ["down", "ctrl-n", "ctrl-j"]
|
|
||||||
select_prev_entry = ["up", "ctrl-p", "ctrl-k"]
|
|
||||||
select_next_page = "pagedown"
|
|
||||||
select_prev_page = "pageup"
|
|
||||||
# Select an entry
|
|
||||||
select_entry = "enter"
|
|
||||||
# Toggle the remote control mode
|
|
||||||
toggle_remote_control = "ctrl-r"
|
|
||||||
# Toggle the help bar
|
|
||||||
toggle_help = "ctrl-g"
|
|
||||||
# Toggle the preview panel
|
|
||||||
toggle_preview = "ctrl-o"
|
|
||||||
|
|
||||||
|
|
||||||
# Send to channel mode
|
|
||||||
# --------------------------------
|
|
||||||
[keybindings.SendToChannel]
|
|
||||||
# Quit the application
|
|
||||||
quit = "esc"
|
|
||||||
# Scrolling through entries
|
|
||||||
select_next_entry = ["down", "ctrl-n", "ctrl-j"]
|
|
||||||
select_prev_entry = ["up", "ctrl-p", "ctrl-k"]
|
|
||||||
select_next_page = "pagedown"
|
|
||||||
select_prev_page = "pageup"
|
|
||||||
# Select an entry
|
|
||||||
select_entry = "enter"
|
|
||||||
# Toggle the send to channel mode
|
|
||||||
toggle_send_to_channel = "ctrl-s"
|
|
||||||
# Toggle the help bar
|
|
||||||
toggle_help = "ctrl-g"
|
|
||||||
# Toggle the preview panel
|
|
||||||
toggle_preview = "ctrl-o"
|
|
||||||
|
|
||||||
|
|
||||||
# Shell integration
|
# Shell integration
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
@ -6,7 +6,9 @@ use tracing::{debug, info, trace};
|
|||||||
|
|
||||||
use crate::channels::entry::Entry;
|
use crate::channels::entry::Entry;
|
||||||
use crate::channels::TelevisionChannel;
|
use crate::channels::TelevisionChannel;
|
||||||
use crate::config::{parse_key, Config};
|
use crate::config::{
|
||||||
|
merge_keybindings, parse_key, Binding, Config, KeyBindings,
|
||||||
|
};
|
||||||
use crate::keymap::Keymap;
|
use crate::keymap::Keymap;
|
||||||
use crate::render::UiState;
|
use crate::render::UiState;
|
||||||
use crate::television::{Mode, Television};
|
use crate::television::{Mode, Television};
|
||||||
@ -109,16 +111,22 @@ impl App {
|
|||||||
let (_, event_rx) = mpsc::unbounded_channel();
|
let (_, event_rx) = mpsc::unbounded_channel();
|
||||||
let (event_abort_tx, _) = mpsc::unbounded_channel();
|
let (event_abort_tx, _) = mpsc::unbounded_channel();
|
||||||
let tick_rate = config.config.tick_rate;
|
let tick_rate = config.config.tick_rate;
|
||||||
let keymap = Keymap::from(&config.keybindings).with_mode_mappings(
|
let keybindings = merge_keybindings(config.keybindings.clone(), {
|
||||||
Mode::Channel,
|
&KeyBindings::from(passthrough_keybindings.iter().filter_map(
|
||||||
passthrough_keybindings
|
|s| match parse_key(s) {
|
||||||
.iter()
|
Ok(key) => Some((
|
||||||
.flat_map(|s| match parse_key(s) {
|
Action::SelectPassthrough(s.to_string()),
|
||||||
Ok(key) => Ok((key, Action::SelectPassthrough(s.clone()))),
|
Binding::SingleKey(key),
|
||||||
Err(e) => Err(e),
|
)),
|
||||||
})
|
Err(e) => {
|
||||||
.collect(),
|
debug!("Failed to parse keybinding: {}", e);
|
||||||
);
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
});
|
||||||
|
let keymap = Keymap::from(&keybindings);
|
||||||
|
|
||||||
debug!("{:?}", keymap);
|
debug!("{:?}", keymap);
|
||||||
let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel();
|
let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel();
|
||||||
let television =
|
let television =
|
||||||
@ -258,14 +266,13 @@ impl App {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
// get action based on keybindings
|
// get action based on keybindings
|
||||||
self.keymap
|
self.keymap.get(&keycode).cloned().unwrap_or(
|
||||||
.get(&self.television.mode)
|
if let Key::Char(c) = keycode {
|
||||||
.and_then(|keymap| keymap.get(&keycode).cloned())
|
|
||||||
.unwrap_or(if let Key::Char(c) = keycode {
|
|
||||||
Action::AddInputChar(c)
|
Action::AddInputChar(c)
|
||||||
} else {
|
} else {
|
||||||
Action::NoOp
|
Action::NoOp
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// terminal events
|
// terminal events
|
||||||
Event::Tick => Action::Tick,
|
Event::Tick => Action::Tick,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::action::Action;
|
use crate::action::Action;
|
||||||
use crate::event::{convert_raw_event_to_key, Key};
|
use crate::event::{convert_raw_event_to_key, Key};
|
||||||
use crate::television::Mode;
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
@ -30,7 +29,16 @@ impl Display for Binding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct KeyBindings(pub FxHashMap<Mode, FxHashMap<Action, Binding>>);
|
pub struct KeyBindings(pub FxHashMap<Action, Binding>);
|
||||||
|
|
||||||
|
impl<I> From<I> for KeyBindings
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (Action, Binding)>,
|
||||||
|
{
|
||||||
|
fn from(iter: I) -> Self {
|
||||||
|
KeyBindings(iter.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for KeyBindings {
|
impl Hash for KeyBindings {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
@ -40,7 +48,7 @@ impl Hash for KeyBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for KeyBindings {
|
impl Deref for KeyBindings {
|
||||||
type Target = FxHashMap<Mode, FxHashMap<Action, Binding>>;
|
type Target = FxHashMap<Action, Binding>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
@ -52,27 +60,20 @@ impl DerefMut for KeyBindings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merge two sets of keybindings together.
|
||||||
|
///
|
||||||
|
/// Note that this function won't "meld", for a given action, the bindings from the first set
|
||||||
|
/// with the bindings from the second set. Instead, it will simply overwrite them with the second
|
||||||
|
/// set's keys.
|
||||||
|
/// This is because it is assumed that the second set will be the user's custom keybindings, and
|
||||||
|
/// they should take precedence over the default ones, effectively replacing them to avoid
|
||||||
|
/// conflicts.
|
||||||
pub fn merge_keybindings(
|
pub fn merge_keybindings(
|
||||||
mut keybindings: KeyBindings,
|
mut keybindings: KeyBindings,
|
||||||
new_keybindings: &KeyBindings,
|
new_keybindings: &KeyBindings,
|
||||||
) -> KeyBindings {
|
) -> KeyBindings {
|
||||||
for (mode, bindings) in new_keybindings.iter() {
|
for (action, binding) in new_keybindings.iter() {
|
||||||
for (action, binding) in bindings {
|
keybindings.insert(action.clone(), binding.clone());
|
||||||
match keybindings.get_mut(mode) {
|
|
||||||
Some(mode_bindings) => {
|
|
||||||
mode_bindings.insert(action.clone(), binding.clone());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
keybindings.insert(
|
|
||||||
*mode,
|
|
||||||
[(action.clone(), binding.clone())]
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
keybindings
|
keybindings
|
||||||
}
|
}
|
||||||
@ -89,43 +90,30 @@ impl<'de> Deserialize<'de> for KeyBindings {
|
|||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let parsed_map = FxHashMap::<
|
let parsed_map =
|
||||||
Mode,
|
FxHashMap::<Action, SerializedBinding>::deserialize(deserializer)?;
|
||||||
FxHashMap<Action, SerializedBinding>,
|
|
||||||
>::deserialize(deserializer)?;
|
|
||||||
|
|
||||||
let keybindings: FxHashMap<Mode, FxHashMap<Action, Binding>> =
|
let keybindings: FxHashMap<Action, Binding> = parsed_map
|
||||||
parsed_map
|
|
||||||
.into_iter()
|
|
||||||
.map(|(mode, inner_map)| {
|
|
||||||
let converted_inner_map = inner_map
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(cmd, binding)| {
|
.map(|(cmd, binding)| {
|
||||||
(
|
(
|
||||||
cmd,
|
cmd,
|
||||||
match binding {
|
match binding {
|
||||||
SerializedBinding::SingleKey(key_str) => {
|
SerializedBinding::SingleKey(key_str) => {
|
||||||
Binding::SingleKey(
|
Binding::SingleKey(parse_key(&key_str).unwrap())
|
||||||
parse_key(&key_str).unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
SerializedBinding::MultipleKeys(
|
SerializedBinding::MultipleKeys(keys_str) => {
|
||||||
keys_str,
|
Binding::MultipleKeys(
|
||||||
) => Binding::MultipleKeys(
|
|
||||||
keys_str
|
keys_str
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key_str| {
|
.map(|key_str| parse_key(key_str).unwrap())
|
||||||
parse_key(key_str).unwrap()
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(mode, converted_inner_map)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(KeyBindings(keybindings))
|
Ok(KeyBindings(keybindings))
|
||||||
}
|
}
|
||||||
@ -380,4 +368,127 @@ mod tests {
|
|||||||
KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT)
|
KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_keybindings() {
|
||||||
|
let keybindings: KeyBindings = toml::from_str(
|
||||||
|
r#"
|
||||||
|
# Quit the application
|
||||||
|
quit = ["esc", "ctrl-c"]
|
||||||
|
# Scrolling through entries
|
||||||
|
select_next_entry = ["down", "ctrl-n", "ctrl-j"]
|
||||||
|
select_prev_entry = ["up", "ctrl-p", "ctrl-k"]
|
||||||
|
select_next_page = "pagedown"
|
||||||
|
select_prev_page = "pageup"
|
||||||
|
# Scrolling the preview pane
|
||||||
|
scroll_preview_half_page_down = "ctrl-d"
|
||||||
|
scroll_preview_half_page_up = "ctrl-u"
|
||||||
|
# Add entry to selection and move to the next entry
|
||||||
|
toggle_selection_down = "tab"
|
||||||
|
# Add entry to selection and move to the previous entry
|
||||||
|
toggle_selection_up = "backtab"
|
||||||
|
# Confirm selection
|
||||||
|
confirm_selection = "enter"
|
||||||
|
# Copy the selected entry to the clipboard
|
||||||
|
copy_entry_to_clipboard = "ctrl-y"
|
||||||
|
# Toggle the remote control mode
|
||||||
|
toggle_remote_control = "ctrl-r"
|
||||||
|
# Toggle the send to channel mode
|
||||||
|
toggle_send_to_channel = "ctrl-s"
|
||||||
|
# Toggle the help bar
|
||||||
|
toggle_help = "ctrl-g"
|
||||||
|
# Toggle the preview panel
|
||||||
|
toggle_preview = "ctrl-o"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
keybindings,
|
||||||
|
KeyBindings::from(vec![
|
||||||
|
(
|
||||||
|
Action::Quit,
|
||||||
|
Binding::MultipleKeys(vec![Key::Esc, Key::Ctrl('c'),])
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Action::SelectNextEntry,
|
||||||
|
Binding::MultipleKeys(vec![
|
||||||
|
Key::Down,
|
||||||
|
Key::Ctrl('n'),
|
||||||
|
Key::Ctrl('j'),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Action::SelectPrevEntry,
|
||||||
|
Binding::MultipleKeys(vec![
|
||||||
|
Key::Up,
|
||||||
|
Key::Ctrl('p'),
|
||||||
|
Key::Ctrl('k'),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
(Action::SelectNextPage, Binding::SingleKey(Key::PageDown)),
|
||||||
|
(Action::SelectPrevPage, Binding::SingleKey(Key::PageUp)),
|
||||||
|
(
|
||||||
|
Action::ScrollPreviewHalfPageDown,
|
||||||
|
Binding::SingleKey(Key::Ctrl('d'))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Action::ScrollPreviewHalfPageUp,
|
||||||
|
Binding::SingleKey(Key::Ctrl('u'))
|
||||||
|
),
|
||||||
|
(Action::ToggleSelectionDown, Binding::SingleKey(Key::Tab)),
|
||||||
|
(Action::ToggleSelectionUp, Binding::SingleKey(Key::BackTab)),
|
||||||
|
(Action::ConfirmSelection, Binding::SingleKey(Key::Enter)),
|
||||||
|
(
|
||||||
|
Action::CopyEntryToClipboard,
|
||||||
|
Binding::SingleKey(Key::Ctrl('y'))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Action::ToggleRemoteControl,
|
||||||
|
Binding::SingleKey(Key::Ctrl('r'))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Action::ToggleSendToChannel,
|
||||||
|
Binding::SingleKey(Key::Ctrl('s'))
|
||||||
|
),
|
||||||
|
(Action::ToggleHelp, Binding::SingleKey(Key::Ctrl('g'))),
|
||||||
|
(Action::TogglePreview, Binding::SingleKey(Key::Ctrl('o'))),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_keybindings() {
|
||||||
|
let base_keybindings = KeyBindings::from(vec![
|
||||||
|
(Action::Quit, Binding::SingleKey(Key::Esc)),
|
||||||
|
(
|
||||||
|
Action::SelectNextEntry,
|
||||||
|
Binding::MultipleKeys(vec![Key::Down, Key::Ctrl('n')]),
|
||||||
|
),
|
||||||
|
(Action::SelectPrevEntry, Binding::SingleKey(Key::Up)),
|
||||||
|
]);
|
||||||
|
let custom_keybindings = KeyBindings::from(vec![
|
||||||
|
(Action::SelectNextEntry, Binding::SingleKey(Key::Ctrl('j'))),
|
||||||
|
(
|
||||||
|
Action::SelectPrevEntry,
|
||||||
|
Binding::MultipleKeys(vec![Key::Up, Key::Ctrl('k')]),
|
||||||
|
),
|
||||||
|
(Action::SelectNextPage, Binding::SingleKey(Key::PageDown)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let merged = merge_keybindings(base_keybindings, &custom_keybindings);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
merged,
|
||||||
|
KeyBindings::from(vec![
|
||||||
|
(Action::Quit, Binding::SingleKey(Key::Esc)),
|
||||||
|
(Action::SelectNextEntry, Binding::SingleKey(Key::Ctrl('j'))),
|
||||||
|
(
|
||||||
|
Action::SelectPrevEntry,
|
||||||
|
Binding::MultipleKeys(vec![Key::Up, Key::Ctrl('k')]),
|
||||||
|
),
|
||||||
|
(Action::SelectNextPage, Binding::SingleKey(Key::PageDown)),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use keybindings::merge_keybindings;
|
pub use keybindings::merge_keybindings;
|
||||||
pub use keybindings::{parse_key, Binding, KeyBindings};
|
pub use keybindings::{parse_key, Binding, KeyBindings};
|
||||||
use previewers::PreviewersConfig;
|
use previewers::PreviewersConfig;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -100,6 +100,17 @@ pub fn default_config_from_file() -> Result<Config> {
|
|||||||
Ok(default_config)
|
Ok(default_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const USER_CONFIG_ERROR_MSG: &str = "
|
||||||
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║ If this follows a recent update, it is likely due to a breaking change in ║
|
||||||
|
║ the configuration format. ║
|
||||||
|
║ ║
|
||||||
|
║ Check https://github.com/alexpasmantier/television/releases/latest for the ║
|
||||||
|
║ latest release notes. ║
|
||||||
|
║ ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════════════════╝";
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
||||||
pub fn new(config_env: &ConfigEnv) -> Result<Self> {
|
pub fn new(config_env: &ConfigEnv) -> Result<Self> {
|
||||||
@ -134,8 +145,11 @@ impl Config {
|
|||||||
fn load_user_config(config_dir: &Path) -> Result<Self> {
|
fn load_user_config(config_dir: &Path) -> Result<Self> {
|
||||||
let path = config_dir.join(CONFIG_FILE_NAME);
|
let path = config_dir.join(CONFIG_FILE_NAME);
|
||||||
let contents = std::fs::read_to_string(&path)?;
|
let contents = std::fs::read_to_string(&path)?;
|
||||||
let user_cfg: Config = toml::from_str(&contents)
|
let user_cfg: Config = toml::from_str(&contents).context(format!(
|
||||||
.context("Error parsing configuration file.")?;
|
"Error parsing configuration file: {}\n{}",
|
||||||
|
path.display(),
|
||||||
|
USER_CONFIG_ERROR_MSG,
|
||||||
|
))?;
|
||||||
Ok(user_cfg)
|
Ok(user_cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +260,6 @@ fn default_tick_rate() -> f64 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::action::Action;
|
use crate::action::Action;
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::television::Mode;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
@ -322,11 +335,9 @@ mod tests {
|
|||||||
[previewers.file]
|
[previewers.file]
|
||||||
theme = "Visual Studio Dark"
|
theme = "Visual Studio Dark"
|
||||||
|
|
||||||
[keybindings.Channel]
|
[keybindings]
|
||||||
toggle_help = ["ctrl-a", "ctrl-b"]
|
toggle_help = ["ctrl-a", "ctrl-b"]
|
||||||
|
confirm_selection = "ctrl-enter"
|
||||||
[keybindings.RemoteControl]
|
|
||||||
toggle_help = ["ctrl-c", "ctrl-d"]
|
|
||||||
|
|
||||||
[shell_integration.commands]
|
[shell_integration.commands]
|
||||||
"git add" = "git-diff"
|
"git add" = "git-diff"
|
||||||
@ -358,33 +369,15 @@ mod tests {
|
|||||||
default_config.ui.theme = "television".to_string();
|
default_config.ui.theme = "television".to_string();
|
||||||
default_config.previewers.file.theme =
|
default_config.previewers.file.theme =
|
||||||
"Visual Studio Dark".to_string();
|
"Visual Studio Dark".to_string();
|
||||||
default_config
|
default_config.keybindings.extend({
|
||||||
.keybindings
|
|
||||||
.get_mut(&Mode::Channel)
|
|
||||||
.unwrap()
|
|
||||||
.extend({
|
|
||||||
let mut map = FxHashMap::default();
|
let mut map = FxHashMap::default();
|
||||||
map.insert(
|
map.insert(
|
||||||
Action::ToggleHelp,
|
Action::ToggleHelp,
|
||||||
Binding::MultipleKeys(vec![
|
Binding::MultipleKeys(vec![Key::Ctrl('a'), Key::Ctrl('b')]),
|
||||||
Key::Ctrl('a'),
|
|
||||||
Key::Ctrl('b'),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
map
|
|
||||||
});
|
|
||||||
default_config
|
|
||||||
.keybindings
|
|
||||||
.get_mut(&Mode::RemoteControl)
|
|
||||||
.unwrap()
|
|
||||||
.extend({
|
|
||||||
let mut map = FxHashMap::default();
|
|
||||||
map.insert(
|
map.insert(
|
||||||
Action::ToggleHelp,
|
Action::ConfirmSelection,
|
||||||
Binding::MultipleKeys(vec![
|
Binding::SingleKey(Key::CtrlEnter),
|
||||||
Key::Ctrl('c'),
|
|
||||||
Key::Ctrl('d'),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
map
|
map
|
||||||
});
|
});
|
||||||
|
@ -193,16 +193,12 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
|||||||
&ctx.colorscheme,
|
&ctx.colorscheme,
|
||||||
&ctx.config
|
&ctx.config
|
||||||
.keybindings
|
.keybindings
|
||||||
.get(&ctx.tv_state.mode)
|
|
||||||
.unwrap()
|
|
||||||
.get(&Action::ToggleHelp)
|
.get(&Action::ToggleHelp)
|
||||||
// just display the first keybinding
|
// just display the first keybinding
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
&ctx.config
|
&ctx.config
|
||||||
.keybindings
|
.keybindings
|
||||||
.get(&ctx.tv_state.mode)
|
|
||||||
.unwrap()
|
|
||||||
.get(&Action::TogglePreview)
|
.get(&Action::TogglePreview)
|
||||||
// just display the first keybinding
|
// just display the first keybinding
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::television::Mode;
|
|
||||||
|
|
||||||
use crate::action::Action;
|
use crate::action::Action;
|
||||||
use crate::config::{Binding, KeyBindings};
|
use crate::config::{Binding, KeyBindings};
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
@ -13,20 +11,15 @@ use crate::event::Key;
|
|||||||
/// # Example:
|
/// # Example:
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// Keymap {
|
/// Keymap {
|
||||||
/// Mode::Channel => {
|
|
||||||
/// Key::Char('j') => Action::MoveDown,
|
/// Key::Char('j') => Action::MoveDown,
|
||||||
/// Key::Char('k') => Action::MoveUp,
|
/// Key::Char('k') => Action::MoveUp,
|
||||||
/// Key::Char('q') => Action::Quit,
|
/// Key::Char('q') => Action::Quit,
|
||||||
/// },
|
|
||||||
/// Mode::Insert => {
|
|
||||||
/// Key::Ctrl('a') => Action::MoveToStart,
|
|
||||||
/// },
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Keymap(pub FxHashMap<Mode, FxHashMap<Key, Action>>);
|
pub struct Keymap(pub FxHashMap<Key, Action>);
|
||||||
|
|
||||||
impl Deref for Keymap {
|
impl Deref for Keymap {
|
||||||
type Target = FxHashMap<Mode, FxHashMap<Key, Action>>;
|
type Target = FxHashMap<Key, Action>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
@ -40,38 +33,18 @@ impl From<&KeyBindings> for Keymap {
|
|||||||
/// key events.
|
/// key events.
|
||||||
fn from(keybindings: &KeyBindings) -> Self {
|
fn from(keybindings: &KeyBindings) -> Self {
|
||||||
let mut keymap = FxHashMap::default();
|
let mut keymap = FxHashMap::default();
|
||||||
for (mode, bindings) in keybindings.iter() {
|
for (action, binding) in keybindings.iter() {
|
||||||
let mut mode_keymap = FxHashMap::default();
|
|
||||||
for (action, binding) in bindings {
|
|
||||||
match binding {
|
match binding {
|
||||||
Binding::SingleKey(key) => {
|
Binding::SingleKey(key) => {
|
||||||
mode_keymap.insert(*key, action.clone());
|
keymap.insert(*key, action.clone());
|
||||||
}
|
}
|
||||||
Binding::MultipleKeys(keys) => {
|
Binding::MultipleKeys(keys) => {
|
||||||
for key in keys {
|
for key in keys {
|
||||||
mode_keymap.insert(*key, action.clone());
|
keymap.insert(*key, action.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keymap.insert(*mode, mode_keymap);
|
|
||||||
}
|
|
||||||
Self(keymap)
|
Self(keymap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keymap {
|
|
||||||
/// For a provided `Mode`, merge the given `mappings` into the keymap.
|
|
||||||
pub fn with_mode_mappings(
|
|
||||||
mut self,
|
|
||||||
mode: Mode,
|
|
||||||
mappings: Vec<(Key, Action)>,
|
|
||||||
) -> Self {
|
|
||||||
let mode_keymap = self.0.entry(mode).or_default();
|
|
||||||
|
|
||||||
for (key, action) in mappings {
|
|
||||||
mode_keymap.insert(key, action);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -146,15 +146,7 @@ fn serialized_keys_for_actions(
|
|||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
actions
|
actions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| {
|
.map(|a| keybindings.get(a).unwrap().clone().to_string())
|
||||||
keybindings
|
|
||||||
.get(&Mode::Channel)
|
|
||||||
.unwrap()
|
|
||||||
.get(a)
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user