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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
//! Named TUI Actions resolved by the Keymap (never hard-coded in the event loop).
//!
//! Physical KeyChord bindings live in [`crate::tui::keymap`]; this module owns
//! the stable action vocabulary used by TOML overlays, the dispatcher, and
//! [`Action::is_page_changing`] lifecycle classification.
use serde::{Deserialize, Serialize};
/// Semantic actions the terminal browser can perform.
///
/// Key bindings map to these names via [`crate::tui::keymap::TuiKeymap`].
/// Default chords in docs (e.g. `f`, `j`) are illustrative only—runtime
/// resolution always goes through the keymap.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Action {
/// `f` / `s` — enter hint mode (links + form controls); links follow in current tab.
LinkHintsFollow,
/// `F` / `S` — enter hint mode; links open in a new tab (forms still select/edit).
LinkHintsNewTab,
/// `j` / `↓` — scroll / move selection down one block.
ScrollDown,
/// `k` / `↑` — scroll / move selection up one block.
ScrollUp,
/// `h` — horizontal scroll left when content overflows.
ScrollLeft,
/// `l` — horizontal scroll right when content overflows.
ScrollRight,
/// `u` / `→` — half-page view pan up (selection unchanged).
HalfPageUp,
/// `d` / `←` — half-page view pan down (selection unchanged).
HalfPageDown,
/// `Ctrl-u` — move selection up by about half a page.
PageSelectUp,
/// `Ctrl-d` — move selection down by about half a page.
PageSelectDown,
/// `gg` — jump to document top.
GoTop,
/// `G` — jump to document bottom.
GoBottom,
/// `gi` — focus first focusable form control.
FocusFirstInput,
/// `H` / `b` — browser history back.
HistoryBack,
/// `L` — browser history forward.
HistoryForward,
/// `r` — reload active page.
Reload,
/// `w` — next tab.
NextTab,
/// `q` — previous tab.
PrevTab,
/// `x` — close current tab.
CloseTab,
/// `t` — open a new tab.
NewTab,
/// `o` — open URL entry prompt with an empty buffer (new location).
OpenUrl,
/// `O` — open URL entry prompt prefilled with the current URL for editing.
EditUrl,
/// `/` — forward search by exact semantic content.
Search,
/// `n` — repeat the last forward search.
SearchNext,
/// `N` — repeat the last search in the opposite direction.
SearchPrevious,
/// `Space` — collapse / expand selected block.
Collapse,
/// `zw` — toggle soft word-wrap of content lines (on by default).
ToggleWrap,
/// `zs` — toggle prose vs structure content projection (prose by default).
ToggleStructure,
/// `w` — toggle full-width content vs configured `content_max_width` column.
ToggleFullWidth,
/// `e` — open current page markdown in `$VISUAL` / `$EDITOR` / `vi`.
EditExternal,
/// `i` — inspect selected component metadata.
Inspect,
/// `y` — copy rendered block text (OSC 52).
CopyBlock,
/// `Y` — copy opaque semantic_ref (OSC 52).
CopyRef,
/// `Tab` — next focusable control.
TabNext,
/// `Shift-Tab` — previous focusable control.
TabPrev,
/// `Enter` — confirm URL/search/input submission.
Confirm,
/// `Escape` — leave prompt, hint, or inspect; dismiss Error → Ready.
Escape,
/// `q` / `Ctrl-c` — quit the TUI.
Quit,
}
impl Action {
/// Stable snake_case name used in TOML configuration.
pub fn name(self) -> &'static str {
match self {
Self::LinkHintsFollow => "link_hints_follow",
Self::LinkHintsNewTab => "link_hints_new_tab",
Self::ScrollDown => "scroll_down",
Self::ScrollUp => "scroll_up",
Self::ScrollLeft => "scroll_left",
Self::ScrollRight => "scroll_right",
Self::HalfPageUp => "half_page_up",
Self::HalfPageDown => "half_page_down",
Self::PageSelectUp => "page_select_up",
Self::PageSelectDown => "page_select_down",
Self::GoTop => "go_top",
Self::GoBottom => "go_bottom",
Self::FocusFirstInput => "focus_first_input",
Self::HistoryBack => "history_back",
Self::HistoryForward => "history_forward",
Self::Reload => "reload",
Self::NextTab => "next_tab",
Self::PrevTab => "prev_tab",
Self::CloseTab => "close_tab",
Self::NewTab => "new_tab",
Self::OpenUrl => "open_url",
Self::EditUrl => "edit_url",
Self::Search => "search",
Self::SearchNext => "search_next",
Self::SearchPrevious => "search_previous",
Self::Collapse => "collapse",
Self::ToggleWrap => "toggle_wrap",
Self::ToggleStructure => "toggle_structure",
Self::ToggleFullWidth => "toggle_full_width",
Self::EditExternal => "edit_external",
Self::Inspect => "inspect",
Self::CopyBlock => "copy_block",
Self::CopyRef => "copy_ref",
Self::TabNext => "tab_next",
Self::TabPrev => "tab_prev",
Self::Confirm => "confirm",
Self::Escape => "escape",
Self::Quit => "quit",
}
}
/// Parse a configuration action name.
pub fn from_name(name: &str) -> Option<Self> {
match name {
"link_hints_follow" => Some(Self::LinkHintsFollow),
"link_hints_new_tab" => Some(Self::LinkHintsNewTab),
"scroll_down" => Some(Self::ScrollDown),
"scroll_up" => Some(Self::ScrollUp),
"scroll_left" => Some(Self::ScrollLeft),
"scroll_right" => Some(Self::ScrollRight),
"half_page_up" => Some(Self::HalfPageUp),
"half_page_down" => Some(Self::HalfPageDown),
"page_select_up" => Some(Self::PageSelectUp),
"page_select_down" => Some(Self::PageSelectDown),
"go_top" => Some(Self::GoTop),
"go_bottom" => Some(Self::GoBottom),
"focus_first_input" => Some(Self::FocusFirstInput),
"history_back" => Some(Self::HistoryBack),
"history_forward" => Some(Self::HistoryForward),
"reload" => Some(Self::Reload),
"next_tab" => Some(Self::NextTab),
"prev_tab" => Some(Self::PrevTab),
"close_tab" => Some(Self::CloseTab),
"new_tab" => Some(Self::NewTab),
"open_url" => Some(Self::OpenUrl),
"edit_url" => Some(Self::EditUrl),
"search" => Some(Self::Search),
"search_next" => Some(Self::SearchNext),
"search_previous" => Some(Self::SearchPrevious),
"collapse" => Some(Self::Collapse),
"toggle_wrap" => Some(Self::ToggleWrap),
"toggle_structure" => Some(Self::ToggleStructure),
"toggle_full_width" => Some(Self::ToggleFullWidth),
"edit_external" => Some(Self::EditExternal),
"inspect" => Some(Self::Inspect),
"copy_block" => Some(Self::CopyBlock),
"copy_ref" => Some(Self::CopyRef),
"tab_next" => Some(Self::TabNext),
"tab_prev" => Some(Self::TabPrev),
"confirm" => Some(Self::Confirm),
"escape" => Some(Self::Escape),
"quit" => Some(Self::Quit),
_ => None,
}
}
/// All actions in deterministic declaration order.
pub fn all() -> &'static [Action] {
&ALL_ACTIONS
}
/// Whether performing this action may change the active page document.
pub fn is_page_changing(self) -> bool {
matches!(
self,
Self::HistoryBack
| Self::HistoryForward
| Self::Reload
| Self::Confirm
| Self::LinkHintsFollow
| Self::LinkHintsNewTab
| Self::NextTab
| Self::PrevTab
| Self::CloseTab
| Self::NewTab
)
}
}
const ALL_ACTIONS: [Action; 38] = [
Action::LinkHintsFollow,
Action::LinkHintsNewTab,
Action::ScrollDown,
Action::ScrollUp,
Action::ScrollLeft,
Action::ScrollRight,
Action::HalfPageUp,
Action::HalfPageDown,
Action::PageSelectUp,
Action::PageSelectDown,
Action::GoTop,
Action::GoBottom,
Action::FocusFirstInput,
Action::HistoryBack,
Action::HistoryForward,
Action::Reload,
Action::NextTab,
Action::PrevTab,
Action::CloseTab,
Action::NewTab,
Action::OpenUrl,
Action::EditUrl,
Action::Search,
Action::SearchNext,
Action::SearchPrevious,
Action::Collapse,
Action::ToggleWrap,
Action::ToggleStructure,
Action::ToggleFullWidth,
Action::EditExternal,
Action::Inspect,
Action::CopyBlock,
Action::CopyRef,
Action::TabNext,
Action::TabPrev,
Action::Confirm,
Action::Escape,
Action::Quit,
];