alethiophile30 downloadsTransparently hard-wrap markdown files on disk while displaying them as soft-wrapped paragraphs in the editor.
Transparently hard-wrap markdown files on disk while displaying them as soft-wrapped paragraphs in the editor.
Files are always stored with hard line breaks at a configurable column width (default 80), making them pleasant to read in a terminal, emacs, git diffs, etc. Obsidian's editor sees the unwrapped form — one long line per paragraph — and handles visual wrapping natively.
In Settings > Hard Wrap:
| Setting | Default | Description |
|---|---|---|
| Enable hard-wrapping | on | Master toggle |
| Column width | 80 | Wrap target (40–200) |
Add a hard-wrap key to a file's frontmatter:
---
hard-wrap: 60 # wrap at 60 columns
---
---
hard-wrap: false # disable for this file
---
---
hard-wrap: true # enable with global default columns
---
Omitting the key uses the global default.
The plugin monkey-patches three methods on MarkdownView.prototype
using monkey-around:
setViewData): hard-wrapped paragraphs are joined into
single lines before the content reaches the editor.getViewData): the editor's single-line paragraphs are
re-wrapped at the configured column width before being written to disk.setMode): when switching back to source mode,
this.data is re-unwrapped. Obsidian's setMode bypasses
setViewData and feeds this.data directly to the editor, so
without this patch the editor would show hard-wrapped lines after
toggling between source and reading mode.Both auto-save (the debounced requestSave) and manual save (Ctrl+S, close) go
through getViewData, so all save paths produce hard-wrapped output. Patching
getViewData rather than save is important because Obsidian also calls
getViewData outside the save path. When a file is modified externally while
the editor has unsaved changes, Obsidian performs a 3-way merge between
lastSavedData, the current getViewData() return value, and the new disk
content. Since lastSavedData and the disk content are both in hard-wrapped
form, getViewData() must return wrapped text too — otherwise the merge would
treat every re-wrapped line break as a conflicting change. Obsidian also uses
getViewData() for change detection (comparing against lastSavedData to
decide whether a save is needed), which likewise requires consistent
representations.
On plugin unload, all open markdown files are force-saved so they remain hard-wrapped on disk.
The transform follows CommonMark soft-line-break semantics. Anything a markdown renderer would join into a single paragraph gets wrapped/unwrapped:
Everything else passes through verbatim:
--- fences)[!type] lines)Within wrappable text, inline elements are never broken across lines:
[text](url) and [text][ref]`code`Hard line breaks (trailing \ or two trailing spaces) are preserved.
Requires Node.js >= 18.
npm install
npm run build # production build → main.js
npm run dev # watch mode (rebuilds on change)
npm test # run unit tests
The build uses esbuild. Output is a single main.js (CommonJS). The
obsidian package and @codemirror/* are externals (provided by
Obsidian at runtime). monkey-around is bundled.
This plugin and its documentation were substantially created using AI. They have been tested and reviewed by the human author.