Skip to content

jscaltreto/downstage

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

154 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Downstage logo, markdown for playwrights

Downstage

Docs Start Writing

A plain-text playwriting format with a simple way to write, preview, and export PDF manuscripts.

What is Downstage?

Downstage is a plaintext markup language for writing stage plays, inspired by Fountain (for screenplays) and the archived TheatreScript spec. It gives you three clear ways to start: the Web Editor, the VS Code extension, or the command line. Files use the .ds extension.

If you just want to start, open the Web Editor.

Read the Syntax Guide.

Website

The Pages site is built with Eleventy from the templates in site/.

npm install
npm --prefix web install
npm run build:site
npm run serve:site

Quick Example

# The Example Play
Author: Jane Smith
Date: 2025
Draft: First

## Dramatis Personae

HAMLET - Prince of Denmark
HORATIO - Friend to Hamlet

### Courtiers

ROSENCRANTZ - A courtier
GUILDENSTERN - A courtier

## ACT I

### SCENE 1

> The battlements of Elsinore Castle. Night.

HORATIO
Who's there?

HAMLET
(aside)
A piece of work is man, how **noble** in reason,
how *infinite* in faculty.
  In form and moving, how express
  and admirable; in action, how like
  an angel.

HORATIO
They're here.

HAMLET ^
Then let them come.

// A line comment

> Enter GHOST

===

### SCENE 2

ROSENCRANTZ
Good my lord!

SONG 1: The Wanderer's Lament

HAMLET
  O, that this too, too solid flesh
  Would melt, thaw, and resolve itself
  Into a dew.

SONG END

Features

  • Readable plaintext format — scripts look natural without markup noise
  • Inline formatting: *italic*, **bold**, ***bold italic***, _underline_, ~strikethrough~
  • Verse support via indentation (2+ spaces)
  • Dual dialogue with trailing ^ on the second cue
  • Songs with SONG/SONG END blocks
  • Comments: // line and /* block */
  • Forced elements: @character and .heading for edge cases
  • Character aliases: HAMLET/HAM or [HAMLET/HAM]
  • Page breaks: ===
  • LSP server with:
    • Semantic syntax highlighting
    • Document outline (acts/scenes/characters)
    • Hover info on character names (shows description from dramatis personae)
    • Go-to-definition (jump to character's dramatis personae entry)
    • Rename Symbol for characters (updates dramatis personae entry, aliases, and matching cues — refuses on prose mentions)
    • Context-aware completion for character cues
    • Diagnostics (parse errors, unknown character warnings, Dramatis Personae hygiene, cue hygiene)
    • Code actions (quick fixes for unknown characters, unnumbered or misnumbered acts/scenes, duplicate DP entries, and inserting a missing Dramatis Personae section)
  • Neovim integration out of the box (0.11+)
  • CLI tools for parsing and validation

Installation

If you are new to Downstage, you probably want the Web Editor or the VS Code extension before you want installation steps.

go install github.com/jscaltreto/downstage@latest

Homebrew

brew tap jscaltreto/tap
brew install downstage

CLI Usage

downstage parse play.ds            # Output AST as JSON
downstage validate play.ds         # Check for errors
downstage stats play.ds            # Report word/dialogue/runtime stats
downstage render play.ds           # Render to PDF (default)
downstage render -f html play.ds   # Render to HTML
downstage revisions play_v2.ds --against play_v1.ds   # Render revision pages
downstage revisions play.ds --from HEAD~1             # ...against a git ref
downstage lsp                      # Start LSP server (stdio)
downstage version                  # Print version info

Use -v or --verbose to enable debug logging on any command.

downstage validate exits non-zero on parse errors. downstage parse prints parse errors to stderr but still emits the AST JSON. downstage render exits non-zero if parsing fails.

Render Formats and Styles

downstage render supports PDF (default) and HTML output via --format:

downstage render play.ds                              # PDF, manuscript
downstage render --format html play.ds                # HTML, manuscript
downstage render --format html --style condensed play.ds
downstage render --format html -o play.html play.ds   # explicit output file

Both formats support two styles via --style:

  • standard (default, Manuscript) — Traditional manuscript format. Character names centered above dialogue, generous margins.
  • condensed (Acting Edition) — Acting edition format designed for rehearsal use. Character names inline with dialogue (e.g. HAMLET. To be or not...), tighter spacing.

PDF also supports --page-size letter (default) and --page-size a4. Manuscript output renders on the selected sheet size. Acting edition output derives its logical page from that sheet size: half-letter for Letter, A5 for A4.

Acting edition can be imposed for print via --pdf-layout:

  • single (default) — one logical page per sheet
  • 2up — two logical pages per landscape sheet
  • booklet — duplex booklet order, padded to a multiple of 4. Print double-sided, then fold in half. --gutter <measurement> sets the inside gap (default 0.125in; accepts in and mm).

HTML produces a self-contained document with embedded CSS using semantic .downstage-* class names for custom styling.

Revision Pages

downstage revisions produces a small PDF containing only the pages that changed between two drafts of a play — the Hollywood "revision pages" workflow. Print the result and slot the pages into a v1 binder by their A/B/C-suffixed numbers; changed blocks get a right-margin asterisk and each page has a top-margin note indicating where it inserts or replaces in the v1 print.

downstage revisions v2.ds --against v1.ds            # explicit prior version
downstage revisions v2.ds --from HEAD~1              # prior version from git
downstage revisions v2.ds --against v1.ds -o rev.pdf # custom output path

Useful flags:

  • --page-numbers v1-labels|natural|none — default v1-labels (A/B/C suffix off v1 page numbers). natural numbers from 1, none suppresses the footer.
  • --mark-changes / --no-mark-changes — right-margin asterisks (default on).
  • --anchor-window N — merge change hunks separated by ≤ N equal blocks into a single region (default 4). Lower for tighter regions, higher for fewer.
  • --removed-marker / --no-removed-marker — emit a REMOVED placeholder page when a revision shortens v1's pagination (default on).

Exit codes: 0 success, 1 parse/render error, 2 no differences detected, 3 git-ref resolution failed.

Block-level granularity: when one word changes inside a long dialogue cue, the whole cue is marked. Per-visual-line marking is a planned follow-up.

Statistics

downstage stats reports manuscript metrics derived from the parsed AST: word counts, dialogue breakdown, per-character speeches and lines, scene/act counts, and a rough runtime estimate.

downstage stats play.ds                 # Human-readable summary
downstage stats play.ds --format json   # Machine-readable output
downstage stats play.ds --rate slow     # Use the 110 wpm preset
downstage stats play.ds --wpm 140 --pause 0  # Custom rate, no pause overhead

Runtime is based on spoken dialogue word count divided by a speaking rate, plus a small pause factor for pacing:

spoken_minutes    = dialogue_words / words_per_minute
estimated_minutes = spoken_minutes * (1 + pause_factor)

Presets: slow (110 wpm), standard (130 wpm, default), conversational (150 wpm). The default pause factor is 10%. Stage directions and prose do not contribute to the runtime estimate. Treat the output as a rough guide, not a prediction.

Editor Setup

Neovim (0.11+)

Install downstage.nvim with your plugin manager:

-- lazy.nvim
{ "jscaltreto/downstage.nvim", ft = "downstage" }

This provides filetype detection, buffer settings, and LSP integration for .ds files. The downstage binary must be on your PATH.

If you use nvim-cmp, enable the plugin's optional completion integration to limit Downstage buffers to LSP-driven cue completions:

-- lazy.nvim
{
  "jscaltreto/downstage.nvim",
  ft = "downstage",
  opts = {
    cmp = true,
  },
}

Other Editors

Any LSP-compatible editor can use the Downstage language server. The server communicates over stdio using JSON-RPC 2.0 (LSP 3.17). Point your editor's LSP client at the server:

{
  "command": ["downstage", "lsp"],
  "filetypes": ["downstage"],
  "rootPatterns": [".git"]
}

VS Code

The editors/vscode/ extension provides full Downstage support: LSP-powered completions, diagnostics, and folding; live PDF preview; render-to-PDF commands; snippets; and TextMate syntax highlighting.

See the extension README for details.

For local development:

cd editors/vscode
npm install
npm run compile

Then open the editors/vscode folder in VS Code and press F5 to launch an Extension Development Host.

Desktop App (alpha)

Alpha / pre-release. The desktop app is under active development. No pre-built binaries are published yet, the on-disk config format may still change, and some workflows (drafts, cross-device sync, auto-update) are unimplemented. Build from source and expect rough edges.

cmd/downstage-write/ is a native desktop build — the same Vue editor wrapped in a Wails shell with a single-library workflow: open a directory of .ds files, edit in place, snapshot revisions with git, and restore older versions from a native menu + command palette.

Shared prerequisites (all platforms)

  • Go 1.23+
  • Node 20+
  • Wails v2 CLI: go install github.com/wailsapp/wails/v2/cmd/wails@latest
  • Frontend deps: npm --prefix web install (one-time)

Run wails doctor after installing the CLI — it lists anything else your system is missing.

Linux / macOS

Linux additionally needs webkit2gtk 4.1 (sudo apt install libwebkit2gtk-4.1-dev on Debian/Ubuntu; equivalents via dnf/pacman on other distros). macOS has no extra system packages.

make desktop-dev    # live-reloading dev build
make desktop-build  # release build → cmd/downstage-write/build/bin/

make desktop-dev (via wails dev) serves the real desktop entry (desktop.htmlAppDesktop.vue) with HMR; it's independent of the browser-app dev flow at make web-dev, which keeps serving the Vue web editor at /editor/.

Windows

Windows uses WebView2, which ships with Windows 10 20H1+ and Windows 11. On older Windows, install the Evergreen runtime from Microsoft. No other system packages are needed.

Since GNU make isn't part of Windows, use the bundled PowerShell wrapper (from the repo root):

.\scripts\build-desktop.ps1           # release build
.\scripts\build-desktop.ps1 -Mode dev # hot-reload dev mode

The binary lands at cmd\downstage-write\build\bin\downstage-write.exe.


See internal/desktop/AGENTS.md for the backend architecture and web/src/desktop/AGENTS.md for the frontend layer.

Web Editor

Start writing in the Web Editor, a modern browser-based editor built with Vue 3, Tailwind CSS v4, and CodeMirror 6. It features live preview, adaptive syntax highlighting, completions and quick-fix code actions sourced from the Downstage LSP, spellcheck with a per-script dictionary, and PDF export. No install required; the entire pipeline runs client-side via WebAssembly.

For local development:

npm --prefix web install
make web      # Build the editor for Pages output
make web-dev  # Run the editor dev server with Vite

See web/README.md for details.

Language Overview

A Downstage document is organized around top-level # sections:

  1. Play Header — each play starts with # Title, followed by optional Key: Value metadata lines such as Author: or Draft:
  2. Dramatis Personae — an optional ## Dramatis Personae, ## Cast of Characters, or ## Characters section inside that play, with character entries and optional ### subgroup headings; rendered output keeps the chosen wording
  3. Body — the play itself: acts (## ACT), scenes (### SCENE), dialogue (ALL CAPS character name followed by speech text), stage directions (> prefixed lines), callouts (>> prefixed lines), verse (indented 2+ spaces), songs, and comments

Dual Dialogue

To mark simultaneous dialogue, put ^ at the end of the second character cue.

HORATIO
They're here.

HAMLET ^
Then let them come.

The renderer places the two dialogue blocks side by side when they fit on the page. If they do not fit cleanly, rendering falls back to sequential dialogue instead of producing broken columns.

See SPEC.md for the complete language specification.

Building from Source

git clone https://github.com/jscaltreto/downstage.git
cd downstage
make

To embed version information:

go build -ldflags "\
  -X github.com/jscaltreto/downstage/cmd.version=1.0.0 \
  -X github.com/jscaltreto/downstage/cmd.commit=$(git rev-parse HEAD) \
  -X github.com/jscaltreto/downstage/cmd.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -o downstage .

Releases

Release PRs and changelog updates are managed by Release Please. Merging a release PR creates the Git tag and GitHub release, and GoReleaser then builds artifacts for macOS, Linux, and Windows on amd64 and arm64.

For local release validation:

make release-check
make release-snapshot

These targets require goreleaser to be installed locally.

Publishing the Homebrew formula is handled by the release workflow, which updates jscaltreto/homebrew-tap after GoReleaser publishes release assets. This requires a repository secret named HOMEBREW_TAP_GITHUB_TOKEN with push access to jscaltreto/homebrew-tap.

Release Please requires a repository secret named RELEASE_PLEASE_TOKEN with enough access to create release PRs, tags, and releases in jscaltreto/downstage.

License

Downstage source code is licensed under the MIT License.

Bundled fonts in internal/render/pdf/fonts/ remain under the SIL Open Font License 1.1; see the included OFL.txt and OFL-LibreBaskerville.txt files.

Acknowledgments

  • Fountain — screenplay markup language that inspired Downstage's plaintext philosophy
  • TheatreScript — archived stage play markup spec that Downstage builds on

About

Plaintext markup language and tools for stage plays

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors