Skip to content

Commit

Permalink
Pretty up KeyCombos representation in user-facing errors (#193)
Browse files Browse the repository at this point in the history
* Self review

* Improve user-facing keycombo representation
  • Loading branch information
CosmicHorrorDev authored Dec 11, 2023
1 parent 0a6c6e1 commit e618e88
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 32 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C debuginfo=0"

# TODO: build/test on stable and beta?

jobs:
build:
strategy:
Expand All @@ -29,6 +31,8 @@ jobs:
steps:
- uses: actions/checkout@v3

# FIXME: This is in the wrong area. It should be below setting up the
# toolchain
- name: Cache
uses: Swatinem/rust-cache@v2

Expand Down
1 change: 1 addition & 0 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ impl TokenSink for HtmlInterpreter {

self.push_current_textbox();
}
// FIXME: `input` is self closing. This never gets called
"input" => {
self.push_current_textbox();
self.state.element_stack.pop();
Expand Down
91 changes: 63 additions & 28 deletions src/keybindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ use std::{collections::BTreeMap, fmt, slice::Iter, str::FromStr, vec::IntoIter};
use action::Action;

use serde::Deserialize;
use winit::event::{ModifiersState, ScanCode, VirtualKeyCode};
use winit::event::{ModifiersState, ScanCode, VirtualKeyCode as VirtKey};

#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct Keybindings(Vec<(Action, KeyCombo)>);

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Key {
Resolved(VirtualKeyCode),
Resolved(VirtKey),
ScanCode(ScanCode),
}

Expand Down Expand Up @@ -49,16 +49,16 @@ impl Default for Keybindings {
}

impl Key {
pub fn new(resolved: Option<VirtualKeyCode>, scan_code: ScanCode) -> Self {
pub fn new(resolved: Option<VirtKey>, scan_code: ScanCode) -> Self {
match resolved {
Some(key_code) => Self::Resolved(key_code),
None => Self::ScanCode(scan_code),
}
}
}

impl From<VirtualKeyCode> for Key {
fn from(key_code: VirtualKeyCode) -> Self {
impl From<VirtKey> for Key {
fn from(key_code: VirtKey) -> Self {
Self::Resolved(key_code)
}
}
Expand All @@ -72,10 +72,10 @@ impl fmt::Display for Key {
.find_map(|&(key_str, key)| (*resolved == key).then_some(key_str));
match maybe_key {
Some(key) => f.write_str(key),
None => write!(f, "<unsupported: {:?}>", resolved),
None => write!(f, "<unsupported: {resolved:?}>"),
}
}
Key::ScanCode(_) => write!(f, "{:?}", self),
Key::ScanCode(scan_code) => write!(f, "<scan code: {scan_code}>"),
}
}
}
Expand All @@ -97,7 +97,43 @@ pub struct ModifiedKey(pub Key, pub ModifiersState);
impl fmt::Display for ModifiedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.1 == ModifiersState::default() {
write!(f, "{}", self.0)
let is_not_visible = [
VirtKey::F1,
VirtKey::F2,
VirtKey::F3,
VirtKey::F4,
VirtKey::F5,
VirtKey::F6,
VirtKey::F7,
VirtKey::F8,
VirtKey::F9,
VirtKey::F10,
VirtKey::F11,
VirtKey::F12,
VirtKey::Up,
VirtKey::Right,
VirtKey::Down,
VirtKey::Left,
VirtKey::Escape,
VirtKey::Tab,
VirtKey::Insert,
VirtKey::Delete,
VirtKey::Back,
VirtKey::Return,
VirtKey::Home,
VirtKey::End,
VirtKey::PageUp,
VirtKey::PageDown,
VirtKey::Space,
]
.map(Key::from)
.contains(&self.0);

if is_not_visible {
write!(f, "<{}>", self.0)
} else {
write!(f, "{}", self.0)
}
} else {
let mut mod_list = Vec::new();

Expand All @@ -114,14 +150,14 @@ impl fmt::Display for ModifiedKey {
mod_list.push("Shift");
}

let mods = mod_list.join(", ");
write!(f, "{{ {}, [{mods}] }}", self.0)
let mods = mod_list.join("+");
write!(f, "<{}+{}>", mods, self.0)
}
}
}

impl From<VirtualKeyCode> for ModifiedKey {
fn from(keycode: VirtualKeyCode) -> Self {
impl From<VirtKey> for ModifiedKey {
fn from(keycode: VirtKey) -> Self {
Self(Key::from(keycode), ModifiersState::empty())
}
}
Expand All @@ -131,12 +167,11 @@ pub struct KeyCombo(pub Vec<ModifiedKey>);

impl fmt::Display for KeyCombo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let keys = self
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
write!(f, "[{}]", keys)
for key in self.iter() {
write!(f, "{key}")?;
}

Ok(())
}
}

Expand All @@ -158,8 +193,8 @@ impl KeyCombo {
}
}

