Hard Wrap

pending

by alethiophile

Transparently hard-wrap markdown files on disk while displaying them as soft-wrapped paragraphs in the editor.

Updated 12d agoMITDiscovered via Obsidian Unofficial Plugins
View on GitHub

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:

SettingDefaultDescription
Enable hard-wrappingonMaster toggle
Column width80Wrap target (40–200)

Per-file override

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.

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:

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

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.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.