# Hard Line Wrap for Obsidian 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. ## Configuration ### Global settings In Settings > Hard Wrap: | Setting | Default | Description | |----------------------|---------|----------------------| | Enable hard-wrapping | on | Master toggle | | Column width | 80 | Wrap target (40–200) | ### Per-file override Add a `hard-wrap` key to a file's frontmatter: ```yaml --- hard-wrap: 60 # wrap at 60 columns --- ``` ```yaml --- hard-wrap: false # disable for this file --- ``` ```yaml --- hard-wrap: true # enable with global default columns --- ``` Omitting the key uses the global default. ## Commands - **Reformat current file with hard wraps** — normalizes the editor content by running unwrap → wrap → unwrap. This cleans up hard breaks that the plugin didn't insert — e.g. text pasted from an external source, or soft wraps added manually — without requiring a save/reload cycle. Also useful after changing the column width or after external edits. ## How it works The plugin monkey-patches three methods on `MarkdownView.prototype` using [monkey-around](https://github.com/pjeby/monkey-around): - **On load** (`setViewData`): hard-wrapped paragraphs are joined into single lines before the content reaches the editor. - **On save** (`getViewData`): the editor's single-line paragraphs are re-wrapped at the configured column width before being written to disk. - **On mode switch** (`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. ## What gets wrapped The transform follows CommonMark soft-line-break semantics. Anything a markdown renderer would join into a single paragraph gets wrapped/unwrapped: - Plain paragraphs - Blockquote content (including nested blockquotes) - List item content (unordered, ordered, nested) - Footnote definitions - Callout body text Everything else passes through verbatim: - Frontmatter (YAML between `---` fences) - Fenced code blocks (backtick and tilde) - Indented code blocks - ATX and setext headings - Tables - Thematic breaks - HTML blocks - Callout headers (`[!type]` lines) Within wrappable text, inline elements are never broken across lines: - Links: `[text](url)` and `[text][ref]` - Images: `![alt](url)` - Inline code: `` `code` `` Hard line breaks (trailing `\` or two trailing spaces) are preserved. ## Build Requires Node.js >= 18. ```sh 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. ## Known limitations - **Search**: Obsidian indexes on-disk (hard-wrapped) content. A search for a phrase that spans a wrap boundary won't match. ## Note on code authorship This plugin and its documentation were substantially created using AI. They have been tested and reviewed by the human author.