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
96 changes: 47 additions & 49 deletions DEMO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
This document is both a demo and an executable smoke test.

Open it in the TUI with `upmd DEMO.md` and press Enter on any block to run it.
Run with `upmd` (no args) to auto-open `up.md`, or use the file picker to
select a different file. Headless execution: `upmd --all --yes DEMO.md`.
Run one named block without the TUI with
`upmd --cli --block stream --yes DEMO.md`.

## Getting started

Expand All @@ -14,23 +14,26 @@ A regular shell command. Select it and press Enter.
ls
```

An interactive prompt. Press `i` to focus the running block's inline terminal, type your name, then press Enter.
## Markdown inline styles

```sh
read -p "Your name: " ME
echo "Hi, $ME!"
```
Plain text, **bold**, *italic*, ~~strikethrough~~, `inline code`,
[a link](https://github.com/rezigned/upmd "upmd repository"), and
![the upmd logo](.github/pages/upmd-logo.png).

Full terminal applications work inside the preview. Press `i` to interact, `o` for the full output view, `ctrl-o` to leave.
Styles can be nested: ***bold italic***, **[a bold link](https://github.com/rezigned/upmd)**,
and ~~*struck italic*~~.

```sh
nvim
```
### A heading with **bold**, *italic*, and `code`

- A list item with **bold**, *italic*, ~~strikethrough~~, and `inline code`.
- A linked image: [![upmd logo](.github/pages/upmd-square.svg)](https://upmd.rezigned.com/).

> [!TIP]
> Run `upmd` with no arguments to auto-open `up.md`, or browse the file picker.
| Context | Styled example |
|---|---|
| Emphasis | **bold**, *italic*, and ***both*** |
| Other | ~~strikethrough~~, `inline code`, and [link](https://upmd.rezigned.com/) |

## 1. Streamed output
## Streamed output

Output appears inline while the block runs. The preview follows new rows only when they extend below the viewport.

Expand All @@ -43,7 +46,7 @@ done
printf '\nReady.\n'
```

## 2. ANSI colors and Unicode
## ANSI colors and Unicode

Every block runs in a real PTY, so ANSI escape codes and Unicode render correctly.

Expand All @@ -53,9 +56,9 @@ printf 'Unicode: café ภาษาไทย 🚀 ★\n'
printf 'Bold: \033[1mimportant\033[0m Dim: \033[2mquiet\033[0m Underline: \033[4mlink\033[0m\n'
```

## 3. Shell state persistence
## Shell state persistence

Shell runners automatically capture exported variables and the final working directory.
Shell runners carry exported variables and the final working directory into later blocks.

```bash [name:set-state]
export UPMD_DEMO_MESSAGE="state carried from the previous block"
Expand All @@ -65,7 +68,7 @@ cd "${TMPDIR:-/tmp}"
printf 'Saved environment and cwd: %s\n' "$PWD"
```

The next block reads the captured state. Notice that `$UPMD_DEMO_MESSAGE`, `$UPMD_DEMO_COLOR`, and `$PWD` are available despite being set in a separate process.
The next block reads the state captured above.

```bash [name:read-state]
printf 'Message: %s\n' "$UPMD_DEMO_MESSAGE"
Expand All @@ -74,9 +77,7 @@ printf 'Count: %s\n' "$UPMD_DEMO_COUNT"
printf 'Working directory: %s\n' "$PWD"
```

Press `e` in the TUI to inspect or edit the environment before running another block.

## 4. Multiple language runners
## Multiple language runners

Python inherits state captured by the shell block.

Expand All @@ -99,7 +100,7 @@ console.log('Node version:', process.version);

upmd also supports TypeScript, Ruby, PHP, C, Go, Rust, Zig, Fish, Zsh, Cmd, and PowerShell when their executables are installed.

## 5. Named blocks and goto
## Named blocks and goto

Blocks with a `name` attribute are selectable by name or numeric ID.

Expand All @@ -109,9 +110,9 @@ printf 'Jump to it with: upmd DEMO.md --block named\n'
printf 'Or press ctrl-g in the TUI and type "named".\n'
```

## 6. Workflow dependencies
## Workflow dependencies

Blocks can declare dependencies on other blocks. Deps run first, and their captured environment (exported variables, working directory) is inherited by dependents.
Dependencies run first and pass their captured environment and working directory to dependents.

```bash [name:build]
sleep 1; export BIN_PATH="/tmp/demo"
Expand All @@ -130,42 +131,39 @@ printf 'test: %s\n' "$TEST_STATUS"

```bash [name:verify, deps:"lint | test"]
read CONFIRM
printf 'BIN_PATH=%s\n' "$BIN_PATH"
printf 'LINT_REPORT=%s\n' "$LINT_REPORT"
printf 'TEST_STATUS=%s\n' "$TEST_STATUS"
printf "bin: $BIN_PATH, lint: $LINT_REPORT, test: $TEST_STATUS\n"
printf 'verify: complete (%s).\n' "$CONFIRM"
```

`lint` and `test` run in parallel after `build` finishes. Once both pass, `verify` runs and reads their captured state.
`build` runs first. `lint` and `test` then run in parallel; `verify` waits for both.

## TUI controls

Press `'` while a workflow runs to toggle the inline dependency graph below the preview.
Run an interactive prompt and press `i` to send it input:

