Skip to content

IDE

IDE guide

The Nowline VS Code / Cursor extension — LSP, live preview, snippets.

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.

MilestoneScope
m1TextMate grammar (syntax highlighting only, ships with the DSL)
m3LSP server + VS Code/Cursor extension with live preview
m4.5Obsidian 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)

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.

TokenScopeExample
Keywordskeyword.control.nowlineconfig, roadmap, include, person, team, anchor, swimlane, item, parallel, group, milestone, label, duration, status, footnote
Stringsstring.quoted.double.nowline"Auth refactor"
Propertiesentity.other.attribute-name.nowlinestatus:, owner:, after:, before:, duration:, length:, remaining:, labels:, link:, style:, depends:, date:, on:
Property valuesconstant.other.nowlinedone, in-progress, at-risk, blocked, planned, merge, ignore, isolate
Identifiersvariable.other.nowlineauth-refactor, audit-log
Identifiers (references)entity.name.tag.nowlinesam, jen, platform (in owner:, on:, depends:, after:, before:)
URLsmarkup.underline.link.nowlinehttps://github.com/acme/...
Commentscomment.line.double-slash.nowline// comment
Listsmeta.structure.list.nowlinelabels:[enterprise, security], depends:[auth-refactor, audit-log]
Datesconstant.numeric.date.nowline2026-06-01

Langium generates an LSP server from the grammar definition. The server runs as a standalone process and communicates over stdio or TCP.

FeatureDescription
AutocompleteKeyword 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:[]
ValidationReal-time error reporting — parse errors, unknown references, circular dependencies, invalid enum values
Go-to-definitionJump from after:auth-refactor to the item with id:auth-refactor
Find referencesFind all usages of an id across the file
HoverShow item details on hover (title, status, owner, links)
RenameRename an id and update all references
Document symbolsOutline view showing roadmap → swimlanes → items hierarchy
FoldingFold swimlane blocks
FormattingAuto-indent and align properties

The LSP server is available in two forms:

  1. 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.
  2. Via the CLI (nowline lsp) — for editors that cannot bundle Node.js modules (Neovim, JetBrains, Emacs). Requires the nowline CLI to be installed.

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).

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/renderer bundled into the webview for client-side parsing and rendering.

Users install the extension from the marketplace and it works immediately.

  1. Syntax highlighting — via the TextMate grammar.
  2. LSP integration — autocomplete, validation, go-to-definition, rename, etc.
  3. Live preview panel — side panel that renders the .nowline file as SVG and re-renders on every keystroke or save. Uses @nowline/renderer running in a webview.
  4. File icon — custom icon for .nowline files in the explorer.
  5. Snippets — code snippets for common patterns (new swimlane, new item, new parallel/group).
  6. CommandsNowline: Open Preview, Nowline: Open Preview to the Side, Nowline: Open Link in Side Browser, Nowline: Export… (m3e), Nowline: New Roadmap… (m3f).
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.0

