mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 11:35:25 +00:00
fix(config): swap out default keymaps with user defined ones instead of stacking (#26)
* fix(config): swap out default keymaps with user defined ones instead of stacking * fix default configuration fallback
This commit is contained in:
parent
4f0daec63d
commit
06a4feb9f2
@ -35,51 +35,50 @@ theme = "Visual Studio Dark+"
|
||||
# Keybindings
|
||||
# ----------------------------------------------------------------------------
|
||||
#
|
||||
# Channel mode keybindings
|
||||
# Channel mode
|
||||
# ------------------------
|
||||
[keybindings.Channel]
|
||||
# Quit the application
|
||||
esc = "Quit"
|
||||
Quit = "esc"
|
||||
# Scrolling through entries
|
||||
down = "SelectNextEntry"
|
||||
ctrl-n = "SelectNextEntry"
|
||||
up = "SelectPrevEntry"
|
||||
ctrl-p = "SelectPrevEntry"
|
||||
SelectNextEntry = "down"
|
||||
SelectPrevEntry = "up"
|
||||
# Scrolling the preview pane
|
||||
ctrl-d = "ScrollPreviewHalfPageDown"
|
||||
ctrl-u = "ScrollPreviewHalfPageUp"
|
||||
ScrollPreviewHalfPageDown = "ctrl-d"
|
||||
ScrollPreviewHalfPageUp = "ctrl-u"
|
||||
# Select an entry
|
||||
enter = "SelectEntry"
|
||||
SelectEntry = "enter"
|
||||
# Copy the selected entry to the clipboard
|
||||
ctrl-y = "CopyEntryToClipboard"
|
||||
CopyEntryToClipboard = "ctrl-y"
|
||||
# Toggle the remote control mode
|
||||
ctrl-r = "ToggleRemoteControl"
|
||||
ToggleRemoteControl = "ctrl-r"
|
||||
# Toggle the send to channel mode
|
||||
ctrl-s = "ToggleSendToChannel"
|
||||
ToggleSendToChannel = "ctrl-s"
|
||||
|
||||
# Remote control mode keybindings
|
||||
|
||||
# Remote control mode
|
||||
# -------------------------------
|
||||
[keybindings.RemoteControl]
|
||||
# Quit the application
|
||||
esc = "Quit"
|
||||
Quit = "esc"
|
||||
# Scrolling through entries
|
||||
down = "SelectNextEntry"
|
||||
up = "SelectPrevEntry"
|
||||
ctrl-n = "SelectNextEntry"
|
||||
ctrl-p = "SelectPrevEntry"
|
||||
SelectNextEntry = "down"
|
||||
SelectPrevEntry = "up"
|
||||
# Select an entry
|
||||
enter = "SelectEntry"
|
||||
SelectEntry = "enter"
|
||||
# Toggle the remote control mode
|
||||
ctrl-r = "ToggleRemoteControl"
|
||||
ToggleRemoteControl = "ctrl-r"
|
||||
|
||||
# Send to channel mode keybindings
|
||||
|
||||
# Send to channel mode
|
||||
# --------------------------------
|
||||
[keybindings.SendToChannel]
|
||||
# Quit the application
|
||||
esc = "Quit"
|
||||
Quit = "esc"
|
||||
# Scrolling through entries
|
||||
down = "SelectNextEntry"
|
||||
up = "SelectPrevEntry"
|
||||
ctrl-n = "SelectNextEntry"
|
||||
ctrl-p = "SelectPrevEntry"
|
||||
SelectNextEntry = "down"
|
||||
SelectPrevEntry = "up"
|
||||
# Select an entry
|
||||
enter = "SelectEntry"
|
||||
SelectEntry = "enter"
|
||||
# Toggle the send to channel mode
|
||||
ctrl-s = "ToggleSendToChannel"
|
||||
ToggleSendToChannel = "ctrl-s"
|
||||
|
@ -2,7 +2,9 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
|
||||
/// The different actions that can be performed by the application.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display)]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, Hash,
|
||||
)]
|
||||
pub enum Action {
|
||||
// input actions
|
||||
/// Add a character to the input buffer.
|
||||
|
@ -1,9 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::Result;
|
||||
use derive_deref::Deref;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::config::KeyBindings;
|
||||
use crate::television::{Mode, Television};
|
||||
use crate::{
|
||||
action::Action,
|
||||
@ -14,10 +17,28 @@ use crate::{
|
||||
use television_channels::channels::TelevisionChannel;
|
||||
use television_channels::entry::Entry;
|
||||
|
||||
#[derive(Deref, Default)]
|
||||
pub struct Keymap(pub HashMap<Mode, HashMap<Key, Action>>);
|
||||
|
||||
impl From<&KeyBindings> for Keymap {
|
||||
fn from(keybindings: &KeyBindings) -> Self {
|
||||
let mut keymap = HashMap::new();
|
||||
for (mode, bindings) in keybindings.iter() {
|
||||
let mut mode_keymap = HashMap::new();
|
||||
for (action, key) in bindings {
|
||||
mode_keymap.insert(*key, action.clone());
|
||||
}
|
||||
keymap.insert(*mode, mode_keymap);
|
||||
}
|
||||
Self(keymap)
|
||||
}
|
||||
}
|
||||
|
||||
/// The main application struct that holds the state of the application.
|
||||
pub struct App {
|
||||
/// The configuration of the application.
|
||||
config: Config,
|
||||
keymap: Keymap,
|
||||
// maybe move these two into config instead of passing them
|
||||
// via the cli?
|
||||
tick_rate: f64,
|
||||
@ -51,14 +72,17 @@ impl App {
|
||||
let (_, event_rx) = mpsc::unbounded_channel();
|
||||
let (event_abort_tx, _) = mpsc::unbounded_channel();
|
||||
let television = Arc::new(Mutex::new(Television::new(channel)));
|
||||
let config = Config::new()?;
|
||||
let keymap = Keymap::from(&config.keybindings);
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
keymap,
|
||||
tick_rate,
|
||||
frame_rate,
|
||||
television,
|
||||
should_quit: false,
|
||||
should_suspend: false,
|
||||
config: Config::new()?,
|
||||
action_tx,
|
||||
action_rx,
|
||||
event_rx,
|
||||
@ -159,8 +183,7 @@ impl App {
|
||||
_ => {}
|
||||
}
|
||||
// get action based on keybindings
|
||||
self.config
|
||||
.keybindings
|
||||
self.keymap
|
||||
.get(&self.television.lock().await.mode)
|
||||
.and_then(|keymap| keymap.get(&keycode).cloned())
|
||||
.unwrap_or(if let Key::Char(c) = keycode {
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
event::{convert_raw_event_to_key, Key},
|
||||
television::Mode,
|
||||
};
|
||||
use color_eyre::Result;
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use directories::ProjectDirs;
|
||||
@ -113,47 +113,56 @@ const CONFIG_FILE_NAME: &str = "config.toml";
|
||||
|
||||
impl Config {
|
||||
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
||||
pub fn new() -> Result<Self, config::ConfigError> {
|
||||
pub fn new() -> Result<Self> {
|
||||
// Load the default_config values as base defaults
|
||||
let default_config: Config =
|
||||
toml::from_str(CONFIG).expect("default config should be valid");
|
||||
|
||||
// initialize the config builder
|
||||
let data_dir = get_data_dir();
|
||||
let config_dir = get_config_dir();
|
||||
let mut builder = config::Config::builder()
|
||||
.set_default("data_dir", data_dir.to_str().unwrap())?
|
||||
.set_default("config_dir", config_dir.to_str().unwrap())?;
|
||||
|
||||
// Load the default_config values as base defaults
|
||||
builder = builder.add_source(config::File::from_str(
|
||||
CONFIG,
|
||||
config::FileFormat::Toml,
|
||||
));
|
||||
|
||||
// Load the user's config file
|
||||
let source = config::File::from(config_dir.join(CONFIG_FILE_NAME))
|
||||
.format(config::FileFormat::Toml)
|
||||
.required(false);
|
||||
builder = builder.add_source(source);
|
||||
|
||||
if !config_dir.join(CONFIG_FILE_NAME).is_file() {
|
||||
if config_dir.join(CONFIG_FILE_NAME).is_file() {
|
||||
debug!("Found config file at {:?}", config_dir);
|
||||
let mut cfg: Self =
|
||||
builder.build()?.try_deserialize().with_context(|| {
|
||||
format!(
|
||||
"Error parsing config file at {:?}",
|
||||
config_dir.join(CONFIG_FILE_NAME)
|
||||
)
|
||||
})?;
|
||||
|
||||
for (mode, default_bindings) in default_config.keybindings.iter() {
|
||||
let user_bindings = cfg.keybindings.entry(*mode).or_default();
|
||||
for (command, key) in default_bindings {
|
||||
user_bindings
|
||||
.entry(command.clone())
|
||||
.or_insert_with(|| *key);
|
||||
}
|
||||
}
|
||||
|
||||
for (mode, default_styles) in default_config.styles.iter() {
|
||||
let user_styles = cfg.styles.entry(*mode).or_default();
|
||||
for (style_key, style) in default_styles {
|
||||
user_styles.entry(style_key.clone()).or_insert(*style);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Config: {:?}", cfg);
|
||||
Ok(cfg)
|
||||
} else {
|
||||
warn!("No config file found at {:?}", config_dir);
|
||||
Ok(default_config)
|
||||
}
|
||||
|
||||
let mut cfg: Self = builder.build()?.try_deserialize()?;
|
||||
|
||||
for (mode, default_bindings) in default_config.keybindings.iter() {
|
||||
let user_bindings = cfg.keybindings.entry(*mode).or_default();
|
||||
for (key, cmd) in default_bindings {
|
||||
user_bindings.entry(*key).or_insert_with(|| cmd.clone());
|
||||
}
|
||||
}
|
||||
for (mode, default_styles) in default_config.styles.iter() {
|
||||
let user_styles = cfg.styles.entry(*mode).or_default();
|
||||
for (style_key, style) in default_styles {
|
||||
user_styles.entry(style_key.clone()).or_insert(*style);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +197,7 @@ fn project_directory() -> Option<ProjectDirs> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct KeyBindings(pub HashMap<Mode, HashMap<Key, Action>>);
|
||||
pub struct KeyBindings(pub config::Map<Mode, config::Map<Action, Key>>);
|
||||
|
||||
impl<'de> Deserialize<'de> for KeyBindings {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
@ -196,7 +205,7 @@ impl<'de> Deserialize<'de> for KeyBindings {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let parsed_map =
|
||||
HashMap::<Mode, HashMap<String, Action>>::deserialize(
|
||||
HashMap::<Mode, HashMap<Action, String>>::deserialize(
|
||||
deserializer,
|
||||
)?;
|
||||
|
||||
@ -205,7 +214,7 @@ impl<'de> Deserialize<'de> for KeyBindings {
|
||||
.map(|(mode, inner_map)| {
|
||||
let converted_inner_map = inner_map
|
||||
.into_iter()
|
||||
.map(|(key_str, cmd)| (parse_key(&key_str).unwrap(), cmd))
|
||||
.map(|(cmd, key_str)| (cmd, parse_key(&key_str).unwrap()))
|
||||
.collect();
|
||||
(mode, converted_inner_map)
|
||||
})
|
||||
@ -573,9 +582,8 @@ mod tests {
|
||||
c.keybindings
|
||||
.get(&Mode::Channel)
|
||||
.unwrap()
|
||||
.get(&parse_key("esc").unwrap())
|
||||
.unwrap(),
|
||||
&Action::Quit
|
||||
.get(&Action::Quit),
|
||||
Some(&parse_key("esc").unwrap())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
let args = Cli::parse();
|
||||
|
||||
let mut app: App = App::new(
|
||||
match App::new(
|
||||
{
|
||||
if is_readable_stdin() {
|
||||
debug!("Using stdin channel");
|
||||
@ -42,13 +42,19 @@ async fn main() -> Result<()> {
|
||||
},
|
||||
args.tick_rate,
|
||||
args.frame_rate,
|
||||
)?;
|
||||
|
||||
if let Some(entry) = app.run(stdout().is_terminal()).await? {
|
||||
// print entry to stdout
|
||||
stdout().flush()?;
|
||||
info!("{:?}", entry);
|
||||
writeln!(stdout(), "{}", entry.stdout_repr())?;
|
||||
) {
|
||||
Ok(mut app) => {
|
||||
if let Some(entry) = app.run(stdout().is_terminal()).await? {
|
||||
// print entry to stdout
|
||||
stdout().flush()?;
|
||||
info!("{:?}", entry);
|
||||
writeln!(stdout(), "{}", entry.stdout_repr())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{err:?}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::app::Keymap;
|
||||
use crate::picker::Picker;
|
||||
use crate::ui::input::actions::InputActionHandler;
|
||||
use crate::ui::layout::{Dimensions, Layout};
|
||||
@ -32,6 +33,7 @@ pub enum Mode {
|
||||
pub struct Television {
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
pub config: Config,
|
||||
pub keymap: Keymap,
|
||||
pub(crate) channel: TelevisionChannel,
|
||||
pub(crate) remote_control: TelevisionChannel,
|
||||
pub mode: Mode,
|
||||
@ -62,6 +64,7 @@ impl Television {
|
||||
Self {
|
||||
action_tx: None,
|
||||
config: Config::default(),
|
||||
keymap: Keymap::default(),
|
||||
channel,
|
||||
remote_control: TelevisionChannel::RemoteControl(
|
||||
RemoteControl::default(),
|
||||
@ -228,6 +231,7 @@ impl Television {
|
||||
/// * `Result<()>` - An Ok result or an error.
|
||||
pub fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
self.config = config;
|
||||
self.keymap = Keymap::from(&self.config.keybindings);
|
||||
let previewer_config =
|
||||
std::convert::Into::<previewers::PreviewerConfig>::into(
|
||||
self.config.previewers.clone(),
|
||||
|
@ -211,8 +211,7 @@ impl Television {
|
||||
/// A reference to the keymap for the current mode.
|
||||
fn keymap_for_mode(&self) -> Result<&HashMap<Key, Action>> {
|
||||
let keymap = self
|
||||
.config
|
||||
.keybindings
|
||||
.keymap
|
||||
.get(&self.mode)
|
||||
.ok_or_eyre("No keybindings found for the current Mode")?;
|
||||
Ok(keymap)
|
||||
|
Loading…
x
Reference in New Issue
Block a user