Improve UX: welcome screen, vim keybindings, command mode
- Welcome overlay shown when model has no categories, listing common commands and navigation hints to orient new users - Vim-style keybindings: - i / a → Insert mode (edit cell); Esc → Normal - x → clear cell; yy / p → yank / paste - G / gg → last / first row; 0 / $ → first / last col - Ctrl+D / Ctrl+U → half-page scroll - n / N → next / prev search match - T → tile-select mode (single key, no Ctrl needed) - ZZ → save + quit - F / C / V → toggle panels (no Ctrl needed) - ? → help (in addition to F1) - Command mode (:) for vim-style commands: :q :q! :w [path] :wq ZZ :import <file.json> :export [path] :add-cat <name> :add-item <cat> <item> :formula <cat> <Name=expr> :add-view [name] :help - Status bar now context-sensitive: shows mode-specific hint text instead of always showing the same generic shortcuts - Mode label changed: "Editing" → "INSERT" to match vim convention - Title bar shows filename in parentheses when model is backed by a file - Help widget updated with full key reference in two-column layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
737
src/ui/app.rs
737
src/ui/app.rs
File diff suppressed because it is too large
Load Diff
108
src/ui/help.rs
108
src/ui/help.rs
@ -9,67 +9,83 @@ pub struct HelpWidget;
|
||||
|
||||
impl Widget for HelpWidget {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
// Center popup
|
||||
let popup_w = 60u16.min(area.width);
|
||||
let popup_h = 30u16.min(area.height);
|
||||
let popup_w = 66u16.min(area.width);
|
||||
let popup_h = 36u16.min(area.height);
|
||||
let x = area.x + area.width.saturating_sub(popup_w) / 2;
|
||||
let y = area.y + area.height.saturating_sub(popup_h) / 2;
|
||||
let popup_area = Rect::new(x, y, popup_w, popup_h);
|
||||
|
||||
Clear.render(popup_area, buf);
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Help — Improvise ")
|
||||
.border_style(Style::default().fg(Color::Yellow));
|
||||
.title(" improvise — key reference (any key to close) ")
|
||||
.border_style(Style::default().fg(Color::Blue));
|
||||
let inner = block.inner(popup_area);
|
||||
block.render(popup_area, buf);
|
||||
|
||||
let help_text = [
|
||||
("Navigation", ""),
|
||||
(" ↑/↓/←/→ or hjkl", "Move cursor"),
|
||||
(" Enter", "Edit selected cell"),
|
||||
(" /", "Search in grid"),
|
||||
(" [ / ]", "Prev/next page item"),
|
||||
("", ""),
|
||||
("Panels", ""),
|
||||
(" Ctrl+F", "Toggle formula panel"),
|
||||
(" Ctrl+C", "Toggle category panel"),
|
||||
(" Ctrl+V", "Toggle view panel"),
|
||||
(" Tab", "Focus next open panel"),
|
||||
("", ""),
|
||||
("Tiles / Pivot", ""),
|
||||
(" Ctrl+Arrow", "Enter tile select mode"),
|
||||
(" Enter/Space", "Cycle axis (Row→Col→Page)"),
|
||||
(" r / c / p", "Set axis to Row/Col/Page"),
|
||||
("", ""),
|
||||
("File", ""),
|
||||
(" Ctrl+S", "Save model"),
|
||||
(" Ctrl+E", "Export CSV"),
|
||||
("", ""),
|
||||
("Headless / Batch", ""),
|
||||
(" --cmd '{...}'", "Run a single JSON command"),
|
||||
(" --script file", "Run commands from file"),
|
||||
("", ""),
|
||||
(" F1", "This help"),
|
||||
(" Ctrl+Q", "Quit"),
|
||||
("", ""),
|
||||
(" Any key to close", ""),
|
||||
let head = Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD);
|
||||
let key = Style::default().fg(Color::Cyan);
|
||||
let dim = Style::default().fg(Color::DarkGray);
|
||||
let norm = Style::default();
|
||||
|
||||
// (key_col, desc_col, style)
|
||||
let rows: &[(&str, &str, Style)] = &[
|
||||
("Navigation", "", head),
|
||||
(" hjkl / ↑↓←→", "Move cursor", key),
|
||||
(" gg / G", "First / last row", key),
|
||||
(" 0 / $", "First / last column", key),
|
||||
(" Ctrl+D / Ctrl+U", "Scroll ½-page down / up", key),
|
||||
(" [ / ]", "Cycle page-axis filter", key),
|
||||
("", "", norm),
|
||||
("Editing", "", head),
|
||||
(" i / a / Enter", "Enter Insert mode", key),
|
||||
(" Esc", "Return to Normal mode", key),
|
||||
(" x", "Clear cell", key),
|
||||
(" yy", "Yank (copy) cell value", key),
|
||||
(" p", "Paste yanked value", key),
|
||||
("", "", norm),
|
||||
("Search", "", head),
|
||||
(" /", "Enter search, highlight matches", key),
|
||||
(" n / N", "Next / previous match", key),
|
||||
(" Esc or Enter", "Exit search", key),
|
||||
("", "", norm),
|
||||
("Panels", "", head),
|
||||
(" F", "Toggle Formula panel (n:new d:del)", key),
|
||||
(" C", "Toggle Category panel (Space:cycle-axis)", key),
|
||||
(" V", "Toggle View panel (n:new d:del Enter:switch)", key),
|
||||
(" Tab", "Focus next open panel", key),
|
||||
("", "", norm),
|
||||
("Pivot / Tiles", "", head),
|
||||
(" T", "Tile-select mode", key),
|
||||
(" ← h / → l", "Select previous/next tile", dim),
|
||||
(" Space / Enter", "Cycle axis (Row→Col→Page)", dim),
|
||||
(" r / c / p", "Set axis to Row / Col / Page", dim),
|
||||
("", "", norm),
|
||||
("Command line ( : )", "", head),
|
||||
(" :q :q! :wq ZZ", "Quit / force-quit / save+quit", key),
|
||||
(" :w [path]", "Save (path optional)", key),
|
||||
(" :import <path.json>", "Open JSON import wizard", key),
|
||||
(" :export [path.csv]", "Export active view to CSV", key),
|
||||
(" :add-cat <name>", "Add a category", key),
|
||||
(" :add-item <cat> <item>", "Add an item to a category", key),
|
||||
(" :formula <cat> <Name=expr>", "Add a formula", key),
|
||||
(" :add-view [name]", "Create a new view", key),
|
||||
("", "", norm),
|
||||
(" ? or F1", "This help", key),
|
||||
(" Ctrl+S", "Save (same as :w)", key),
|
||||
];
|
||||
|
||||
for (i, (key, desc)) in help_text.iter().enumerate() {
|
||||
let key_col_w = 32usize;
|
||||
for (i, (k, d, style)) in rows.iter().enumerate() {
|
||||
if i >= inner.height as usize { break; }
|
||||
let y = inner.y + i as u16;
|
||||
if key.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if desc.is_empty() {
|
||||
buf.set_string(inner.x, y, key, Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD));
|
||||
if d.is_empty() {
|
||||
buf.set_string(inner.x, y, k, *style);
|
||||
} else {
|
||||
buf.set_string(inner.x, y, key, Style::default().fg(Color::Cyan));
|
||||
let desc_x = inner.x + 26;
|
||||
if desc_x < inner.x + inner.width {
|
||||
buf.set_string(desc_x, y, desc, Style::default());
|
||||
buf.set_string(inner.x, y, k, *style);
|
||||
let dx = inner.x + key_col_w as u16;
|
||||
if dx < inner.x + inner.width {
|
||||
buf.set_string(dx, y, d, norm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user