feat: emphasize keybinding overview headings
This commit is contained in:
+85
-17
@@ -1,6 +1,10 @@
|
||||
use std::{fmt::Write as _, io};
|
||||
|
||||
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use ratatui::{
|
||||
style::Stylize,
|
||||
text::{Line, Span, Text},
|
||||
};
|
||||
use toml_edit::{Array, Item};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -419,25 +423,28 @@ impl Bindings {
|
||||
.map_or("unbound", String::as_str)
|
||||
}
|
||||
|
||||
pub fn help_text(&self) -> String {
|
||||
let mut text = String::from("Keyboard bindings\n\n");
|
||||
pub fn help_text(&self) -> Text<'static> {
|
||||
let mut text = Text::from(Line::from(Span::raw("Keyboard bindings").bold()));
|
||||
text.push_line("");
|
||||
for definition in DEFINITIONS {
|
||||
let aliases = &self.labels[definition.action as usize];
|
||||
let bindings = if aliases.is_empty() {
|
||||
"unbound".to_owned()
|
||||
let mut body = String::from(": ");
|
||||
if aliases.is_empty() {
|
||||
body.push_str("unbound");
|
||||
} else {
|
||||
aliases.join(", ")
|
||||
};
|
||||
let _ = write!(
|
||||
text,
|
||||
"{}: {} — {} [",
|
||||
definition.name, bindings, definition.description
|
||||
);
|
||||
for (index, alias) in aliases.iter().enumerate() {
|
||||
if index != 0 {
|
||||
body.push_str(", ");
|
||||
}
|
||||
body.push_str(alias);
|
||||
}
|
||||
}
|
||||
let _ = write!(body, " — {} [", definition.description);
|
||||
for (index, context) in definition.contexts.iter().enumerate() {
|
||||
if index != 0 {
|
||||
text.push_str(", ");
|
||||
body.push_str(", ");
|
||||
}
|
||||
text.push_str(match context {
|
||||
body.push_str(match context {
|
||||
Context::Normal => "video list",
|
||||
Context::Info => "video overview",
|
||||
Context::Description => "video description",
|
||||
@@ -449,11 +456,18 @@ impl Bindings {
|
||||
Context::Help => "help",
|
||||
});
|
||||
}
|
||||
text.push_str("]\n");
|
||||
body.push(']');
|
||||
text.push_line(Line::from(vec![
|
||||
Span::raw(definition.name).bold(),
|
||||
Span::raw(body),
|
||||
]));
|
||||
}
|
||||
text.push_str("\nText input inserts literal characters; normal-mode commands do not run there.\n\
|
||||
Shared overview/description bindings cycle overview → description → overview; distinct bindings open either view directly.\n\
|
||||
Preview close takes priority over history back. Description scrolling takes priority over video selection on shared bindings.\n");
|
||||
text.push_line("");
|
||||
text.push_line(
|
||||
"Text input inserts literal characters; normal-mode commands do not run there.",
|
||||
);
|
||||
text.push_line("Shared overview/description bindings cycle overview → description → overview; distinct bindings open either view directly.");
|
||||
text.push_line("Preview close takes priority over history back. Description scrolling takes priority over video selection on shared bindings.");
|
||||
text
|
||||
}
|
||||
}
|
||||
@@ -519,6 +533,60 @@ mod tests {
|
||||
bindings.resolve(context, KeyEvent::new(code, modifiers))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_headings_are_bold_but_bindings_and_descriptions_are_not() {
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Modifier,
|
||||
widgets::{Paragraph, Widget, Wrap},
|
||||
};
|
||||
let bindings = configured("[keys]\napp.interrupt = 'f24'\n").unwrap();
|
||||
for width in [21, 64] {
|
||||
let area = Rect::new(0, 0, width, 8);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
Paragraph::new(bindings.help_text())
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(area, &mut buffer);
|
||||
assert!(buffer[(0, 0)].modifier.contains(Modifier::BOLD));
|
||||
for x in 0.."app.interrupt".len() as u16 {
|
||||
assert!(buffer[(x, 2)].modifier.contains(Modifier::BOLD));
|
||||
}
|
||||
assert!(
|
||||
!buffer[("app.interrupt".len() as u16, 2)]
|
||||
.modifier
|
||||
.contains(Modifier::BOLD)
|
||||
);
|
||||
let mut found_binding = false;
|
||||
for y in 2..area.height {
|
||||
let row = (0..width)
|
||||
.map(|x| buffer[(x, y)].symbol())
|
||||
.collect::<String>();
|
||||
if y > 2 && row.starts_with("app.quit") {
|
||||
break;
|
||||
}
|
||||
let body_start = if y == 2 {
|
||||
"app.interrupt".len() as u16
|
||||
} else {
|
||||
0
|
||||
};
|
||||
for x in body_start..width {
|
||||
assert!(!buffer[(x, y)].modifier.contains(Modifier::BOLD));
|
||||
}
|
||||
if let Some(x) = row.find("F24") {
|
||||
found_binding = true;
|
||||
for x in x as u16..x as u16 + 3 {
|
||||
assert!(!buffer[(x, y)].modifier.contains(Modifier::BOLD));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
found_binding,
|
||||
"configured binding must remain visible after wrapping"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_cycle_and_direct_preview_bindings() {
|
||||
let defaults = Bindings::default();
|
||||
|
||||
Reference in New Issue
Block a user