1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Phase 4d1: `:map` family intercept extracted from `ex_dispatch`.
//!
//! Handles the ~24 `:map` / `:noremap` / `:unmap` / `:mapclear` verb forms via
//! the single shared parser `keymap::parse_runtime_map_command`. Keeping them
//! in a dedicated module avoids forcing 24 individual `HostCmd` impls that would
//! either duplicate the parser call or make registration purely ceremonial.
use crate::keymap_actions::AppAction;
use super::{App, keymap};
impl App {
/// Try to handle a `:map`-family ex command.
///
/// Returns `true` if `raw` was a map command and has been applied (the
/// caller must `return` immediately). Returns `false` if
/// `parse_runtime_map_command` returned `None` — i.e. `raw` is not a map
/// verb — and the caller should continue with normal dispatch.
pub(crate) fn try_handle_runtime_map(&mut self, raw: &str) -> bool {
let Some(map_cmd) = keymap::parse_runtime_map_command(raw, self.config.editor.leader)
else {
return false;
};
match map_cmd {
keymap::RuntimeMapCommand::Add {
modes,
recursive,
lhs,
rhs,
} => {
let lhs_km: Vec<hjkl_keymap::KeyEvent> =
lhs.iter().map(|&i| keymap::input_to_km_event(i)).collect();
let rhs_km: Vec<hjkl_keymap::KeyEvent> =
rhs.iter().map(|&i| keymap::input_to_km_event(i)).collect();
let action = AppAction::Replay {
keys: rhs_km,
recursive,
};
let leader = self.config.editor.leader;
let mut any_skipped = false;
for &mode in &modes {
let Some(km_mode) = keymap::map_mode_to_km_mode(mode) else {
// Terminal mode: no keymap equivalent yet — skip silently.
any_skipped = true;
continue;
};
// Convert lhs_km to a notation string and use Keymap::add so
// the trie round-trips through Chord::parse (avoids submodule edits).
let lhs_chord = hjkl_keymap::Chord(lhs_km.clone());
let notation = lhs_chord.to_notation(leader);
let binding = hjkl_keymap::Binding {
action: action.clone(),
desc: "user runtime map".to_string(),
recursive,
condition: None,
};
// Re-parse to get canonical Chord, then add_chord.
if let Ok(chord) = hjkl_keymap::Chord::parse(¬ation, leader) {
self.app_keymap.add_chord(km_mode, chord, binding);
}
// Record for listing (de-dup by mode+lhs).
self.user_keymap_records
.retain(|r| !(r.mode == mode && r.lhs == lhs));
self.user_keymap_records.push(keymap::UserKeymapRecord {
mode,
lhs: lhs.clone(),
rhs: rhs.clone(),
recursive,
});
}
if any_skipped {
self.status_message = Some("mapping added (terminal mode skipped)".into());
} else {
self.status_message = Some("mapping added".into());
}
}
keymap::RuntimeMapCommand::Remove { modes, lhs } => {
let leader = self.config.editor.leader;
let lhs_km: Vec<hjkl_keymap::KeyEvent> =
lhs.iter().map(|&i| keymap::input_to_km_event(i)).collect();
let notation = hjkl_keymap::Chord(lhs_km).to_notation(leader);
let mut removed = false;
for &mode in &modes {
let Some(km_mode) = keymap::map_mode_to_km_mode(mode) else {
continue;
};
if let Ok(true) = self.app_keymap.remove(km_mode, ¬ation) {
removed = true;
}
self.user_keymap_records
.retain(|r| !(r.mode == mode && r.lhs == lhs));
}
self.status_message = Some(
if removed {
"mapping removed"
} else {
"E31: No such mapping"
}
.into(),
);
}
keymap::RuntimeMapCommand::Clear { modes } => {
let leader = self.config.editor.leader;
let to_remove: Vec<keymap::UserKeymapRecord> = self
.user_keymap_records
.iter()
.filter(|r| modes.contains(&r.mode))
.cloned()
.collect();
for r in &to_remove {
if let Some(km_mode) = keymap::map_mode_to_km_mode(r.mode) {
let lhs_km: Vec<hjkl_keymap::KeyEvent> = r
.lhs
.iter()
.map(|&i| keymap::input_to_km_event(i))
.collect();
let notation = hjkl_keymap::Chord(lhs_km).to_notation(leader);
let _ = self.app_keymap.remove(km_mode, ¬ation);
}
}
self.user_keymap_records
.retain(|r| !modes.contains(&r.mode));
self.status_message = Some("mappings cleared".into());
}
keymap::RuntimeMapCommand::List { modes } => {
self.info_popup = Some(keymap::format_user_map_list(
&self.user_keymap_records,
&modes,
));
}
}
true
}
}