MirrorstoneObsidian-style Markdown (OMD) for VS Code — view standalone notes in the built-in Markdown Preview and export them to PDF. Neither workflow requires an Obsidian vault or an open workspace folder. The Mirrorstone CLI runtime is bundled; PDF export also requires Pandoc and Typst.
Status: v0.3.0 public pre-release candidate. The built-in-preview dialect, PDF export and optional vault index (wikilink completion, navigation, best-effort backlinks) are implemented. Full Obsidian compatibility and identical preview/PDF appearance are not claimed. Mirrorstone extends the built-in Markdown Preview and the Markdown source editor. Compatibility with VS Code's native visual Markdown Editor is still under investigation. This release provides no separate editor, and that development path is on hold. Install and receive updatesAfter this candidate is published to the Visual Studio Marketplace, open
Extensions in ordinary VS Code, search for The source repository remains private. Installing the public package does not require repository access. The package's documentation and license are included in the installation. Open a Markdown file and use Markdown: Open Preview ( PreviewVS Code's own Markdown preview ( Rendered: callouts in Obsidian's own The preview and PDF share many dialect rules, with known differences. The most
visible limitations: a heading or
|
| Value | Behavior |
|---|---|
auto |
On only when a workspace folder root contains a .obsidian directory — i.e. when you actually opened an Obsidian vault. |
on (default) |
OMD rendering with no vault or open folder required. This changes every Markdown preview in the window, including READMEs and agent instruction files. |
off |
Never. VS Code's stock preview, byte for byte. |
Only optional auto-detection needs a real filesystem; the default on also
works in virtual workspaces. Changing the setting
reloads the preview's plugins — if an already-open preview does not update, run
Markdown: Refresh Preview.
Frontmatter is left to VS Code, which shows it as a properties table
(markdown.preview.frontMatter). The exported PDF strips colon-bearing
frontmatter, so this is a deliberate, registered difference.
Three preview settings are re-applied by VS Code after every plugin and therefore
win over anything the extension asks for: markdown.preview.breaks,
markdown.preview.linkify and markdown.preview.typographer. Smart dashes,
ellipses and curly quotes are forced at rule level so PDF parity survives
typographer: false (the default), but breaks: true will add line breaks the
PDF does not have.
Vault features
Open a folder that is an Obsidian vault — one whose root contains .obsidian/ —
and [[wikilinks]] become live in VS Code's Markdown source editor through its
completion popup, Cmd+click and Find All References. Vault detection is only an
indexing boundary; standalone preview and export remain available outside a vault.
- Completion inside
[[…]]. Note names as you type;#switches to the target note's headings,#^to its block ids, and a|ends it because an alias is your prose, not a filename.![[embeds]]complete the same way. Accepting an item writes[[Note]]— including when VS Code has already auto-closed the brackets, where a naive implementation writes[[Note]]]]. - Cmd+click on
[[note]],[[note#heading]]or[[note#^block]]opens the target at that line. Go to Definition does the same from the keyboard, and hovering shows the target's path and first lines. - Backlinks through Find All References (
Shift+F12) on any note: best-effort results from indexed notes, with peek and grouping. An empty list is not proof that no notes link here; see the limits below. - A link to a note that does not exist is still a link — hovering offers Mirrorstone: Create Note from Link (command palette too), which creates an empty note at the vault root unless the link names a folder, then opens it. If either existence check finds a file, it opens that file instead. Creation is not exclusive: a concurrent write between the final check and creation can be overwritten, and stat errors currently count as absence. Use ordinary file creation when another program may be writing the same destination. Cmd+click on a missing note never mints files.
- Renaming a note rewrites nothing in this release. Update affected links manually; future link-update work requires separate write-safety verification.
Korean names work in both normalizations. macOS stores filenames decomposed
(NFD) while anything you type arrives composed (NFC); the two are different
strings to JavaScript, so an index that ignores this silently fails to resolve
[[한글 노트]] to the file sitting next to it. Every lookup here is normalized,
in both directions, and it is tested in both directions.
Index limits
- Results can be incomplete or stale. The index builds in the background; discovery failures and missed filesystem watcher events can leave gaps. Run Mirrorstone: Reindex Vault before relying on an empty backlink list. Failed walks produce a warning; reindexing cannot remove the limits below.
- Large notes have a scan ceiling. Notes over 1,048,576 bytes (1 MiB) are retained by name but contribute no links, headings or block IDs. The skipped note is named in Mirrorstone's output channel, with a warning for the first such note in the session.
- Frontmatter links are omitted.
[[links]]in YAML properties contribute no backlinks or completion. The preview's properties table is plain text. - Case and Unicode collisions lose index coverage. Files whose names differ only by case or Unicode normalization share an index key. The first indexed note is retained; the other is refused and reported. Mirrorstone does not rename files to normalize them. If both files exist, give them distinct names and update affected links manually.
- References can include extra links. Self-links are included, as are some links inside math or repeated footnote definitions that the preview does not display as links.
What it does not do
There is no graph view, Dataview, Templater or canvas support.
Attachments (images, PDFs) are not indexed, so
![[diagram.png]] still renders but is not offered by completion. Only ATX
headings (## Like this) are anchor targets. [[note#Outer#Inner]] matches the
last heading named rather than verifying the outline path. Mirrorstone does not
provide a custom visual editor; native visual-editor compatibility remains
unverified. The old custom-editor spike is unregistered because its resync
could discard typing.
Set mirrorstone.vault.enabled to false to switch the index and its file
watcher off entirely.
Export to PDF
Mirrorstone: Export to PDF — command palette, editor title bar, editor and tab
context menus, or the explorer context menu, including a multi-select that
becomes one batch run. Converts .md / .markdown / .mdown / .txt to
<stem>.pdf next to the source file.
- Extensions match case-sensitively, exactly as the CLI matches them: rename
NOTE.MDtonote.md. - Unsaved editors are saved first, because the CLI reads from disk. If a file cannot be saved you are asked whether to export the on-disk version, skip it, or cancel — and "export anyway" is reported as a warning, never a plain success.
- A failed re-render leaves the previous PDF untouched.
- What happened is read from the CLI's own log (
SUCCESS/FAILED/SKIPper file), so a batch where some files fail reports "N of M converted" and still offers the PDFs that landed. - Two exports of the same file never overlap; the second is declined while the first runs.
Also: Mirrorstone: Diagnostics (what was resolved, and where) and Mirrorstone: Recheck Export Dependencies (after installing something).
The export runs the CLI as a child process, which inherits the extension host's
environment plus Mirrorstone's own MIRRORSTONE_* variables — standard for an
extension that runs a tool on your behalf. Mirrorstone never sends telemetry.
Requirements
Export shells out to two external programs. Neither is bundled, and nothing
else is needed: the CLI runtime ships inside the extension, so there is no
install.sh step.
| Tool | Minimum | macOS | Debian / Ubuntu / Pop!_OS |
|---|---|---|---|
| pandoc | 3.9 | brew install pandoc |
upstream .deb from jgm/pandoc/releases — apt ships 3.1.x |
| Typst | 0.14 | brew install typst |
cargo install --locked typst-cli |
| Pretendard | — | optional; without it the PDF falls back to system fonts | same |
Older-than-minimum versions warn once and export anyway. A missing tool is the only thing that blocks, and then the install one-liner is on a button.
"It works in my terminal but not in VS Code." A VS Code launched from the Dock or a desktop launcher does not inherit your shell's
PATH. The extension searches the same prefix the CLI does (/opt/homebrew/bin,/usr/local/bin,~/.local/bin,~/.cargo/bin,/usr/bin,/bin) before falling back to the inheritedPATH. If your tools live elsewhere, setmirrorstone.pandocPath/mirrorstone.typstPathand run Mirrorstone: Diagnostics to see exactly what was searched.
Platforms: macOS and Linux. The CLI is bash + perl, so Windows is not supported natively — use a Remote — WSL window, where the export command is offered because that is where the CLI actually runs. On plain Windows the command explains this and does nothing. Export is disabled in Restricted Mode, since it runs external programs.
Settings
| Setting | Default | What |
|---|---|---|
mirrorstone.vault.enabled |
true |
Index the vault so [[wikilinks]] resolve. Off = no completion, no Cmd+click, no backlinks, no watcher. |
mirrorstone.preview.enabled |
on |
When the dialect applies to the built-in preview. See above — on affects every Markdown preview in the window. |
mirrorstone.cli.path |
"" |
Explicit CLI path. Machine-scoped; blocked in untrusted workspaces. |
mirrorstone.pandocPath |
"" |
Explicit pandoc. Machine-scoped; blocked in untrusted workspaces. |
mirrorstone.typstPath |
"" |
Explicit typst. Machine-scoped; blocked in untrusted workspaces. |
mirrorstone.font |
"" |
Primary document font for exported PDFs; empty means Pretendard. |
mirrorstone.useVaultRoot |
true |
Pass the file's workspace folder as the vault root so vault-relative images and embeds resolve. Security trade: it widens Typst's render root from the document's folder to the whole workspace folder, so a note can #image() anything under it. Turn it off for document-directory scoping. |
The three path settings are machine-scoped and listed in
capabilities.untrustedWorkspaces.restrictedConfigurations: a workspace's
.vscode/settings.json must never be able to point the extension at a script of
its choosing.
Package notices
- Third-party notices:
THIRD-PARTY-NOTICES.md, shipped in this package. - License: MIT (
LICENSE).