Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
73 changes: 58 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -130,7 +159,7 @@ cargo run --bin listbox
<summary>Command</summary>

```bash
cargo run --bin query_selector
cargo run --bin query-selector
```
</details>

Expand Down Expand Up @@ -194,6 +223,20 @@ cargo run --bin yaml ${PATH_TO_YAML_FILE}

<img src="https://github.com/ynqa/ynqa/blob/master/demo/promkit/yaml.gif" width="50%" height="auto">

### CSV

<details>
<summary>Command</summary>

```bash
cargo run --bin csv ${PATH_TO_CSV_FILE}
```
</details>

[Code](./examples/csv/src/csv.rs)

<img src="https://github.com/ynqa/ynqa/blob/master/demo/promkit/csv.gif" width="50%" height="auto">

### Text

<details>
Expand Down
121 changes: 38 additions & 83 deletions promkit-widgets/README.md
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 19 additions & 0 deletions tapes/csv.csv
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions tapes/csv.tape
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions tapes/json.tape
Original file line number Diff line number Diff line change
Expand Up @@ -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
61 changes: 61 additions & 0 deletions tapes/kubernetes.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
]
}
}
}
}
]
34 changes: 34 additions & 0 deletions tapes/kubernetes.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading