IDE
IDE guide
The Nowline VS Code / Cursor extension — LSP, live preview, snippets.
Overview
Section titled “Overview”Nowline provides first-class IDE support through a Language Server Protocol (LSP) server and platform-specific extensions. The LSP server is generated by Langium alongside the parser, giving us autocomplete, validation, and navigation for free.
Milestones
Section titled “Milestones”| Milestone | Scope |
|---|---|
| m1 | TextMate grammar (syntax highlighting only, ships with the DSL) |
| m3 | LSP server + VS Code/Cursor extension with live preview |
| m4.5 | Obsidian plugin, Neovim LSP config, JetBrains plugin; Zed extension (priority); VS Code-fork editors via Open VSX: Windsurf, Antigravity, Trae, Kiro, VSCodium, PearAI, Void (timing TBD) |
TextMate Grammar (m1)
Section titled “TextMate Grammar (m1)”A nowline.tmLanguage.json file that provides syntax highlighting in any editor that supports TextMate grammars (VS Code, Sublime Text, Atom, TextMate, GitHub linguist).
Ships in the OSS monorepo at grammars/nowline.tmLanguage.json.
Token Scopes
Section titled “Token Scopes”| Token | Scope | Example |
|---|---|---|
| Keywords | keyword.control.nowline | config, roadmap, include, person, team, anchor, swimlane, item, parallel, group, milestone, label, duration, status, footnote |
| Strings | string.quoted.double.nowline | "Auth refactor" |
| Properties | entity.other.attribute-name.nowline | status:, owner:, after:, before:, duration:, length:, remaining:, labels:, link:, style:, depends:, date:, on: |
| Property values | constant.other.nowline | done, in-progress, at-risk, blocked, planned, merge, ignore, isolate |
| Identifiers | variable.other.nowline | auth-refactor, audit-log |
| Identifiers (references) | entity.name.tag.nowline | sam, jen, platform (in owner:, on:, depends:, after:, before:) |
| URLs | markup.underline.link.nowline | https://github.com/acme/... |
| Comments | comment.line.double-slash.nowline | // comment |
| Lists | meta.structure.list.nowline | labels:[enterprise, security], depends:[auth-refactor, audit-log] |
| Dates | constant.numeric.date.nowline | 2026-06-01 |
LSP Server (m3)
Section titled “LSP Server (m3)”Langium generates an LSP server from the grammar definition. The server runs as a standalone process and communicates over stdio or TCP.
Capabilities
Section titled “Capabilities”| Feature | Description |
|---|---|
| Autocomplete | Keyword completion (swimlane, item, parallel, group, milestone, etc.), property name completion (status:, owner:, duration:, etc.), property value completion (done, in-progress, etc.), ID reference completion in after:, before:, owner:, depends:[], on:, labels:[] |
| Validation | Real-time error reporting — parse errors, unknown references, circular dependencies, invalid enum values |
| Go-to-definition | Jump from after:auth-refactor to the item with id:auth-refactor |
| Find references | Find all usages of an id across the file |
| Hover | Show item details on hover (title, status, owner, links) |
| Rename | Rename an id and update all references |
| Document symbols | Outline view showing roadmap → swimlanes → items hierarchy |
| Folding | Fold swimlane blocks |
| Formatting | Auto-indent and align properties |
Distribution
Section titled “Distribution”The LSP server is available in two forms:
- Bundled in the VS Code/Cursor extension — the extension packages the Langium-generated LSP as a Node.js module. No CLI install required. The extension is fully self-contained.
- Via the CLI (
nowline lsp) — for editors that cannot bundle Node.js modules (Neovim, JetBrains, Emacs). Requires thenowlineCLI to be installed.
VS Code / Cursor Extension (m3)
Section titled “VS Code / Cursor Extension (m3)”Source: packages/vscode-extension/ in this monorepo (OSS, Apache 2.0). Published to the VS Code Marketplace and Open VSX from monorepo CI (.github/workflows/release.yml) via vsce publish and ovsx publish. No standalone lolay/nowline-vscode repository is needed: both marketplaces publish from a built package directory, not a repo root.
Marketplace: Visual Studio Marketplace + Open VSX (for Cursor, VSCodium).
Self-Contained
Section titled “Self-Contained”The extension bundles everything it needs — no nowline CLI install required:
- LSP server — Langium-generated LSP runs as a bundled Node.js module in the extension host process.
- Preview renderer —
@nowline/core+@nowline/layout+@nowline/rendererbundled into the webview for client-side parsing and rendering.
Users install the extension from the marketplace and it works immediately.
Features
Section titled “Features”- Syntax highlighting — via the TextMate grammar.
- LSP integration — autocomplete, validation, go-to-definition, rename, etc.
- Live preview panel — side panel that renders the
.nowlinefile as SVG and re-renders on every keystroke or save. Uses@nowline/rendererrunning in a webview. - File icon — custom icon for
.nowlinefiles in the explorer. - Snippets — code snippets for common patterns (new swimlane, new item, new parallel/group).
- Commands —
Nowline: Open Preview,Nowline: Open Preview to the Side,Nowline: Open Link in Side Browser,Nowline: Export…(m3e),Nowline: New Roadmap…(m3f).
Extension Structure
Section titled “Extension Structure”nowline-vscode/ src/ extension.ts # Activation, LSP client, command dispatch server-launcher.ts # Bundled LSP server boot io/ rc-config.ts # .nowlinerc reader + workspace watcher (m3d) disagreement-check.ts # Settings-vs-.nowlinerc shadow notifications (m3f) export/ cli-runner.ts # `Nowline: Export…` shell-out (m3e) new-roadmap.ts # `Nowline: New Roadmap…` scaffolder (m3f) preview/ preview-manager.ts # One-panel-per-source registry preview-panel.ts # Wraps vscode.WebviewPanel, debounced render render-pipeline.ts # parseSource + resolveIncludes + layout + renderSvg option-resolver.ts # Collapses settings/.nowlinerc/env/defaults into render inputs (m3d) shell-html.ts # Webview HTML/CSS/JS shell diagnostic-row.ts # Adapters for the host -> webview diagnostic protocol syntaxes/ nowline.tmLanguage.json # Synced from monorepo grammars/ snippets/ nowline.json package.json # Extension manifest LICENSE # Apache 2.0Live Preview
Section titled “Live Preview”The preview panel is a VS Code webview that displays an SVG rendered by the extension host, not by the webview itself:
- The extension host reuses the CLI’s pipeline (
parseSource→resolveIncludes→layoutRoadmap→renderSvg) insidepreview/render-pipeline.ts. This keepsinclude:resolution, asset embedding, and theme handling identical to the CLI without bouncingreadFilecallbacks across the host/webview message boundary. - The host posts
{ type: 'svg', body }(success),{ type: 'diagnostics', rows }(parse / validate / include errors), or{ type: 'fatal', message }(unexpected throw) into a “dumb” webview that renders SVG, a clickable diagnostic table, and a viewport layer (toolbar, zoom, pan, minimap, save / copy). - Updates fire on
onDidChangeTextDocument(debounced; default 200 ms),onDidSaveTextDocument(immediate),onDidChangeActiveColorTheme, anynowline.preview.*/nowline.export.*/nowline.ignoreRcFilesetting change, and (m3d+) any change to a.nowlinercreachable from the source file. - Two open commands match VS Code’s markdown UX:
nowline.openPreview(Cmd+Shift+V, same tab) andnowline.openPreviewToSide(Cmd+K V, beside).
This deviates from earlier drafts of this spec that called for client-side parsing and rendering inside the webview. The host-render approach was chosen to avoid asset / include round-trips, ship one bundle instead of two, and reuse the exact CLI codepath. The future embed (m4) still owns the client-side bundle for browser environments without an extension host.
Preview toolbar chrome
Section titled “Preview toolbar chrome”The viewport chrome is provided by @nowline/preview-shell’s mountPreview() (see specs/architecture.md § surfaces). UX rules the toolbar must hold to:
- Default placement & resize. The toolbar floats in the upper-right corner by default and tracks that corner on resize. It keeps its natural width: when the panel narrows it shifts left as a whole rather than squishing or wrapping its controls. A drag grip repositions it anywhere in the viewport (clamped to the gutter); the dragged position persists for the JS session.
- Collapse / restore. A
«control collapses the toolbar to a translucent puck (just the drag grip and a»restore arrow);»expands it again. (Replaces the earlier×hide-and-auto-fade affordance.) - Tab-frame expand / collapse. A
$(screen-full)button in the preview panel’s VS Code title bar (next to Show Source) maximizes the editor group so the preview fills the entire editor area — exactly the “fill the window” button on the free web app. Clicking it again (now a$(screen-normal)icon) restores the previous layout. Implemented via VS Code’s built-inworkbench.action.toggleMaximizeEditorGroup; the icon swap is driven by thenowline.previewMaximizedcontext key. Closing a maximized preview clears the context so the icon never strands in the “restore” state. - Fit controls. Separate Fit width (
↔) and Fit page (⤢) buttons, mirroring the3/1keyboard presets. - More-menu. Format, Copy, Export, Theme, Now (calendar), and Show-links live in a
▾ moremenu. The Export action uses a download glyph; Copy / Export each take half the action row and are centred. Sub-menus size to their content (no dead whitespace). Format / Copy / Export are gated by@nowline/preview-shell’sexportControlsoption (default'show'; hidden in MCP Apps in-chat previews where export is tool-owned). - Stay in view. The more-menu, its sub-dropdowns, and the Now calendar flip and clamp so they always render inside the preview root and respect the gutter — they never run off-screen, regardless of where the toolbar sits.
Configuration
Section titled “Configuration”The extension resolves render-affecting and export-affecting options through a single chain (highest wins):
- Toolbar / session override — applies only to the active preview panel; not persisted.
- VS Code settings —
nowline.preview.*,nowline.export.*,nowline.ignoreRcFile,nowline.trace.server. .nowlinerc— discovered by walking up from the source file; honored by default. Skipped whennowline.ignoreRcFileistrue.- DSL directive — e.g.
nowline v1 locale:fr-CAin the source file. - Built-in defaults — match the CLI defaults from
specs/cli.md.
This keeps the in-panel preview byte-stable with nowline render for the same source.
Locale resolution (special case)
Section titled “Locale resolution (special case)”Locale uses two chains, mirroring the CLI’s packages/cli/src/i18n/locale.ts:
- Operator chain (validator-table messages):
nowline.preview.locale>.nowlinerclocale>vscode.env.language>en-US. - Content chain (rendered axis labels, now-pill, footnote sort): the file’s
nowline v1 locale:directive wins outright; the operator chain only takes over when no directive is present.
vscode.env.language plays the role the CLI’s LC_ALL/LC_MESSAGES/LANG env vars play — it reflects Cursor’s display language (set via Cursor’s “Configure Display Language” command; defaults to the OS locale on first install). A French-installed Cursor therefore renders a French preview and French diagnostics with nowline.preview.locale left at its empty default. A power user who wants English output despite a French Cursor sets nowline.preview.locale to en-US. Locale is deliberately not exposed as a toolbar override — Cursor-locale auto-detection plus directive precedence covers the cases a per-panel toggle would address.
Settings
Section titled “Settings”| Setting | Default | CLI flag | Notes |
|---|---|---|---|
nowline.trace.server | off | — | LSP trace level (off / messages / verbose). Ships in m3b. |
nowline.ignoreRcFile | false | — | Skip the .nowlinerc baseline lookup entirely. Ships in m3d. |
nowline.preview.refreshOn | keystroke | — | keystroke or save. Ships in m3c. |
nowline.preview.debounceMs | 200 | — | Keystroke debounce window. Ships in m3c. |
nowline.preview.theme | auto | --theme | auto follows the active VS Code color theme. Ships in m3c. |
nowline.preview.defaultFit | fitPage | — | Initial viewport fit. Ships in m3c. |
nowline.preview.showMinimap | true | — | Minimap default visibility. Ships in m3c. |
nowline.preview.locale | "" | --locale | BCP-47; empty falls through to .nowlinerc → vscode.env.language → en-US. File directive overrides for content. Ships in m3d. |
nowline.preview.now | auto | --now | auto / none / YYYY-MM-DD. Ships in m3d. |
nowline.preview.strict | false | --strict | Promotes asset / sanitizer warnings to errors. Ships in m3d. |
nowline.preview.showLinks | true | inverse of --no-links | Toggle link icons in rendered items. Ships in m3d. |
nowline.preview.width | 0 | --width | 0 = unset; preview has zoom anyway. Ships in m3d. |
nowline.preview.assetRoot | "" | --asset-root | Empty = source file’s directory. Ships in m3d. |
nowline.export.cliPath | nowline | — | Path to the nowline binary; PATH lookup by default. ${workspaceFolder} substitution supported. Ships in m3e. |
nowline.export.pdf.pageSize | letter | --page-size | Ships in m3e. |
nowline.export.pdf.orientation | auto | --orientation | Ships in m3e. |
nowline.export.pdf.margin | 36pt | --margin | Ships in m3e. |
nowline.export.fonts.sans | "" | --font-sans | Ships in m3e. |
nowline.export.fonts.mono | "" | --font-mono | Ships in m3e. |
nowline.export.fonts.headless | false | --headless | Ships in m3e. |
nowline.export.png.scale | 1 | --scale | Ships in m3e. |
nowline.export.msproj.start | "" | --start | Ships in m3e. |
.nowlinerc handling
Section titled “.nowlinerc handling”The extension reads .nowlinerc via the same loadConfig helper used by @nowline/cli — extracted to the shared @nowline/config package so both consumers stay byte-identical. A workspace FileSystemWatcher('**/.nowlinerc') invalidates the per-directory cache and re-renders any open preview whose source file resolves through the changed config.
Honored keys for the preview: theme, width, locale, assetRoot. The export-only keys (pdfPageSize, pdfOrientation, pdfMargin, fontSans, fontMono, headlessFonts) are read by the export command only. defaultFormat is consulted by the export command’s format picker as the pre-selected default.
Toolbar overrides
Section titled “Toolbar overrides”The preview toolbar adds three per-session controls that don’t write back to settings:
- Theme — Auto /
light/dark/grayscale(overrides the resolved diagram theme for screenshots). Auto is a client-side meta-option — Title-case in the UI, not a CLI token — meaning “follow the active Mode.” The real theme tokens (light,dark,grayscale) are lowercase and code-styled throughout, matchingnowline.preview.themeand--theme. The UK spellinggreyscaleis accepted as an alias and canonicalizes tograyscale. - Now-line — show today / show as-of date / hide (
--nowparity). - Show links — Yes / No dropdown in the toolbar more-menu; the initial state reflects the
nowline.preview.showLinkssetting (which still applies).
Theme vs Mode. Theme (nowline.preview.theme) is the diagram render palette — auto, light, dark, or grayscale (greyscale accepted as alias). Mode is the color scheme of the tooling chrome (toolbar, VS Code workbench), driven by VS Code’s onDidChangeActiveColorTheme. auto on the Theme axis means “follow the active Mode”; system is the Mode-axis equivalent where chrome is present (e.g. the Free SPA). The embed has no Mode axis — it samples prefers-color-scheme once on init.
Locale, strict, width, and asset-root stay settings-only — they aren’t things you flip while skimming a roadmap.
Export to other formats
Section titled “Export to other formats”Nowline: Export… (palette + editor-title menu + tab right-click; m3e) produces PDF, PNG, SVG, HTML, Markdown+Mermaid, XLSX, MS Project XML, or canonical JSON in-process — no nowline CLI install required. The flow:
- Pick format → save dialog.
- Run the full parse → layout → render → export pipeline in the extension host.
- Write the result bytes to the chosen path.
- Surface failures via
vscode.window.showErrorMessage+ theNowline Exportoutput channel.
PNG rasterization uses @resvg/resvg-wasm (pure WASM build of the same resvg engine), bundled into the .vsix as dist/resvg.wasm (~2.4 MB). All formats — including PNG — produce byte-for-byte identical output to nowline -f <fmt> for the same source and render inputs (see specs/export-determinism.md).
Copy semantics — the preview toolbar has two copy actions:
- Copy SVG: posts the engine-rendered SVG string to the clipboard. This is canonical output; the string equals what
nowline -f svgwrites. - Copy PNG: the happy path calls
navigator.clipboard.write()from inside the preview webview. Because VS Code’senv.clipboardis text-only, the extension host cannot write image bytes; instead, the webview uses its own<canvas>rasterization — a documented non-canonical exception. This path is intentionally non-canonical because VS Code’s clipboard API makes canonical (WASM) rasterization inside a user gesture impractical. If the webview clipboard write is unavailable, the fallback path saves a temp file — the fallback temp-file bytes ARE kernel-canonical (written viaexportInProcess).
Save PNG (the preview toolbar “Save” button) and the copy-PNG fallback temp file both use the kernel (@nowline/export via exportInProcess) and are byte-for-byte identical to Nowline: Export… → PNG. The in-clipboard PNG is the only exception.
CLI override escape hatch. If nowline.export.cliPath is set to an explicit path (anything other than the default 'nowline'), the command shells out to that binary instead — preserving exact CLI-identical output for environments where that matters. The existing in-webview SVG and browser-canvas-PNG toolbar buttons are unaffected.
.vsix footprint. The compressed marketplace bundle is approximately 2.5 MB (extension.cjs ~4 MB minified + resvg.wasm ~2.4 MB + server.cjs ~0.6 MB). This is larger than the pre-m3e footprint (~2 MB) but well within the VS Code Marketplace recommended limit.
Authoring commands
Section titled “Authoring commands”| Command | Keybinding | Notes |
|---|---|---|
Nowline: Open Preview | Cmd/Ctrl+Shift+V | Same tab. Ships in m3c. |
Nowline: Open Preview to the Side | Cmd/Ctrl+K V | Beside. Ships in m3c. |
Nowline: Open Link in Side Browser | — | Opens the URL nearest the cursor in VS Code’s Simple Browser. Ships in m3c. |
Nowline: Export… | — | See § Export to other formats. Ships in m3e. |
Nowline: New Roadmap… | — | Scaffolds a .nowline file from the same starter the CLI’s --init writes; prompts for a name and target folder. Ships in m3f. |
Obsidian Plugin (m4.5)
Section titled “Obsidian Plugin (m4.5)”Source: packages/obsidian-plugin/ (planned, m4.5) in this monorepo (OSS, Apache 2.0).
Marketplace mirror: lolay/nowline-obsidian (write-only; populated by release.yml on each tag. Exists because Obsidian’s community directory requires manifest.json, README.md, and LICENSE at the repository root plus release assets attached there — the same root-repo constraint that requires the lolay/nowline-action mirror described in specs/embed.md).
Marketplace: Obsidian Community Plugins.
Features
Section titled “Features”- Inline preview —
```nowlinecode blocks render as SVG in reading mode and live preview mode. - Edit support — syntax highlighting in the code block (via CodeMirror 6 extension).
- Settings — theme selection, auto-render toggle.
How It Works
Section titled “How It Works”The plugin registers a markdown post-processor that finds ```nowline blocks and replaces them with rendered SVG. It bundles @nowline/core, @nowline/layout, and @nowline/renderer — the same stack as the embed script.
Neovim (m4.5)
Section titled “Neovim (m4.5)”No separate plugin required. Neovim’s built-in LSP client connects to the Nowline LSP server.
Users add a config snippet to their Neovim LSP configuration:
require('lspconfig').nowline.setup({ cmd = { 'nowline', 'lsp' }, filetypes = { 'nowline' },})The nowline lsp subcommand starts the LSP server in stdio mode.
A TreeSitter grammar (future) could provide better highlighting than the TextMate grammar, but the TextMate grammar works via existing Neovim TextMate plugins.
Deliverable
Section titled “Deliverable”A documentation page and a sample config — not a maintained plugin. The LSP server does the heavy lifting.
JetBrains (m4.5)
Section titled “JetBrains (m4.5)”Source: packages/jetbrains-plugin/ (planned) in this monorepo. Uploaded to JetBrains Marketplace from CI. No standalone repository is needed.
JetBrains IDEs (IntelliJ, WebStorm, etc.) support LSP via the built-in LSP client (2023.2+).
A lightweight plugin that:
- Registers
.nowlineas a file type. - Points the LSP client to the
nowline lspcommand. - Bundles the TextMate grammar for highlighting.
Deliverable
Section titled “Deliverable”A JetBrains Marketplace plugin. Minimal custom code — mostly configuration wrapping the LSP server.
Zed (m4.5)
Section titled “Zed (m4.5)”Package: packages/zed-extension/ in this monorepo (planned, OSS, Apache 2.0).
Registry: Zed Extension Registry.
Zed is the priority m4.5 editor target beyond the initial Obsidian / Neovim / JetBrains set. It has a native extension API (Rust WASM host, tree-sitter grammars, built-in LSP client) and an open extension registry.
A Zed extension that:
- Registers a tree-sitter grammar for
.nowlinefiles (syntax highlighting, structural selection). - Points Zed’s built-in LSP client at
nowline lsp— the same subcommand used by Neovim and JetBrains.
Distribution
Section titled “Distribution”The extension lives in this monorepo at packages/zed-extension/, alongside packages/vscode-extension/ and packages/nowline-action/. No standalone lolay/nowline-zed repository is needed: Zed’s registry has no repo-root constraint (unlike the GitHub Action, whose lolay/nowline-action mirror exists only because GitHub Marketplace forces action.yml to the repo root).
Publishing is a PR to zed-industries/extensions that:
- Adds this monorepo as a git submodule.
- Adds an
extensions.tomlentry whosepathfield points at the submodule’spackages/zed-extension/subdirectory — the same monorepo pattern the live registry already uses (path = "packages/zed",path = "editors/zed", etc.).
License caveat: the Apache-2.0 LICENSE must reside at the package path; a root LICENSE alone does not satisfy the registry. Symlink the monorepo license into packages/zed-extension/ so the package directory carries it directly.
Deliverable
Section titled “Deliverable”The packages/zed-extension/ package, published to the Zed extension registry via the submodule + path PR above. Minimal Rust code wrapping the grammar and LSP configuration.
VS Code-Fork Editors via Open VSX (m4.5)
Section titled “VS Code-Fork Editors via Open VSX (m4.5)”VS Code-compatible editors that source extensions from Open VSX — VSCodium, PearAI, Void, Windsurf, Antigravity, Trae, and Kiro — are served by the nowline.vscode-nowline Open VSX listing published in m3. No separate per-fork packaging is planned.
Users install nowline.vscode-nowline from the editor’s built-in extension browser or directly from open-vsx.org. No separate per-fork build or submission is required — the same artifact serves all seven editors.
Deliverable
Section titled “Deliverable”Per-fork installation and compatibility verification, planned for m4.5. These editors are not yet confirmed-working. The VS Code extension API is expected to carry over given the shared extension model; webview and preview-panel behavior in any given fork may require adjustments once testing begins.
LSP Subcommand
Section titled “LSP Subcommand”The CLI gains a hidden nowline lsp command in m4.5 for editors that cannot bundle the LSP as a Node.js module:
nowline lsp [options]
Options: --stdio Use stdio transport (default) --port <n> Use TCP transport on specified portUsed by Neovim, JetBrains, and other LSP-capable editors. Not needed by the VS Code/Cursor extension (which bundles the LSP directly).