diff --git a/.gitignore b/.gitignore index 42e4ac07..6cf2fe6d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,7 @@ Cargo.lock # Ignore GIF files in the tapes directory tapes/*.gif -# Ignore test artifacts emitted by zsh-render-parity integration tests -zsh-render-parity/.artifacts/ +# Keep tape fixtures under version control +!tapes/*.csv +!tapes/*.json +!tapes/*.yaml diff --git a/README.md b/README.md index f35e1551..0d382705 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,29 @@ promkit = { version = "0.14.0", features = ["runtime", "texteditor"] } ## Features -- Cross-platform support for both UNIX and Windows utilizing [crossterm](https://github.com/crossterm-rs/crossterm) -- Modularized architecture - - [promkit-core](./promkit-core/) - - Core functionality for terminal rendering and keyed grapheme chunk management - - [promkit-widgets](./promkit-widgets/) - - Various UI components (text, listbox, tree, etc.) - - [promkit](./promkit/) - - Optional prompt runtime, capabilities, and widget facade - - [promkit-derive](./promkit-derive/) - - A Derive macro that simplifies interactive form input -- Composable examples for readline, selection, structured data, forms, and - asynchronous workflows +- **Application-owned composition** — Implement the + [`Prompt`](./promkit/src/runtime.rs) lifecycle and combine only the widget + states an application needs. Key bindings, focus, validation, background + work, and other event policy remain in the application instead of + framework-owned presets. +- **Optional runtime and terminal lifecycle** — Use the asynchronous prompt + runtime when useful, and independently manage raw mode, the alternate screen, + cursor visibility, and mouse capture with + [`TerminalSession`](./promkit/src/terminal_session.rs). +- **Viewport-aware rendering** — [`promkit-core`](./promkit-core/) handles + wrapping or truncation, vertical pane allocation, cursor scrolling, clipping, + resizing, and screen-to-widget hit testing. +- **Reusable widget states** — [`promkit-widgets`](./promkit-widgets/) provides + text editing and display, list and checkbox selection, prefix search, + spinners and status output, trees, JSON and YAML documents, and CSV tables. +- **Efficient large-content projection** — JSON, YAML, and table widgets can + project only the visible terminal viewport instead of rebuilding all content + on every redraw. +- **Feature-gated modules** — No Cargo features are enabled by default. Choose + the runtime, terminal session, capabilities, and individual widgets needed by + the application. +- **Cross-platform terminal support** — UNIX and Windows are supported through + [crossterm](https://github.com/crossterm-rs/crossterm). ## Concept @@ -46,8 +57,26 @@ See [here](./Concept.md). terminal applications. The examples own their event policies and demonstrate how applications can compose the widgets they need. -Show you commands, code, and actual demo screens for examples -that can be executed immediately below. +### Table of contents + +| Example | Description | +| --- | --- | +| [Readline](#readline) | Single-line editing with history and completion | +| [Confirm](#confirm) | Validated yes-or-no input | +| [Password](#password) | Masked input with validation | +| [Form](#form) | Multiple editable fields in one prompt | +| [Listbox](#listbox) | Keyboard-driven single selection | +| [QuerySelector](#queryselector) | Prefix search over selectable candidates | +| [Checkbox](#checkbox) | Keyboard-driven multiple selection | +| [Tree](#tree) | Navigation and folding for hierarchical data | +| [JSON](#json) | Line-numbered navigation and folding for JSON | +| [YAML](#yaml) | Line-numbered navigation and folding for YAML | +| [CSV](#csv) | Vertical and horizontal navigation for tabular data | +| [Text](#text) | Scrollable styled text | +| [Async Task with Spinner](#async-task-with-spinner) | Background work with live input and progress feedback | +| [Multiline REPL](#multiline-repl) | Multiline editing in an interactive loop | + +Each section includes a command, source link, and recorded demo. ### Readline @@ -130,7 +159,7 @@ cargo run --bin listbox Command ```bash -cargo run --bin query_selector +cargo run --bin query-selector ``` @@ -194,6 +223,20 @@ cargo run --bin yaml ${PATH_TO_YAML_FILE} +### CSV + +
+Command + +```bash +cargo run --bin csv ${PATH_TO_CSV_FILE} +``` +
+ +[Code](./examples/csv/src/csv.rs) + + + ### Text
diff --git a/promkit-widgets/README.md b/promkit-widgets/README.md index 6c5c526d..63f9377d 100644 --- a/promkit-widgets/README.md +++ b/promkit-widgets/README.md @@ -1,101 +1,56 @@ # promkit-widgets -Reusable widget states for [promkit](https://github.com/ynqa/promkit). +Reusable widget states for building interactive terminal applications with +[promkit](https://github.com/ynqa/promkit). -Each state implements `promkit_core::Widget` and projects its current state into -styled graphemes and layout hints. Widgets do not own event loops or key -bindings: event handling belongs to application `Prompt` implementations, -while terminal layout and drawing belong to `promkit-core`. -See [Concept.md](../Concept.md) for the full responsibility boundaries. +`promkit-widgets` provides the state and view projection for common UI +components. Each widget implements `promkit_core::Widget` and turns its current +state into styled graphemes and layout hints for `promkit-core` to render. -## Features +## Getting started -No widget is enabled by default. - -| Feature | Widget or capability | -| --- | --- | -| `checkbox` | Checkbox selection; enables `listbox` | -| `json` | Navigable JSON document | -| `yaml` | Navigable YAML document | -| `listbox` | List selection | -| `prefixsearch` | Prefix-matched candidate selection backed by a radix trie | -| `spinner` | Asynchronous spinner; enables Tokio | -| `status` | Status display; enables `text` | -| `text` | Styled text | -| `texteditor` | Editable text with history | -| `tree` | Navigable structured tree | -| `serde` | Serde support for widget configuration | -| `all` | All features above | - -Enable only the widgets an application uses: +Widgets are opt-in Cargo features; none are enabled by default. Applications +using the `promkit` runtime can enable them through the main crate: ```toml [dependencies] -promkit-widgets = { version = "0.7", features = ["json", "yaml"] } -``` - -The crate re-exports `promkit-core` as `promkit_widgets::core`. JSON and YAML -states provide viewport-bounded projection so callers do not need to materialize -every visible row of a large document on each cursor movement. - -## Structured document loading - -JSON and YAML documents can be built directly from strings or readers without -first materializing a `serde_json::Value` or `serde_yaml::Value` tree: - -```rust -use std::{fs::File, io::BufReader}; - -use promkit_widgets::{json, yaml}; - -let json_document = json::Document::from_str(r#"{"name":"alice"}"#).unwrap(); -let yaml_file = File::open("input.yaml").unwrap(); -let yaml_document = yaml::Document::from_reader(BufReader::new(yaml_file)).unwrap(); +promkit = { version = "0.14.0", features = ["runtime", "texteditor"] } ``` -`Document::new` remains available when an application already has deserialized -Serde values. +The widget states can also be used directly: -## Structured benchmark - -The Criterion benchmark covers file reading, deserialization, document -construction, cursor movement, and viewport projection for the bundled JSON and -YAML fixtures: - -```bash -cargo bench -p promkit-widgets --bench structured --features json,yaml +```toml +[dependencies] +promkit-widgets = { version = "0.7", features = ["json", "yaml"] } ``` -Criterion compares a run with its previous local result. Named baselines can be -saved and compared across revisions with `--save-baseline NAME` and -`--baseline NAME`. The benchmark currently reports regressions but does not -enforce a CI failure threshold. - -Override either fixture when needed: - -```bash -PROMKIT_STRUCTURED_JSON=/path/to/input.json \ -PROMKIT_STRUCTURED_YAML=/path/to/input.yaml \ -cargo bench -p promkit-widgets --bench structured --features json,yaml -``` +`promkit` re-exports this crate as `promkit::widgets`, while +`promkit-widgets` re-exports `promkit-core` as `promkit_widgets::core`. -This benchmark exercises the `promkit-widgets` projection layer. It does not -measure `promkit-core::Renderer`, terminal layout, or terminal I/O. +## Widgets -## Structured line numbers +| Feature | Widget or capability | +| --- | --- | +| `checkbox` | Multiple-choice selection | +| `listbox` | List selection | +| `prefixsearch` | Prefix-matched candidate selection | +| `json` | Navigable JSON documents | +| `yaml` | Navigable YAML documents | +| `tree` | Navigable tree structures | +| `table` | Tabular CSV data | +| `text` | Styled text | +| `texteditor` | Editable text with history | +| `spinner` | Asynchronous progress display | +| `status` | Status display | +| `serde` | Serde support for widget configuration | +| `all` | All features above | -The `json`, `yaml`, and `tree` widgets can display stable, one-based line -numbers by enabling `show_line_numbers` in their `Config`. Numbers refer to the -fully expanded structure, so collapsing a node leaves gaps for its hidden rows. +## Responsibilities -Applications configure line numbers directly on widget state: +Widgets manage state and project it into renderable content. They intentionally +do not own event loops or key bindings: application `Prompt` implementations +define input and focus behavior, and `promkit-core` handles terminal layout and +drawing. -```rust -let state = json::State { - document, - config: json::Config { - show_line_numbers: true, - ..Default::default() - }, -}; -``` +See [Concept.md](../Concept.md) for the architecture and the repository +[examples](../examples/) for complete compositions. diff --git a/tapes/csv.csv b/tapes/csv.csv new file mode 100644 index 00000000..4ba492c4 --- /dev/null +++ b/tapes/csv.csv @@ -0,0 +1,19 @@ +id,task,area,owner,status,progress,updated,notes +001,Define widget API,Core,Alice,Done,100%,2026-07-03,Keep state separate from event policy +002,Add table projection,Widgets,Bob,Done,100%,2026-07-05,Render only the visible viewport +003,Parse CSV input,Widgets,Carol,Done,100%,2026-07-06,Support files and standard input +004,Style table header,Examples,Diego,Done,100%,2026-07-08,Use a distinct header style +005,Add row navigation,Examples,Erin,Done,100%,2026-07-09,Handle up and down arrow keys +006,Add column scrolling,Examples,Farah,Done,100%,2026-07-10,Handle left and right arrow keys +007,Support mouse wheel,Examples,Gabriel,Done,100%,2026-07-11,Map wheel events to table movement +008,Measure parsing speed,Benchmarks,Hana,In progress,75%,2026-07-14,Compare representative CSV fixtures +009,Measure projection speed,Benchmarks,Ivan,In progress,60%,2026-07-15,Track visible row rendering cost +010,Test quoted fields,Tests,Julia,Done,100%,2026-07-16,Include commas and multiline values +011,Test narrow terminals,Tests,Kai,In review,90%,2026-07-18,Verify horizontal clipping behavior +012,Test empty input,Tests,Lina,Done,100%,2026-07-19,Keep empty documents safe to render +013,Write CSV example,Examples,Min,Done,100%,2026-07-20,Show file and standard input usage +014,Record demo tape,Docs,Nora,In progress,50%,2026-07-22,Demonstrate both scroll directions +015,Review keyboard help,Docs,Omar,Planned,10%,2026-07-24,Document Enter and Ctrl-C behavior +016,Build release artifacts,Release,Priya,Planned,0%,2026-07-25,Publish all workspace crates together +017,Run compatibility checks,Release,Quinn,Planned,0%,2026-07-26,Test supported terminal environments +018,Publish release notes,Release,Rui,Planned,0%,2026-07-28,Summarize the user-facing changes diff --git a/tapes/csv.tape b/tapes/csv.tape new file mode 100644 index 00000000..b051aa1a --- /dev/null +++ b/tapes/csv.tape @@ -0,0 +1,16 @@ +Output tapes/csv.gif + +Require cargo + +Set Shell "bash" +Set Theme "Dracula" +Set FontSize 32 +Set Width 1200 +Set Height 600 + +Type@50ms "cargo run -q --bin csv tapes/csv.csv" Enter Sleep 2s +Down@80ms 14 Sleep 1s +Right@50ms 36 Sleep 2s +Up@200ms 4 Sleep 1s +Left@50ms 12 Sleep 1s +Enter Sleep 2s diff --git a/tapes/json.tape b/tapes/json.tape index 85d16636..5d03de1e 100644 --- a/tapes/json.tape +++ b/tapes/json.tape @@ -8,9 +8,10 @@ Set FontSize 32 Set Width 1200 Set Height 600 -Type@50ms "cargo run -q --bin json test.json" Enter Sleep 2s -Down@300ms 2 Sleep 1s +Type@50ms "cargo run -q --bin json tapes/kubernetes.json" Enter Sleep 2s +Down@300ms 4 Sleep 1s Space Sleep 1s Down@300ms 1 Sleep 1s Space Sleep 1s -Up@300ms 2 Sleep 1s +Down@200ms 2 Sleep 1s +Enter Sleep 2s diff --git a/tapes/kubernetes.json b/tapes/kubernetes.json new file mode 100644 index 00000000..fb62de2a --- /dev/null +++ b/tapes/kubernetes.json @@ -0,0 +1,61 @@ +[ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "my-nginx-svc", + "labels": { + "app": "nginx" + } + }, + "spec": { + "type": "LoadBalancer", + "ports": [ + { + "port": 80 + } + ], + "selector": { + "app": "nginx" + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "my-nginx", + "labels": { + "app": "nginx" + } + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } + } +] diff --git a/tapes/kubernetes.yaml b/tapes/kubernetes.yaml new file mode 100644 index 00000000..38f8a049 --- /dev/null +++ b/tapes/kubernetes.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Service +metadata: + name: my-nginx-svc + labels: + app: nginx +spec: + type: LoadBalancer + ports: + - port: 80 + selector: + app: nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-nginx + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/tapes/yaml.tape b/tapes/yaml.tape index b444afa5..19f85894 100644 --- a/tapes/yaml.tape +++ b/tapes/yaml.tape @@ -8,9 +8,10 @@ Set FontSize 32 Set Width 1200 Set Height 600 -Type@50ms "cargo run -q --bin yaml small.yaml" Enter Sleep 2s +Type@50ms "cargo run -q --bin yaml tapes/kubernetes.yaml" Enter Sleep 2s Down@300ms 2 Sleep 1s Space Sleep 1s Down@300ms 1 Sleep 1s Space Sleep 1s -Up@300ms 2 Sleep 1s +Down@200ms 2 Sleep 1s +Enter Sleep 2s