+5
-47
.config/config.kdl
+5
-47
.config/config.kdl
···
1
-
scroll_offset 4
2
1
3
-
keybindings {
4
-
Explorer {
5
-
"<q>" Quit // Quit the application
6
-
"<Ctrl-d>" Quit // Another way to quit
7
-
"<Ctrl-c>" Quit // Yet another way to quit
8
-
"<Ctrl-z>" Suspend // Suspend the application
9
-
"<2>" switch-to="TodoList"
10
-
"<3>" switch-to="Inspector"
11
-
f ToggleShowFinished
12
-
x Delete
13
-
t NewTask
14
-
g NewSubGroup
15
-
"<Shift-g>" NewGroup
16
-
j MoveDown
17
-
k MoveUp
18
-
l MoveInto
19
-
h MoveOutOf
20
-
}
21
-
22
-
TodoList {
23
-
"<q>" Quit // Quit the application
24
-
"<Ctrl-d>" Quit // Another way to quit
25
-
"<Ctrl-c>" Quit // Yet another way to quit
26
-
"<Ctrl-z>" Suspend // Suspend the application
27
-
"<1>" switch-to="Explorer"
28
-
"<3>" switch-to="Inspector"
29
-
j MoveDown
30
-
k MoveUp
31
-
}
32
-
33
-
Inspector {
34
-
"<q>" Quit // Quit the application
35
-
"<Ctrl-d>" Quit // Another way to quit
36
-
"<Ctrl-c>" Quit // Yet another way to quit
37
-
"<Ctrl-z>" Suspend // Suspend the application
38
-
"<1>" switch-to="Explorer"
39
-
"<2>" switch-to="TodoList"
40
-
r RandomColor
41
-
n EditName
42
-
c EditColor
43
-
p EditPriority
44
-
u EditDue
45
-
d EditDescription
46
-
f ToggleFinishTask
47
-
t NewTask
48
-
g NewSubGroup
2
+
keymap {
3
+
Home {
4
+
<q> Quit // Quit the application
5
+
<Ctrl-c> Quit // Another way to quit
6
+
<Ctrl-z> Suspend // Suspend the application
49
7
}
50
8
}
.harper-dictionary.txt
.harper-dictionary.txt
This file has not been changed.
+110
-9
Cargo.lock
+110
-9
Cargo.lock
···
290
290
"strsim",
291
291
"terminal_size",
292
292
"unicase",
293
-
"unicode-width",
293
+
"unicode-width 0.2.2",
294
294
]
295
295
296
296
[[package]]
···
751
751
"directories",
752
752
"futures",
753
753
"human-panic",
754
+
"kdl",
754
755
"ratatui",
755
756
"serde",
756
757
"signal-hook 0.4.3",
···
1017
1018
"gix-utils",
1018
1019
"itoa",
1019
1020
"thiserror 2.0.18",
1020
-
"winnow",
1021
+
"winnow 0.7.15",
1021
1022
]
1022
1023
1023
1024
[[package]]
···
1098
1099
"smallvec",
1099
1100
"thiserror 2.0.18",
1100
1101
"unicode-bom",
1101
-
"winnow",
1102
+
"winnow 0.7.15",
1102
1103
]
1103
1104
1104
1105
[[package]]
···
1345
1346
"itoa",
1346
1347
"smallvec",
1347
1348
"thiserror 2.0.18",
1348
-
"winnow",
1349
+
"winnow 0.7.15",
1349
1350
]
1350
1351
1351
1352
[[package]]
···
1442
1443
"gix-utils",
1443
1444
"maybe-async",
1444
1445
"thiserror 2.0.18",
1445
-
"winnow",
1446
+
"winnow 0.7.15",
1446
1447
]
1447
1448
1448
1449
[[package]]
···
1474
1475
"gix-validate",
1475
1476
"memmap2",
1476
1477
"thiserror 2.0.18",
1477
-
"winnow",
1478
+
"winnow 0.7.15",
1478
1479
]
1479
1480
1480
1481
[[package]]
···
1912
1913
]
1913
1914
1914
1915
[[package]]
1916
+
name = "kdl"
1917
+
version = "6.5.0"
1918
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1919
+
checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e"
1920
+
dependencies = [
1921
+
"miette",
1922
+
"num",
1923
+
"winnow 0.6.24",
1924
+
]
1925
+
1926
+
[[package]]
1915
1927
name = "kstring"
1916
1928
version = "2.0.2"
1917
1929
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2062
2074
]
2063
2075
2064
2076
[[package]]
2077
+
name = "miette"
2078
+
version = "7.6.0"
2079
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2080
+
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
2081
+
dependencies = [
2082
+
"cfg-if",
2083
+
"unicode-width 0.1.14",
2084
+
]
2085
+
2086
+
[[package]]
2065
2087
name = "minimal-lexical"
2066
2088
version = "0.2.1"
2067
2089
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2130
2152
]
2131
2153
2132
2154
[[package]]
2155
+
name = "num"
2156
+
version = "0.4.3"
2157
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2158
+
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
2159
+
dependencies = [
2160
+
"num-bigint",
2161
+
"num-complex",
2162
+
"num-integer",
2163
+
"num-iter",
2164
+
"num-rational",
2165
+
"num-traits",
2166
+
]
2167
+
2168
+
[[package]]
2169
+
name = "num-bigint"
2170
+
version = "0.4.6"
2171
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2172
+
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
2173
+
dependencies = [
2174
+
"num-integer",
2175
+
"num-traits",
2176
+
]
2177
+
2178
+
[[package]]
2179
+
name = "num-complex"
2180
+
version = "0.4.6"
2181
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2182
+
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
2183
+
dependencies = [
2184
+
"num-traits",
2185
+
]
2186
+
2187
+
[[package]]
2133
2188
name = "num-conv"
2134
2189
version = "0.2.0"
2135
2190
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2147
2202
]
2148
2203
2149
2204
[[package]]
2205
+
name = "num-integer"
2206
+
version = "0.1.46"
2207
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2208
+
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
2209
+
dependencies = [
2210
+
"num-traits",
2211
+
]
2212
+
2213
+
[[package]]
2214
+
name = "num-iter"
2215
+
version = "0.1.45"
2216
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2217
+
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
2218
+
dependencies = [
2219
+
"autocfg",
2220
+
"num-integer",
2221
+
"num-traits",
2222
+
]
2223
+
2224
+
[[package]]
2225
+
name = "num-rational"
2226
+
version = "0.4.2"
2227
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2228
+
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
2229
+
dependencies = [
2230
+
"num-bigint",
2231
+
"num-integer",
2232
+
"num-traits",
2233
+
]
2234
+
2235
+
[[package]]
2150
2236
name = "num-traits"
2151
2237
version = "0.2.19"
2152
2238
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2477
2563
"thiserror 2.0.18",
2478
2564
"unicode-segmentation",
2479
2565
"unicode-truncate",
2480
-
"unicode-width",
2566
+
"unicode-width 0.2.2",
2481
2567
]
2482
2568
2483
2569
[[package]]
···
2528
2614
"strum 0.27.2",
2529
2615
"time",
2530
2616
"unicode-segmentation",
2531
-
"unicode-width",
2617
+
"unicode-width 0.2.2",
2532
2618
]
2533
2619
2534
2620
[[package]]
···
3293
3379
dependencies = [
3294
3380
"itertools",
3295
3381
"unicode-segmentation",
3296
-
"unicode-width",
3382
+
"unicode-width 0.2.2",
3297
3383
]
3298
3384
3299
3385
[[package]]
3300
3386
name = "unicode-width"
3387
+
version = "0.1.14"
3388
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3389
+
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
3390
+
3391
+
[[package]]
3392
+
name = "unicode-width"
3301
3393
version = "0.2.2"
3302
3394
source = "registry+https://github.com/rust-lang/crates.io-index"
3303
3395
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
···
3879
3971
3880
3972
[[package]]
3881
3973
name = "winnow"
3974
+
version = "0.6.24"
3975
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3976
+
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
3977
+
dependencies = [
3978
+
"memchr",
3979
+
]
3980
+
3981
+
[[package]]
3982
+
name = "winnow"
3882
3983
version = "0.7.15"
3883
3984
source = "registry+https://github.com/rust-lang/crates.io-index"
3884
3985
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+1
Cargo.toml
+1
Cargo.toml
···
61
61
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
62
62
tracing-error = "0.2.1"
63
63
clap = { version = "4.5.60", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] }
64
+
kdl = "6.5.0"
64
65
65
66
[build-dependencies]
66
67
anyhow = "1.0.102"
build.rs
build.rs
This file has not been changed.
flake.lock
flake.lock
This file has not been changed.
flake.nix
flake.nix
This file has not been changed.
justfile
justfile
This file has not been changed.
+29
-17
src/app.rs
+29
-17
src/app.rs
···
2
2
use crossterm::event::KeyEvent;
3
3
use ratatui::layout::Rect;
4
4
use serde::{Deserialize, Serialize};
5
+
use strum::{Display, EnumIter};
5
6
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
6
-
use tracing::{debug, info};
7
+
use tracing::debug;
7
8
8
9
use crate::{
9
10
components::Component,
···
29
30
/// The different regions of the application that the user can
30
31
/// be interacting with. Think of these kind of like the highest class of
31
32
/// components.
32
-
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33
+
#[derive(
34
+
Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display,
35
+
)]
33
36
pub enum Region {
34
37
#[default]
35
38
Home,
···
75
78
76
79
loop {
77
80
self.handle_events(&mut tui).await?;
78
-
79
-
self.handle_signals(&mut tui).await?;
81
+
self.handle_signals(&mut tui)?;
80
82
if self.should_suspend {
81
83
tui.suspend()?;
82
84
···
101
103
return Ok(());
102
104
};
103
105
106
+
debug!("received event: {event:?}");
107
+
104
108
let signal_tx = self.signal_tx.clone();
105
109
106
110
match event {
···
122
126
Ok(())
123
127
}
124
128
125
-
// We are okay with this because we know that this is the function signature,
126
-
// we just haven't implemented the keyboard parsing logic just yet, revisit
127
-
// this later.
128
-
//
129
-
// DO NOT LET THIS MERGE INTO MAIN WITH THIS CLIPPY IGNORES
130
-
#[allow(clippy::needless_pass_by_ref_mut, clippy::unnecessary_wraps)]
131
129
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
132
-
let _signal_tx = self.signal_tx.clone();
130
+
debug!("key received: {key:#?}");
131
+
132
+
let signal_tx = self.signal_tx.clone();
133
+
134
+
let Some(region_keymap) = self.config.keymap.get(&self.region) else {
135
+
return Ok(());
136
+
};
133
137
134
-
info!("key received: {key:#?}");
138
+
if let Some(signal) = region_keymap.get(&vec![key]) {
139
+
signal_tx.send(signal.clone())?;
140
+
} else {
141
+
self.last_tick_key_events.push(key);
142
+
if let Some(signal) = region_keymap.get(&self.last_tick_key_events) {
143
+
debug!("Got signal: {signal:?}");
144
+
signal_tx.send(signal.clone())?;
145
+
}
146
+
}
135
147
136
148
Ok(())
137
149
}
138
150
139
-
async fn handle_signals(&mut self, tui: &mut Tui) -> Result<()> {
140
-
while let Some(signal) = self.signal_rx.recv().await {
141
-
if signal != Signal::Tick && signal != Signal::Render {
142
-
debug!("App: handling signal: {signal:?}");
143
-
}
151
+
fn handle_signals(&mut self, tui: &mut Tui) -> Result<()> {
152
+
while let Ok(signal) = self.signal_rx.try_recv() {
153
+
// if signal != Signal::Tick && signal != Signal::Render {
154
+
debug!("App: handling signal: {signal:?}");
155
+
// }
144
156
145
157
match signal {
146
158
Signal::Tick => {
src/cli.rs
src/cli.rs
This file has not been changed.
src/components/mod.rs
src/components/mod.rs
This file has not been changed.
+22
-6
src/config.rs
+22
-6
src/config.rs
···
1
1
use directories::ProjectDirs;
2
+
use kdl::KdlDocument;
2
3
use serde::Deserialize;
3
4
use std::{env, path::PathBuf, sync::LazyLock};
4
5
6
+
use crate::keymap::KeyMap;
7
+
5
8
/// Project Name: Filaments
6
9
pub static PROJECT_NAME: LazyLock<String> =
7
10
LazyLock::new(|| env!("CARGO_CRATE_NAME").to_uppercase());
···
20
23
.map(PathBuf::from)
21
24
});
22
25
26
+
const DEFAULT_CONFIG: &str = include_str!("../.config/config.kdl");
27
+
23
28
/// The App Config and Data locations.
24
29
#[derive(Clone, Debug, Deserialize, Default)]
25
30
#[expect(dead_code)]
26
-
pub struct AppDirs {
31
+
pub struct AppConfig {
27
32
#[serde(default)]
28
33
pub data_dir: PathBuf,
29
34
#[serde(default)]
···
34
39
#[expect(dead_code)]
35
40
#[derive(Debug, Clone)]
36
41
pub struct Config {
37
-
pub app_dirs: AppDirs, // pub data_dir: PathBuf,
38
-
// pub keybindings: KeyBindings,
39
-
40
-
// pub styles: Styles,
42
+
pub app_config: AppConfig,
43
+
pub keymap: KeyMap,
44
+
// pub styles: Styles,
41
45
}
42
46
43
47
impl Config {
44
48
pub fn new() -> Self {
49
+
let default_config: KdlDocument = DEFAULT_CONFIG
50
+
.parse()
51
+
.expect("Default config should always be a valid KDL document.");
52
+
53
+
let keymap_node = default_config
54
+
.get("keymap")
55
+
.expect("Config::new Keymap must exist in default config.");
56
+
57
+
let keymap =
58
+
KeyMap::try_from(keymap_node).expect("default config should always be a valid keymap");
59
+
45
60
Self {
46
-
app_dirs: AppDirs {
61
+
app_config: AppConfig {
47
62
data_dir: get_data_dir(),
48
63
config_dir: get_config_dir(),
49
64
},
65
+
keymap,
50
66
}
51
67
}
52
68
}
src/errors.rs
src/errors.rs
This file has not been changed.
src/logging.rs
src/logging.rs
This file has not been changed.
+1
-1
src/main.rs
+1
-1
src/main.rs
+20
src/signal.rs
+20
src/signal.rs
···
1
+
use std::str::FromStr;
2
+
3
+
use color_eyre::eyre::eyre;
1
4
use strum::Display;
2
5
3
6
use serde::{Deserialize, Serialize};
···
15
18
Error(String),
16
19
Help,
17
20
}
21
+
22
+
impl FromStr for Signal {
23
+
type Err = color_eyre::Report;
24
+
25
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
26
+
Ok(match s.to_lowercase().as_str() {
27
+
"suspend" => Self::Suspend,
28
+
"resume" => Self::Resume,
29
+
"quit" => Self::Quit,
30
+
_ => {
31
+
return Err(eyre!(format!(
32
+
"Attempt to construct a non-user Signal from str: {s}"
33
+
)));
34
+
}
35
+
})
36
+
}
37
+
}
+2
-3
src/tui.rs
+2
-3
src/tui.rs
···
163
163
Some(Ok(event)) => match event {
164
164
// we only care about press down events,
165
165
// not doing anything related to up / down keypresses
166
-
CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => Event::Key(key),
166
+
CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => Event::Key(key),
167
167
CrosstermEvent::Key(_) => continue,
168
-
169
168
CrosstermEvent::Mouse(mouse) => Event::Mouse(mouse),
170
169
CrosstermEvent::Resize(x, y) => Event::Resize(x, y),
171
170
CrosstermEvent::FocusLost => {Event::FocusLost },
···
177
176
None => break,
178
177
}
179
178
};
179
+
180
180
if event_tx.send(event).is_err() {
181
-
// no more receiver
182
181
break;
183
182
}
184
183
}
+50
.config/config_old.kdl
+50
.config/config_old.kdl
···
1
+
scroll_offset 4
2
+
3
+
keybindings {
4
+
Explorer {
5
+
"<q>" Quit // Quit the application
6
+
"<Ctrl-d>" Quit // Another way to quit
7
+
"<Ctrl-c>" Quit // Yet another way to quit
8
+
"<Ctrl-z>" Suspend // Suspend the application
9
+
"<2>" switch-to="TodoList"
10
+
"<3>" switch-to="Inspector"
11
+
f ToggleShowFinished
12
+
x Delete
13
+
t NewTask
14
+
g NewSubGroup
15
+
"<Shift-g>" NewGroup
16
+
j MoveDown
17
+
k MoveUp
18
+
l MoveInto
19
+
h MoveOutOf
20
+
}
21
+
22
+
TodoList {
23
+
"<q>" Quit // Quit the application
24
+
"<Ctrl-d>" Quit // Another way to quit
25
+
"<Ctrl-c>" Quit // Yet another way to quit
26
+
"<Ctrl-z>" Suspend // Suspend the application
27
+
"<1>" switch-to="Explorer"
28
+
"<3>" switch-to="Inspector"
29
+
j MoveDown
30
+
k MoveUp
31
+
}
32
+
33
+
Inspector {
34
+
"<q>" Quit // Quit the application
35
+
"<Ctrl-d>" Quit // Another way to quit
36
+
"<Ctrl-c>" Quit // Yet another way to quit
37
+
"<Ctrl-z>" Suspend // Suspend the application
38
+
"<1>" switch-to="Explorer"
39
+
"<2>" switch-to="TodoList"
40
+
r RandomColor
41
+
n EditName
42
+
c EditColor
43
+
p EditPriority
44
+
u EditDue
45
+
d EditDescription
46
+
f ToggleFinishTask
47
+
t NewTask
48
+
g NewSubGroup
49
+
}
50
+
}
+209
src/keymap.rs
+209
src/keymap.rs
···
1
+
use std::{
2
+
collections::HashMap,
3
+
ops::{Deref, DerefMut},
4
+
};
5
+
6
+
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
7
+
use kdl::KdlNode;
8
+
use strum::IntoEnumIterator;
9
+
10
+
use crate::{app::Region, signal::Signal};
11
+
12
+
#[derive(Debug, Clone)]
13
+
pub struct KeyMap(pub HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>);
14
+
15
+
impl TryFrom<&KdlNode> for KeyMap {
16
+
type Error = color_eyre::Report;
17
+
18
+
fn try_from(value: &KdlNode) -> std::result::Result<Self, Self::Error> {
19
+
let mut all_binds = HashMap::new();
20
+
21
+
for region in Region::iter() {
22
+
let mut region_binds = HashMap::new();
23
+
let Some(binds) = value
24
+
.children()
25
+
.expect("Keymap must have children.")
26
+
.get(®ion.to_string())
27
+
else {
28
+
continue;
29
+
};
30
+
31
+
// now we iter through the things children
32
+
for child in binds.iter_children() {
33
+
let key_combo_str = child.name().to_string();
34
+
let key_combo_str = key_combo_str.trim();
35
+
36
+
let signal_str = child
37
+
.entries()
38
+
.first()
39
+
.expect("A bind must map to an entry")
40
+
.to_string();
41
+
let signal_str = signal_str.trim();
42
+
43
+
let signal: Signal = signal_str.parse().expect("Must be a \"bindable\" Signal");
44
+
let key_combo = parse_key_sequence(key_combo_str).unwrap();
45
+
46
+
let _ = region_binds.insert(key_combo, signal);
47
+
}
48
+
49
+
let _ = all_binds.insert(region, region_binds);
50
+
}
51
+
52
+
Ok(Self(all_binds))
53
+
}
54
+
}
55
+
56
+
impl Deref for KeyMap {
57
+
type Target = HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>;
58
+
59
+
fn deref(&self) -> &Self::Target {
60
+
&self.0
61
+
}
62
+
}
63
+
64
+
impl DerefMut for KeyMap {
65
+
fn deref_mut(&mut self) -> &mut Self::Target {
66
+
&mut self.0
67
+
}
68
+
}
69
+
70
+
pub fn parse_key_sequence(raw: &str) -> color_eyre::Result<Vec<KeyEvent>, String> {
71
+
if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() {
72
+
return Err(format!("Unable to parse `{raw}`"));
73
+
}
74
+
let raw = if raw.contains("><") {
75
+
raw
76
+
} else {
77
+
let raw = raw.strip_prefix('<').unwrap_or(raw);
78
+
79
+
raw.strip_prefix('>').unwrap_or(raw)
80
+
};
81
+
82
+
raw.split("><")
83
+
.map(|seq| {
84
+
seq.strip_prefix('<')
85
+
.unwrap_or_else(|| seq.strip_suffix('>').map_or(seq, |s| s))
86
+
})
87
+
.map(parse_key_event)
88
+
.collect()
89
+
}
90
+
91
+
fn parse_key_event(raw: &str) -> color_eyre::Result<KeyEvent, String> {
92
+
let raw_lower = raw.to_ascii_lowercase();
93
+
let (remaining, modifiers) = extract_modifiers(&raw_lower);
94
+
parse_key_code_with_modifiers(remaining, modifiers)
95
+
}
96
+
97
+
fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
98
+
let mut modifiers = KeyModifiers::empty();
99
+
let mut current = raw;
100
+
101
+
loop {
102
+
match current {
103
+
rest if rest.starts_with("ctrl-") => {
104
+
modifiers.insert(KeyModifiers::CONTROL);
105
+
current = &rest[5..];
106
+
}
107
+
rest if rest.starts_with("alt-") => {
108
+
modifiers.insert(KeyModifiers::ALT);
109
+
current = &rest[4..];
110
+
}
111
+
rest if rest.starts_with("shift-") => {
112
+
modifiers.insert(KeyModifiers::SHIFT);
113
+
current = &rest[6..];
114
+
}
115
+
_ => break, // break out of the loop if no known prefix is detected
116
+
}
117
+
}
118
+
119
+
(current, modifiers)
120
+
}
121
+
122
+
fn parse_key_code_with_modifiers(
123
+
raw: &str,
124
+
mut modifiers: KeyModifiers,
125
+
) -> color_eyre::Result<KeyEvent, String> {
126
+
let c = match raw {
127
+
"esc" => KeyCode::Esc,
128
+
"enter" => KeyCode::Enter,
129
+
"left" => KeyCode::Left,
130
+
"right" => KeyCode::Right,
131
+
"up" => KeyCode::Up,
132
+
"down" => KeyCode::Down,
133
+
"home" => KeyCode::Home,
134
+
"end" => KeyCode::End,
135
+
"pageup" => KeyCode::PageUp,
136
+
"pagedown" => KeyCode::PageDown,
137
+
"backtab" => {
138
+
modifiers.insert(KeyModifiers::SHIFT);
139
+
KeyCode::BackTab
140
+
}
141
+
"backspace" => KeyCode::Backspace,
142
+
"delete" => KeyCode::Delete,
143
+
"insert" => KeyCode::Insert,
144
+
"f1" => KeyCode::F(1),
145
+
"f2" => KeyCode::F(2),
146
+
"f3" => KeyCode::F(3),
147
+
"f4" => KeyCode::F(4),
148
+
"f5" => KeyCode::F(5),
149
+
"f6" => KeyCode::F(6),
150
+
"f7" => KeyCode::F(7),
151
+
"f8" => KeyCode::F(8),
152
+
"f9" => KeyCode::F(9),
153
+
"f10" => KeyCode::F(10),
154
+
"f11" => KeyCode::F(11),
155
+
"f12" => KeyCode::F(12),
156
+
"space" => KeyCode::Char(' '),
157
+
"hyphen" | "minuc" => KeyCode::Char('-'),
158
+
"tab" => KeyCode::Tab,
159
+
c if c.len() == 1 => {
160
+
let mut c = c.chars().next().unwrap();
161
+
if modifiers.contains(KeyModifiers::SHIFT) {
162
+
c = c.to_ascii_uppercase();
163
+
}
164
+
KeyCode::Char(c)
165
+
}
166
+
_ => return Err(format!("Unable to parse {raw}")),
167
+
};
168
+
Ok(KeyEvent::new(c, modifiers))
169
+
}
170
+
171
+
#[cfg(test)]
172
+
mod test {
173
+
use crossterm::event::{KeyEvent, KeyModifiers};
174
+
use kdl::KdlNode;
175
+
176
+
use crate::{keymap::KeyMap, signal::Signal};
177
+
178
+
#[test]
179
+
fn test_quit_and_suspend_in_home_region() {
180
+
let keymap_str = "
181
+
keymap {
182
+
Home {
183
+
q Quit
184
+
<Ctrl-C> Quit
185
+
}
186
+
}
187
+
";
188
+
189
+
let kdl: &KdlNode = &keymap_str
190
+
.parse()
191
+
.expect("Keymap_str should be a valid KDL document");
192
+
193
+
let keymap: KeyMap = kdl.try_into().expect("Must be a valid keymap");
194
+
195
+
let map = keymap
196
+
.get(&crate::app::Region::Home)
197
+
.expect("Home region must exist in keymap");
198
+
199
+
let signal = map
200
+
.get(&vec![KeyEvent::new_with_kind(
201
+
crossterm::event::KeyCode::Char('q'),
202
+
KeyModifiers::empty(),
203
+
crossterm::event::KeyEventKind::Press,
204
+
)])
205
+
.expect("Must resolve to a signal");
206
+
207
+
assert_eq!(*signal, Signal::Quit);
208
+
}
209
+
}
History
3 rounds
2 comments
suri.codes
submitted
#2
9 commits
expand
collapse
feat: panic_handler
feat/tui: create TUI abstraction
feat/tui: signals + bare config
feat/tui: Component trait
feat/tui: init logging
feat/tui: init app
feat: add build.rs using vergen_gix for metadata
feat/tui: init cli & running tui
feat/config: Keymap + kdl config
2/2 success
expand
collapse
expand 0 comments
pull request successfully merged
suri.codes
submitted
#1
9 commits
expand
collapse
feat: panic_handler
feat/tui: create TUI abstraction
feat/tui: signals + bare config
feat/tui: Component trait
feat/tui: init logging
feat/tui: init app
feat: add build.rs using vergen_gix for metadata
feat/tui: init cli & running tui
feat/config: Keymap + kdl config
2/2 success
expand
collapse
expand 1 comment
suri.codes
submitted
#0
8 commits
expand
collapse
feat: panic_handler
feat/tui: create TUI abstraction
feat/tui: signals + bare config
feat/tui: Component trait
feat/tui: init logging
feat/tui: init app
feat: add build.rs using vergen_gix for metadata
feat/tui: init cli & running tui
expand 1 comment
actually have parsing of the config.kdl and interpret it as keybinds. remove all the unused bindings
app should not emit handling signal on debug