The preview panel is a VS Code webview that displays an SVG rendered by the extension host, not by the webview itself:

  1. The extension host reuses the CLI’s pipeline (parseSourceresolveIncludeslayoutRoadmaprenderSvg) inside preview/render-pipeline.ts. This keeps include: resolution, asset embedding, and theme handling identical to the CLI without bouncing readFile callbacks across the host/webview message boundary.
  2. 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).
  3. Updates fire on onDidChangeTextDocument (debounced; default 200 ms), onDidSaveTextDocument (immediate), onDidChangeActiveColorTheme, any nowline.preview.* / nowline.export.* / nowline.ignoreRcFile setting change, and (m3d+) any change to a .nowlinerc reachable from the source file.
  4. Two open commands match VS Code’s markdown UX: nowline.openPreview (Cmd+Shift+V, same tab) and nowline.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.

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-in workbench.action.toggleMaximizeEditorGroup; the icon swap is driven by the nowline.previewMaximized context 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 the 3 / 1 keyboard presets.
  • More-menu. Format, Copy, Export, Theme, Now (calendar), and Show-links live in a ▾ more menu. 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’s exportControls option (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.

The extension resolves render-affecting and export-affecting options through a single chain (highest wins):

  1. Toolbar / session override — applies only to the active preview panel; not persisted.
  2. VS Code settingsnowline.preview.*, nowline.export.*, nowline.ignoreRcFile, nowline.trace.server.
  3. .nowlinerc — discovered by walking up from the source file; honored by default. Skipped when nowline.ignoreRcFile is true.
  4. DSL directive — e.g. nowline v1 locale:fr-CA in the source file.
  5. 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 uses two chains, mirroring the CLI’s packages/cli/src/i18n/locale.ts:

  • Operator chain (validator-table messages): nowline.preview.locale > .nowlinerc locale > 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.

SettingDefaultCLI flagNotes
nowline.trace.serveroffLSP trace level (off / messages / verbose). Ships in m3b.
nowline.ignoreRcFilefalseSkip the .nowlinerc baseline lookup entirely. Ships in m3d.
nowline.preview.refreshOnkeystrokekeystroke or save. Ships in m3c.
nowline.preview.debounceMs200Keystroke debounce window. Ships in m3c.
nowline.preview.themeauto--themeauto follows the active VS Code color theme. Ships in m3c.
nowline.preview.defaultFitfitPageInitial viewport fit. Ships in m3c.
nowline.preview.showMinimaptrueMinimap default visibility. Ships in m3c.
nowline.preview.locale""--localeBCP-47; empty falls through to .nowlinercvscode.env.languageen-US. File directive overrides for content. Ships in m3d.
nowline.preview.nowauto--nowauto / none / YYYY-MM-DD. Ships in m3d.
nowline.preview.strictfalse--strictPromotes asset / sanitizer warnings to errors. Ships in m3d.
nowline.preview.showLinkstrueinverse of --no-linksToggle link icons in rendered items. Ships in m3d.
nowline.preview.width0--width0 = unset; preview has zoom anyway. Ships in m3d.
nowline.preview.assetRoot""--asset-rootEmpty = source file’s directory. Ships in m3d.
nowline.export.cliPathnowlinePath to the nowline binary; PATH lookup by default. ${workspaceFolder} substitution supported. Ships in m3e.
nowline.export.pdf.pageSizeletter--page-sizeShips in m3e.
nowline.export.pdf.orientationauto--orientationShips in m3e.
nowline.export.pdf.margin36pt--marginShips in m3e.
nowline.export.fonts.sans""--font-sansShips in m3e.
nowline.export.fonts.mono""--font-monoShips in m3e.
nowline.export.fonts.headlessfalse--headlessShips in m3e.
nowline.export.png.scale1--scaleShips in m3e.
nowline.export.msproj.start""--startShips in m3e.

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.

The preview toolbar adds three per-session controls that don’t write back to settings:

  • ThemeAuto / 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, matching nowline.preview.theme and --theme. The UK spelling greyscale is accepted as an alias and canonicalizes to grayscale.
  • Now-line — show today / show as-of date / hide (--now parity).
  • Show links — Yes / No dropdown in the toolbar more-menu; the initial state reflects the nowline.preview.showLinks setting (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.

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:

  1. Pick format → save dialog.
  2. Run the full parse → layout → render → export pipeline in the extension host.
  3. Write the result bytes to the chosen path.
  4. Surface failures via vscode.window.showErrorMessage + the Nowline Export output 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 svg writes.
  • Copy PNG: the happy path calls navigator.clipboard.write() from inside the preview webview. Because VS Code’s env.clipboard is 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 via exportInProcess).

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.

CommandKeybindingNotes
Nowline: Open PreviewCmd/Ctrl+Shift+VSame tab. Ships in m3c.
Nowline: Open Preview to the SideCmd/Ctrl+K VBeside. Ships in m3c.
Nowline: Open Link in Side BrowserOpens 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.

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.

  1. Inline preview```nowline code blocks render as SVG in reading mode and live preview mode.
  2. Edit support — syntax highlighting in the code block (via CodeMirror 6 extension).
  3. Settings — theme selection, auto-render toggle.

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.

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.

A documentation page and a sample config — not a maintained plugin. The LSP server does the heavy lifting.

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:

  1. Registers .nowline as a file type.
  2. Points the LSP client to the nowline lsp command.
  3. Bundles the TextMate grammar for highlighting.

A JetBrains Marketplace plugin. Minimal custom code — mostly configuration wrapping the LSP server.

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:

  1. Registers a tree-sitter grammar for .nowline files (syntax highlighting, structural selection).
  2. Points Zed’s built-in LSP client at nowline lsp — the same subcommand used by Neovim and JetBrains.

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:

  1. Adds this monorepo as a git submodule.
  2. Adds an extensions.toml entry whose path field points at the submodule’s packages/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.

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-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.

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.

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 port

Used by Neovim, JetBrains, and other LSP-capable editors. Not needed by the VS Code/Cursor extension (which bundles the LSP directly).