impl From<VirtualKeyCode> for KeyCombo {
fn from(key_code: VirtualKeyCode) -> Self {
impl From<VirtKey> for KeyCombo {
fn from(key_code: VirtKey) -> Self {
KeyCombo(vec![ModifiedKey::from(key_code)])
}
}
Expand Down Expand Up @@ -267,14 +302,14 @@ impl KeyCombos {
// We ignore modifier keys since they aren't considered part of combos
if let Key::Resolved(key) = &modified_key.0 {
if [
VirtualKeyCode::LAlt,
VirtualKeyCode::RAlt,
VirtualKeyCode::LControl,
VirtualKeyCode::RControl,
VirtualKeyCode::LWin,
VirtualKeyCode::RWin,
VirtualKeyCode::LShift,
VirtualKeyCode::RShift,
VirtKey::LAlt,
VirtKey::RAlt,
VirtKey::LControl,
VirtKey::RControl,
VirtKey::LWin,
VirtKey::RWin,
VirtKey::LShift,
VirtKey::RShift,
]
.contains(key)
{
Expand Down
7 changes: 6 additions & 1 deletion src/opts/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,18 @@ pub struct Config {
}

impl Config {
pub fn load_from_str(s: &str) -> anyhow::Result<Self> {
let config = toml::from_str(s)?;
Ok(config)
}

pub fn load_from_file(path: &Path) -> anyhow::Result<Self> {
let config_content = read_to_string(path).context(format!(
"Failed to read configuration file at '{}'",
path.display()
))?;

Ok(toml::from_str(&config_content)?)
Self::load_from_str(&config_content)
}

pub fn load_from_system() -> anyhow::Result<Self> {
Expand Down
66 changes: 63 additions & 3 deletions src/opts/tests/error_msg.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use crate::{keybindings::KeyCombos, opts::Config};

macro_rules! snapshot_config_parse_error {
( $( ($test_name:ident, $md_text:ident) ),* $(,)? ) => {
( $( ($test_name:ident, $config_text:ident) ),* $(,)? ) => {
$(
#[test]
fn $test_name() {
$crate::test_utils::init_test_log();

let err = ::toml::from_str::<$crate::opts::Config>($md_text).unwrap_err();
let err = $crate::opts::Config::load_from_str($config_text).unwrap_err();

::insta::with_settings!({
description => $md_text,
description => $config_text,
}, {
::insta::assert_display_snapshot!(err);
});
Expand All @@ -20,7 +22,65 @@ macro_rules! snapshot_config_parse_error {
const UNKNOWN_THEME: &str = r#"light-theme.code-highlighter = "doesnt-exist""#;
const INVALID_THEME_TY: &str = "light-theme.code-highlighter = []";

const FIX_THIS_SUCKY_ERROR_MESSAGE: &str = r#"
[keybindings]
base = [
["ToBottom", { key = " ", mod = ["Ctrl", "shift"] }],
["ZoomOut", [{ key = " ", mod = ["ctrl", "shift"] }, "Enter"]],
]
"#;

snapshot_config_parse_error!(
(unknown_theme, UNKNOWN_THEME),
(invalid_theme_ty, INVALID_THEME_TY),
// FIXME: vv
(fix_this_sucky_error_message, FIX_THIS_SUCKY_ERROR_MESSAGE),
);

fn keycombo_conflict_from_config(s: &str) -> anyhow::Result<anyhow::Error> {
let Config { keybindings, .. } = Config::load_from_str(s)?;
let mut combined_keybindings = keybindings.base.unwrap_or_default();
combined_keybindings.extend(keybindings.extra.unwrap_or_default());
let err = KeyCombos::new(combined_keybindings).unwrap_err();
Ok(err)
}

macro_rules! snapshot_keycombo_conflict_err {
( $( ($test_name:ident, $config_text:ident) ),* $(,)? ) => {
$(
#[test]
fn $test_name() {
$crate::test_utils::init_test_log();

let err = keycombo_conflict_from_config($config_text).unwrap();

::insta::with_settings!({
description => $config_text,
}, {
::insta::assert_display_snapshot!(err);
});
}
)*
}
}

const BASIC_EQUALITY: &str = r#"
[keybindings]
base = [
["ToTop", "a"],
["ZoomReset", "a"],
]
"#;

const SPECIAL_PREFIX: &str = r#"
[keybindings]
base = [
["ToBottom", { key = "Space", mod = ["Ctrl", "Shift"] }],
["ZoomOut", [{ key = "Space", mod = ["Ctrl", "Shift"] }, "Enter", 39]],
]
"#;

snapshot_keycombo_conflict_err!(
(basic_equality, BASIC_EQUALITY),
(special_prefix, SPECIAL_PREFIX),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: src/opts/tests/error_msg.rs
description: "\n[keybindings]\nbase = [\n [\"ToTop\", \"a\"],\n [\"ZoomReset\", \"a\"],\n]\n"
expression: err
---
A keycombo starts with another keycombo making it unreachable
Combo: a
Prefix: a
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: src/opts/tests/error_msg.rs
description: "\n[keybindings]\nbase = [\n [\"ToBottom\", { key = \" \", mod = [\"Ctrl\", \"shift\"] }],\n [\"ZoomOut\", [{ key = \" \", mod = [\"ctrl\", \"shift\"] }, \"Enter\"]],\n]\n"
expression: err
---
TOML parse error at line 4, column 5
|
4 | ["ToBottom", { key = " ", mod = ["Ctrl", "shift"] }],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
data did not match any variant of untagged enum ModifiedKeyOrModifiedKeys

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: src/opts/tests/error_msg.rs
description: "\n[keybindings]\nbase = [\n [\"ToBottom\", { key = \"Space\", mod = [\"Ctrl\", \"Shift\"] }],\n [\"ZoomOut\", [{ key = \"Space\", mod = [\"Ctrl\", \"Shift\"] }, \"Enter\", 39]],\n]\n"
expression: err
---
A keycombo starts with another keycombo making it unreachable
Combo: <Ctrl+Shift+Space><Enter><scan code: 39>
Prefix: <Ctrl+Shift+Space>

0 comments on commit e618e88

Please sign in to comment.