Initial implementation of Improvise TUI
Multi-dimensional data modeling terminal application with:
- Core data model: categories, items, groups, sparse cell store
- Formula system: recursive-descent parser, named formulas, WHERE clauses
- View system: Row/Column/Page axes, tile-based pivot, page slicing
- JSON import wizard (interactive TUI + headless auto-mode)
- Command layer: all mutations via typed Command enum for headless replay
- TUI: Ratatui grid, tile bar, formula/category/view panels, help overlay
- Persistence: .improv (JSON), .improv.gz (gzip), CSV export, autosave
- Static binary via x86_64-unknown-linux-musl + nix flake devShell
- Headless mode: --cmd '{"op":"..."}' and --script file.jsonl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
77
src/ui/help.rs
Normal file
77
src/ui/help.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::{Block, Borders, Clear, Widget},
|
||||
};
|
||||
|
||||
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 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));
|
||||
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", ""),
|
||||
];
|
||||
|
||||
for (i, (key, desc)) in help_text.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));
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user