## 7. File picker and reload
```sh
read -p "Your name: " ME
echo "Hi, $ME!"
```

- Press `f` to browse Markdown files relative to this document.
- Type to filter matches while the selected file is previewed.
- Press `ctrl-r` to reload the active file from disk and clear prior output.
- Directory input works in both frontends: `upmd .` and `upmd --cli .`.
Full-screen programs work inside the preview:

## 8. Themes and help
```sh
nvim
```

- Press `t` to search and select a theme (tokyo-night, catppuccin-mocha, dracula, rose-pine, and more).
- Press `ctrl-t` to toggle the terminal background.
- Press `?` to open the searchable, sectioned keymap reference.
- `i` focuses the selected process; `ctrl-o` leaves input mode.
- `o` opens the full output view.
- `f` browses Markdown files; typing filters the list.
- `ctrl-r` reloads the active file and clears prior output.
- `t` selects a theme; `ctrl-t` toggles the terminal background.
- `e` inspects or edits captured environment state.
- `'` toggles the dependency graph while a workflow runs.
- `?` opens the searchable keymap reference.
- Mouse input, wheel scrolling, drag selection, and `ctrl-v` paste work in PTY views.

Print every configurable key and default binding:

```bash [name:dump-default-config]
upmd --dump-default-config 2>/dev/null | head -20 || echo "dump-default-config unavailable"
```

## 9. Interactive PTY and full output

Every block runs in a pseudo-terminal. While a long-running or full-screen program is active:

- Press `i` to focus its inline terminal and send keys directly.
- Press `ctrl-o` to leave inline input mode.
- Press `o` to open the full output view.
- Mouse input is forwarded when the child enables SGR mouse reporting.
- Otherwise wheel input scrolls local history and drag selection copies text.
- Press `ctrl-v` to paste clipboard text into the process.
2 changes: 1 addition & 1 deletion crates/upmd-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub mod nodes;
pub mod options;
pub mod parser;

pub use nodes::{Code, CodeId, Codes, Dependencies, Heading, Node};
pub use nodes::{Code, CodeId, Codes, Dependencies, Heading, InlineSpan, InlineStyle, Node};

/// Completeness of [`Document::nodes`].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
Expand Down
61 changes: 55 additions & 6 deletions crates/upmd-parser/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,47 @@ use std::ops::Range;

#[derive(Debug, Clone)]
pub enum Node {
Heading { level: u8, text: String },
Paragraph(String),
Heading { level: u8, text: Vec<InlineSpan> },
Paragraph(Vec<InlineSpan>),
BlockQuote(Vec<Node>),
List(Vec<ListItem>),
Code(CodeId),
Table(Table),
Text(String),
Text(Vec<InlineSpan>),
ThematicBreak,
}

/// A run of inline text with the formatting applied to it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineSpan {
pub text: String,
/// Active styles for this run. Empty means plain text; styles nest in
/// source order (e.g. bold inside italic becomes `[Italic, Bold]`).
pub style: Vec<InlineStyle>,
}

/// Inline markdown formatting applied to a [`InlineSpan`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlineStyle {
Bold,
Italic,
Strikethrough,
InlineCode,
Link {
destination: String,
title: Option<String>,
},
Image {
alt: String,
src: String,
},
}

/// Concatenates span text into a single plain string (for search, copy, menus).
pub fn inline_text(spans: &[InlineSpan]) -> String {
spans.iter().map(|s| s.text.as_str()).collect()
}

/// Heading metadata collected during parsing.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Heading {
Expand Down Expand Up @@ -42,14 +73,32 @@ pub enum ListKind {
pub struct ListItem {
pub depth: usize,
pub kind: ListKind,
pub text: String,
pub text: Vec<InlineSpan>,
pub children: Vec<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableCell {
pub spans: Vec<InlineSpan>,
}

impl TableCell {
pub fn text(&self) -> String {
inline_text(&self.spans)
}

pub fn char_len(&self) -> usize {
self.spans
.iter()
.map(|span| span.text.chars().count())
.sum()
}
}

/// Markdown table with headers, rows, and column alignments.
#[derive(Debug, Clone)]
pub struct Table {
pub headers: Vec<String>,
pub rows: Vec<Vec<String>>,
pub headers: Vec<TableCell>,
pub rows: Vec<Vec<TableCell>>,
pub alignments: Vec<Alignment>,
}

Expand Down
Loading
Loading