Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>LoomMarkNew to Visual Studio Code? Get it now.
LoomMark

LoomMark

llingshu

|
9 installs
| (0) | Free
A source-backed, live-rendering WYSIWYG Markdown editor for VS Code.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

LoomMark

CI Visual Studio Marketplace License: MIT

LoomMark is a source-preserving Markdown editor for Visual Studio Code. It combines a continuous CodeMirror editing surface with progressive rich rendering while keeping the exact Markdown text as the only source of truth.

LoomMark does not parse a document into a rich-text model and serialize the whole document back to Markdown. Visual features are CodeMirror decorations layered over the original text. Opening a file must not format, normalize, escape, or otherwise rewrite it.

LoomMark rendering rich Markdown, annotation cards, and the Markdown outline in VS Code

Highlights

  • Continuous editing with normal cursor movement, selection, undo, IME composition, and fast input.
  • Progressive rendering for headings, emphasis, strong text, strikethrough, links, and inline code.
  • Card mode: each heading's section is visually set apart — a soft tint, a colored accent bar, or a nested bordered card, so it is obvious which heading you're under. Cycle styles from the editor title bar; colors are customizable.
  • Optional background imagery: a fixed, rotating, or per-document image behind the editor, and a separate stable image per heading section in tint/card mode.
  • Fenced code blocks with language-aware highlighting, line numbers, copy controls, and language selection.
  • GFM tables that render as real tables, editable in place or as expand-to-source widgets, in a bordered grid or a booktabs-style three-line layout.
  • Inline and block image preview, including images in a sibling folder or wrapped in <...>.
  • Paste an image from the clipboard to save it and insert a Markdown image link, honoring VS Code's own markdown.copyFiles.destination setting.
  • Inline and display math rendered with KaTeX.
  • Clickable task-list checkboxes, styled nested bullets, blockquotes, and horizontal rules.
  • Tab/Shift+Tab indent and outdent lines, turning a list item into a nested sub-list. Nested ordered lists renumber automatically (1, a, i cycling by default).
  • Outline connector lines between nested list items, with the cursor's ancestor chain lit up in color.
  • #tag chips that stay part of the editable text.
  • Backslash escapes (\*, \#, \!, ...) turn off Markdown syntax for a single character.
  • Find and replace inside the editor (Ctrl/Cmd+F), styled like VS Code's native find widget.
  • Obsidian-style [[wiki links]] and [[target|label]] without conversion to standard links.
  • Workspace-wide file completion inside [[...]] — any file, not just Markdown.
  • Ctrl/Cmd + click navigation for relative Markdown links and wiki links.
  • An in-editor outline drawer that opens from a floating control, plus a native Explorer outline, both generated from source text.
  • Minimal VS Code WorkspaceEdit synchronization with stale-update protection.
  • Cursor position is remembered and restored when reopening a document.
  • Reading position is remembered too: returning to a recreated editor restores the same visible document location, including after layout-affecting rich rendering has settled, and restores editor focus so typing can continue immediately.
  • Built-in diagnostics command for inspecting the Webview, synchronization state, links, completion, and code-block decorations.

Gallery

Nested Card Mode

Heading sections can remain plain, use colored accent rails, or form nested Cards while preserving the exact Markdown source beneath the editor surface.

LoomMark with Card Mode off LoomMark Card Mode with colored accent rails

LoomMark Card Mode with nested tinted sections LoomMark Card Mode with nested bordered sections

Lists And Math

Nested list guides remain visible across continuation paragraphs, ordered lists can cycle their labels, and inline or display mathematics renders in place.

LoomMark nested list guides and display mathematics

Changelog

0.5.4

  • Standalone images now default to a centered, framed figure with their [] alt text as a caption. The optional title accepts "no-center" and "no-frame" to opt out.
  • Annotation blocks now anchor their stripe and numbered badge to standalone rendered images, matching the existing table, code-block, and display-math behavior.
  • Added an image gallery covering rich rendering and Card mode variants.

0.5.3

  • Restored editors retain both the cursor and the reading position. LoomMark saves a scroll position plus a visible-line anchor, so switching back to a document or recreating its Webview returns to the same place even when cards, images, or wrapping have changed its height.

0.5.2

  • Fixed restored LoomMark tabs after an Extension Host restart. A tab that VS Code retained without a live custom-editor provider is now closed and recreated automatically, establishing a new TextDocument synchronization channel before the editor accepts ongoing work.
  • Fixed fenced code blocks containing Markdown-looking headings. Their contents now remain code: they do not create heading styling, heading Cards, or outline entries.

0.5.1

  • Fixed a critical data-loss risk after an Extension Host restart. Existing Markdown tabs now recreate their Webview instead of retaining a detached in-memory editor; edits reconnect to the authoritative VS Code TextDocument before they can appear saved.
  • Added explicit persistence protection: a rejected document write or an unacknowledged sync now blocks further editing, reports the failure, and offers recovery-text copying rather than silently accepting edits that have not reached disk.
  • Fixed Open Source Editor to resolve the current custom-editor tab instead of a stale tab remembered before the Extension Host restart.

0.5.0

  • Extracted the reusable CodeMirror Markdown editor kernel into the open-source @llingshu/loommark-core package. LoomMark now concentrates on VS Code integration while other hosts can use the same source-preserving editor behavior without copying the implementation.
  • Added experimental annotation blocks: source-backed <<<[N] and >>>[N] notes attach to the preceding document line or block, retain persistent IDs and optional fixed colors, and render as responsive left/right margin cards.
  • Improved dense annotation handling: notes sharing a target merge into one card, display a compact target badge, collapse based on per-side visible density, and reveal a minimal connector only for the active note.
  • Added source as an ordered-list label mode, alongside the default cycle and hierarchical decimal modes.

Source Fidelity

The TextDocument owned by VS Code is the persistence authority. LoomMark follows these rules:

  1. Opening a document never changes it.
  2. Decorations change presentation, not text.
  3. Unsupported syntax remains original source text.
  4. Only direct user input or an explicit command may create a document edit.
  5. Host acknowledgements cannot overwrite newer local typing.
  6. Formatting, if introduced, must be explicit, previewable, and undoable.

This matters for Markdown dialects, repository conventions, external generators, note-taking workflows, embedded HTML, deliberate escapes, and tools that depend on exact source layout.

Installation

Install LoomMark from the Visual Studio Marketplace, or install a packaged VSIX:

code --install-extension artifacts/loommark.vsix

Markdown files open with LoomMark by default. Use Reopen Editor With... to select another editor for an individual document, or disable loommark.openByDefault.

Commands

Command Purpose
LoomMark: Open Source Editor Reopen the current document in VS Code's default text editor.
LoomMark: Focus Markdown Outline Open Explorer and focus LoomMark's native outline.
LoomMark: Copy Editor Diagnostics Copy structured runtime diagnostics for bug reports.
LoomMark: Toggle Heading Card Mode Cycle loommark.cardMode through off/tint/accent/card. Also in the editor title bar.

Settings

Setting Default Description
loommark.openByDefault true Associate *.md and *.markdown with LoomMark at user scope.
loommark.theme vscode Select vscode, crepe, frame, or nord.
loommark.table rich Edit table cells in place (rich) or expand to Markdown source on cursor entry (source).
loommark.tableStyle grid Render tables as a bordered grid or a booktabs-style three-line ruled table.
loommark.orderedListStyle cycle Cycle arabic/letters/roman numbering (cycle), or show the source marker (source) or hierarchical decimal numbering.
loommark.listGuides true Show connector lines between nested list items and their continuation content.
loommark.cardMode card Heading section visualization: off, tint, accent, or card. Also cycled from the editor title bar.
loommark.cardBackgroundColors six-hue palette Colors to cycle per heading level for the Card background fill. Empty draws no background.
loommark.cardBorderColors six-hue palette Colors to cycle per heading level for Card/accent borders and rails. Empty draws no border.
loommark.cardBackgroundStrength 0.06 Amount of heading accent mixed into the translucent Card surface.
loommark.cardBorderStrength 0.52 Accent strength in Card borders; lower values are quieter and more neutral.
loommark.cardImage.enabled false Draw an independently selected, stable image inside each heading section (card and tint modes only).
loommark.cardImage.path "" Card image file or directory; empty reuses loommark.background.path.
loommark.cardImage.opacity 0.72 Card image opacity from 0 to 1.
loommark.cardImage.blur 4 Card image blur radius in pixels.
loommark.cardImage.saturation 0.75 Card image color saturation multiplier.
loommark.cardImage.overlay 0.18 Editor-theme overlay above each Card image.
loommark.background.enabled false Enable an optional image behind the editor.
loommark.background.path "" Image file or directory. This machine-specific path is not synchronized to other devices.
loommark.background.selection daily Directory selection mode: fixed, onOpen, daily, or perDocument.
loommark.background.opacity 0.72 Background image opacity from 0 to 1.
loommark.background.blur 14 Background blur radius in pixels.
loommark.background.saturation 0.7 Background color saturation multiplier.
loommark.background.overlay 0.42 Editor-theme overlay opacity; higher values improve readability.
loommark.keyboardEditing false Let the cursor enter rendered images, tables, and math with the keyboard. When off, they are edited on click.
loommark.outline both Show the outline in both, editor, explorer, or turn it off.
loommark.syncDelay 180 Debounce duration in milliseconds before syncing local typing to VS Code.

Wiki Links

LoomMark preserves and renders both common wiki-link forms:

[[notes/project]]
[[notes/project|Project notes]]

Typing [[ opens completion for any file in the workspace, not just Markdown — scripts, configs, images, and so on. Candidates are relative to the current document; Markdown files omit their .md/.markdown extension (Obsidian-style), other files keep theirs since it identifies the file type. The list refreshes when files are created, deleted, or renamed. Ctrl/Cmd + click opens a target; an extensionless wiki link resolves as .md, one with an extension opens as-is.

Code Blocks

Fenced code blocks use the language following the opening fence:

```typescript
const message: string = "hello";
```

The code-block UI is a view layer. Copy does not include fences. Changing the language selector is an explicit, undoable edit to the opening fence only. Terminal-like languages receive restrained window chrome; other languages use neutral controls.

Card Mode

loommark.cardMode visually sets apart each heading's section — the heading itself plus everything under it, up to the next heading of the same or shallower level. A sub-heading's section nests inside its parent's, so a deeply nested section reads as several levels layered inside each other, one per ancestor heading. This is presentation only and never touches the document text, the same as every other progressive-rendering feature. Cycle through the four modes with the $(layers) button in the editor title bar, or LoomMark: Toggle Heading Card Mode:

  • off — no heading visualization.
  • tint — a soft background color wash per section, no borders. The lightest-touch option.
  • accent — a colored left border bar per heading level, plus a faint background tint.
  • card (default) — each section wrapped in a bordered, rounded card. Content (paragraphs, code blocks, blockquotes) is padded inward from the card's own border rather than sitting flush against it, so nested code blocks and quotes keep their own visual boundary.

Background fill and border/rail color are controlled independently: loommark.cardBackgroundColors and loommark.cardBorderColors each cycle their own list of colors per heading level, defaulting to the same built-in six-hue palette. Each hue is treated as an accent token: Card surfaces mix only a small amount into a translucent editor-colored base, while borders use a muted accent/foreground mixture — loommark.cardBackgroundStrength and loommark.cardBorderStrength tune how strongly each shows. Set either color list to an empty array to draw no color at all for that layer (a border-only or background-only Card, or neither, while keeping the same content inset and geometry) rather than falling back to the default palette.

Nested cards use dedicated boundary widgets for their rounded top and bottom edges, while line decorations draw the continuous side rails and background layers between those boundaries. Cards that close together receive progressively offset bottom spacing, so each heading level keeps a distinct rounded edge without rewriting the Markdown source.

Image Backgrounds

LoomMark can select a fixed or rotating image without modifying the Markdown document. Set loommark.background.path to an image or a directory and enable loommark.background.enabled. Windows paths may use C:\\Users\\name\\Pictures\\loommark; network shares may use //server/share/folder or an escaped UNC path such as \\\\server\\share\\folder in JSON. The extension host resolves the selected local file through VS Code's Webview resource API. A separate theme-colored overlay keeps text legible independently of image blur, saturation, and opacity.

loommark.background.path (and loommark.cardImage.path) also accept an https:// URL, loaded directly by the Webview instead of being resolved as a local file — useful for a shared team image without distributing it alongside the workspace. A URL is always a single fixed image; loommark.background.selection's directory-rotation modes only apply to a local directory.

Card images are configured separately with loommark.cardImage.* and work in both the card and tint loommark.cardMode styles (not accent or off). Each heading section chooses a stable image from the directory using the document and heading identity, so editing or reopening the document does not reshuffle its appearance. Leave loommark.cardImage.path empty to reuse the global background directory. Card images use a dedicated layer below editor content and are clipped to the same inset geometry as the active mode's own borders/bands rather than being repeated on individual editor lines.

Rendered Widgets

Tables, images, and math render as widgets rather than plain decorated text. By default the text cursor skips over them and they are edited by clicking into them; enable loommark.keyboardEditing to let the cursor step in with the keyboard instead, for mouse-free editing.

Tables

GFM tables render as real <table> elements. loommark.table controls how cells are edited:

  • rich (default) — click a cell to edit its raw Markdown in place. Enter or clicking elsewhere commits the change, Escape cancels. Arrow keys move to the adjacent cell (Up/Down always; Left/Right once the caret reaches that side of the current cell's text), and Tab/Shift+Tab does the same in reading order, wrapping to the next/previous row. Tab past the last cell of the last row adds a new row. Alt+Shift+Up/Down/Left/Right inserts an empty row above/below or column left/right of the current cell. Inline and display math ($...$, $$...$$) inside a cell renders with KaTeX like it does everywhere else.
  • source — the whole table expands to Markdown source when the cursor enters it, matching the edit style used for headings and emphasis.

loommark.tableStyle switches the visual style between a bordered grid (default) and a booktabs-style three-line ruled table (heavy top/bottom rules, a header rule, light row separators, no vertical lines) — useful for reference tables like a CLI command list.

Images

![alt](https://github.com/llingshu/vscode-loommark/raw/HEAD/path) renders inline or, on its own line, as a centered block image in an independent bordered container. Add presentation tokens in the optional title only to opt out: ![alt](https://github.com/llingshu/vscode-loommark/raw/HEAD/path "no-center") keeps the block aligned to the source column, ![alt](https://github.com/llingshu/vscode-loommark/raw/HEAD/path "no-frame") removes its container, and "no-center no-frame" disables both. Other title text remains accepted and has no visual effect. Relative paths resolve against the document's own directory and may climb into a sibling folder (../assets/x.png) as long as the target stays inside the document's workspace folder; documents opened outside any workspace can only reach their own directory. Paths with spaces or special characters can be wrapped in angle brackets: ![alt](https://github.com/llingshu/vscode-loommark/raw/HEAD/<../My Assets/figure 1.png>). Remote http(s): and data: sources are used as-is. An image that fails to load shows a placeholder instead of breaking the line. Click an image to edit its Markdown source; Ctrl/Cmd + click opens it instead, whether the image is rendered or shown as source. The raw source gets a highlighted background and a link-colored destination while the cursor is inside it, so it stays easy to find.

Pasting an image from the clipboard saves it to disk and inserts a Markdown image link at the cursor. The destination folder reuses VS Code's own markdown.copyFiles.destination setting (the same one the built-in Markdown editor uses for dropped/pasted files), so existing configuration like:

"markdown.copyFiles.destination": { "**/*": "assets/" }

applies unchanged — no separate LoomMark setting to configure. With nothing configured, the image saves next to the document. The file is named image.png (or the appropriate extension for the clipboard's image type), with -1, -2, ... appended if that name is already taken.

Math

Math is rendered locally with KaTeX — no network request is made. Inline math uses $...$ and display math uses $$...$$ (including multi-line blocks). Currency-like text ($5, $10) is left as plain text. Invalid LaTeX shows KaTeX's inline error instead of breaking the editor.

Lists

Tab and Shift+Tab indent/outdent the current line (or every selected line) by 4 spaces, which is how a list item becomes a nested sub-list — nesting is purely indentation, the same as CommonMark. Four spaces, not two: CommonMark only recognizes a nested item once its content reaches its parent's own content column, which for an ordered marker like 1. is 3+ characters (more for 10. , 100. , and so on); 2 spaces satisfies bullet markers but not ordered ones, and a too-shallow nested ordered item is parsed as a continuation paragraph of its parent instead of a real sub-list — which also breaks Enter part way through typing it, since there is no list for Enter to continue. Use Tab rather than typing spaces by hand to always get a valid amount. Shift+Enter at the end of a list item's own marker line indents the new line to match, for the same reason — pressing it again from that new (now correctly indented) line just continues matching its indent, without adding another level.

Nested ordered lists use cycle by default (arabic, then letters, then lowercase roman numerals per level — 1, a, i — repeating every three levels). loommark.orderedListStyle can instead choose source to show the Markdown marker exactly as written, or decimal (1, 2, 2.1, 2.2, 2.2.1). Click a label to edit its source number.

loommark.listGuides (default on) draws a connector line between a list item, its nested children, and any indented continuation content underneath it (a paragraph, blockquote, or code block). Guides stay visible at all times, including on the line the cursor is on — there is no raw source to reveal, only blank space, so there is nothing to switch to. Guides are gray by default; the cursor's own line, each of its direct ancestor items' lines, and — if the cursor is on one of several lines belonging to the same multi-line item's own paragraph (for example after a Shift+Enter soft line break) — the whole run of those lines together, all light up in color, one per level, while sibling branches and unrelated content stay gray even when they happen to share part of the same connector.

Tags

A standalone #word or #nested/tag renders as a pill. The # stays part of the editable text since it carries meaning, unlike heading or emphasis markers. Heading markers (# Heading), hashes in the middle of a word (foo#bar), and numeric references (#123) are not treated as tags.

Annotations (experimental)

A block opened by <<<[N]color and closed by a bare <<< attaches a left-margin note to the line (or table/code/math block) directly above it; >>> attaches a right-margin note the same way:

this line gets a note
<<<[1]7c3aed
the note's content — can span multiple lines
<<<

Typing the 3rd < (or >) of a delimiter line on an otherwise-empty line auto-closes the block immediately, the same way a code fence or $$ block auto-closes — a bare, unclosed opener can't run on and silently swallow every real paragraph up to the next <<</>>> line later in the document. Ctrl/Cmd+Shift+Left/Ctrl/Cmd+Shift+Right wrap the line the cursor is on in a fresh left-/right-margin note and focus it directly, without needing to type the fence by hand — the arrow you press is the side you get; Ctrl/Cmd+Shift+A toggles whether annotations render at all.

[N] is the note's persistent identity and the optional 6 hex digits after it are its fixed color. Every note created through the extension writes both automatically, using the current document's largest ID plus one and the next palette color. IDs are never renumbered when another note is moved or deleted. Older bare (<<<) and color-only (<<<7c3aed) blocks remain valid for compatibility.

The block never renders as part of the document — no other Markdown construct (list numbering, guide rails, heading sections, ...) sees it as present at all. The attached line (or block) gets a thin color-coded accent stripe and numbered badge — real overlays, not a background painted onto the line itself, so it can never collide with (and erase) that line's own heading-Card background or border the way an earlier version of this feature briefly did. The stripe is also the note's hover/click target: there is no separate marker icon duplicating it. Each note is its own clean card tinted in its own color, pinned flush to the true left/right edge of the editor, outside the editing area entirely and unaffected by heading Card mode's own indentation, so notes on deeply nested headings still line up with everything else on the same side — whenever there's enough room for it there; otherwise it collapses to just the stripe, revealed on hover (or forced open regardless of available room via the right-click "Pin" below). A note can be individually collapsed to a one-line preview or edited directly; its height follows short content and scrolls once long content reaches the viewport limit. Its rendered text supports normal pointer selection and copying. Double-click the text, or focus it and press Enter, to edit with the standard select-all, copy, paste, undo, and redo shortcuts. Delete, add another note at the same attachment point, and pin/unpin live in the visible ellipsis menu (and remain available by right-click). Stacking several blocks back to back (either side, or via "Add note") all attach to the same original line rather than to each other. Notes sharing that target and side render in one compact card, with the first persistent ID shown on the target badge and the remaining notes available inside the card. A block whose target line belongs to a table/fenced-code/display-math construct or a list item's own shift+enter continuation attaches to the whole thing, not just one line of it.

A note only gets a connector — a short, color-matched line back to its stripe — while it is hovered, focused, or click-locked and it actually needed to be displaced from its natural row; several notes sharing one target inevitably need this (they can't all sit at an identical position), but a lone note usually won't draw one at all. The connector's rail sits near the note's own stripe position (near the text) rather than the card's edge, since the card's position can shift (resizing, pinning) in ways the stripe never does, clearing the stripe's own footprint first so it never draws over the color block it's pointing away from. Several connectors that are in flight at the same time — whether several notes on one target, or several separate single-note targets crowded together — share a bundle of lanes reassigned per height range (the same interval-scheduling a calendar view uses to lay out overlapping events side by side, applied per slice rather than once for a connector's whole length): each one only steps out to a further lane for as long as a competitor is actually active alongside it, and shifts back inward the moment that competitor peels off toward its own card. Together they read as a bundle of nested arcs — thickest near the text, thinning out one branch at a time as each connector reaches its card — rather than a set of permanently separate parallel lines. Notes anchored close together on the same side pack in document order, and the active grouped card expands in place while dense areas collapse to their target badges.

This is still a first pass:

  • The color-coded stripe doesn't currently reach a table target — tables replace their whole source with one opaque widget with no per-line element left to attach an overlay to (a fenced-code block still has one, so those get a stripe normally). The note itself still attaches and works normally on a table target, just without the connecting stripe, and — since there's nothing to hover — only shows when pinned (automatically or via the right-click menu), not via hover-reveal.
  • A bare (untagged) annotation's color is still assigned by its position among all other untagged annotations in the document, so adding or removing one earlier in the document can shift the colors of unrelated ones after it — tag it (see above) to pin its color permanently instead.

Search And Replace

Ctrl/Cmd+F opens a find panel inside the editor with case-sensitive, regular-expression, and whole-word toggles, plus replace and replace-all. Escape closes it.

Escaping

A backslash before CommonMark's escapable punctuation (!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~) hides the backslash and renders the character as plain text instead of live Markdown syntax: \*not bold\*, \#not a tag, \![not an image](https://github.com/llingshu/vscode-loommark/raw/HEAD/x.png). Escapes are ignored inside code, matching CommonMark.

Privacy

LoomMark processes documents locally inside VS Code. It includes no analytics, advertising, telemetry, remote document service, or background upload. External links open only after an explicit user action.

Development

Requirements: Node.js 20 or later and VS Code 1.95 or later.

npm ci
npm run check
npm run build

Press F5 to launch an Extension Development Host. Use npm run watch while editing. Package the same artifact used for both registries with:

npm run package

Detailed documentation:

  • Architecture
  • Editor technology
  • Development and debugging
  • Publishing
  • Contributing

Limitations

  • Host synchronization currently applies the smallest single contiguous replacement. Concurrent overlapping edits from another editor are not rebased.
  • Progressive rendering intentionally covers a defined Markdown subset. Unimplemented syntax stays visible and editable rather than being discarded.
  • Only one LoomMark custom editor is supported per text document.
  • The complete language-data bundle increases the Webview bundle size; language splitting remains future optimization work.

Acknowledgements

LoomMark is built with CodeMirror 6, Lezer, mdast, KaTeX, and the Visual Studio Code Extension API. See THIRD_PARTY_NOTICES.md and THIRD_PARTY_LICENSES.txt for dependency license information.

Citation And License

Use CITATION.cff to cite LoomMark. The project is released under the MIT License.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft