# Vim Prose Flow — Specification A Neovim plugin that visualizes the rhythm of prose by coloring each sentence according to its length. This document specifies *what* the plugin does, not *how* it does it. An implementer should be able to build a working plugin from this document alone. ## 1. Purpose Good prose varies its sentence length. A paragraph of uniformly-sized sentences is monotonous and tiring to read. The plugin makes this visible: every sentence in the buffer receives a background color on a continuous scale from "short" to "long". A paragraph that renders as a flat blob of one color is a warning; a paragraph that renders as a varied gradient reads well. Target users are writers editing prose (Markdown, plain text) in Neovim. The plugin is a writing aid, not a linter — it never modifies the buffer, never reports errors, and never blocks. ## 2. Core principles These four constraints drive every other requirement. 1. **Sentences span lines.** Prose is hard-wrapped. A sentence is a unit of the text, not of the line. Highlighting must flow across line breaks, and a sentence's length is measured over its whole extent regardless of wrapping. 2. **Only prose counts.** Markup, metadata, and machine-readable text are not part of the writing being measured. They are neither counted nor colored. 3. **One marker per sentence.** When numeric lengths are shown, each sentence contributes exactly one number, never one per line it occupies. 4. **Never disturb the text.** All output is decoration. Buffer content, cursor position, and line layout are untouched. ## 3. Behavior ### 3.1 Activation - The plugin is **off by default** in every buffer. - Enablement is **per buffer**. Turning it on in one buffer must not affect any other buffer. - The user toggles it with a command. When toggled, the current state ("on" or "off") is reported to the user. - Turning it off removes all decoration from that buffer immediately. - The plugin does not restrict itself by filetype; the user decides where it applies. ### 3.2 Live updating - While enabled, the visualization tracks the buffer: it refreshes after text changes (in both normal and insert mode), when the buffer is displayed in a window, and when window dimensions change. - Updates are **debounced** so that continuous typing does not trigger a refresh per keystroke. The debounce delay is configurable. - Refreshes must be cheap enough to be imperceptible while typing on documents of ordinary essay or article length. - A refresh fully replaces the previous decoration; no stale highlights may survive an edit. ### 3.3 Sentence detection - A sentence runs up to and including a terminating `.`, `!`, or `?`, plus any trailing whitespace. Text at the end of the buffer with no terminator is still treated as a sentence. - Leading whitespace is not part of the sentence; the highlight begins at the first visible character. - Sentence **length** is the character count of the sentence's prose content, with runs of whitespace (including the newlines from hard wrapping) each counting as a single space. Non-prose content contributes nothing. - Fragments shorter than a few characters are ignored entirely — they are punctuation noise, not sentences. ### 3.4 What is not prose The following are excluded from both length measurement and coloring. They appear in the buffer with their normal appearance, unhighlighted, and a sentence whose highlight passes over them is drawn as separate colored segments around them. | Construct | Treatment | |---|---| | YAML front matter (a `---` delimited block at the very start of the file) | Entirely excluded | | HTML/XML-style tags (`<...>`) | Excluded | | Markdown images (`![alt](url)`) | Entirely excluded, including alt text | | Bare URLs (`http://…`, `https://…`) | Excluded | | Markdown links (`[text](url)`) | Link **text** counts as prose; the brackets, parentheses, and URL do not | Excluded content also must not terminate or split a sentence: a `.` inside a URL or an HTML tag is not a sentence boundary. ### 3.5 Coloring - Each sentence gets a background color and a foreground color chosen by interpolating between a "short" pair and a "long" pair of colors. - The position on that scale is the sentence's length mapped onto a configurable range: at or below the minimum length the sentence is fully "short"-colored, at or above the maximum it is fully "long"-colored, and in between it varies smoothly. Lengths outside the range are clamped, not extrapolated. - The scale is **quantized** to a bounded number of steps so that a document produces a bounded set of distinct colors rather than one per sentence. This keeps the visual vocabulary readable and the editor's highlight table small. - Colors are true-color hex values; the plugin assumes a terminal or GUI with 24-bit color. ### 3.6 Length statistics - Optionally, each sentence's numeric length is displayed as virtual text in the right margin, in the same colors as the sentence it describes. - All markers align to a single column, placed a small fixed gap to the right of the buffer's longest line, so they form a readable column rather than a ragged edge. - Exactly one marker is drawn per sentence, on the first line the sentence occupies that has visible prose on it and is not already occupied by another sentence's marker. If a sentence has no free line, it gets no marker. - Markers overlay the space to the right of the text; they never shift, wrap, or reflow the buffer's content. - The statistics display has its own toggle, independent of the main on/off toggle. It is a **global** setting: toggling it updates every buffer where the plugin is currently enabled, and reports the new state to the user. ## 4. User interface Two commands, both taking no arguments and both carrying a short description for Neovim's command completion: | Command | Effect | |---|---| | `:ProseFlowToggle` | Enable/disable the visualization in the current buffer | | `:ProseFlowToggleStats` | Show/hide the numeric length markers, globally | The plugin defines no key mappings. Users bind the commands themselves. ## 5. Configuration The plugin exposes a `setup` function following the conventional Neovim plugin idiom: it accepts an optional table of options, merges it over the defaults, and installs the live-update machinery. Calling it with no arguments must yield a fully working plugin. | Option | Meaning | Default | |---|---|---| | `min_length` | Length at or below which a sentence is fully "short" | `20` | | `max_length` | Length at or above which a sentence is fully "long" | `150` | | `low_color` | Background for the shortest sentences | `#2e3440` | | `high_color` | Background for the longest sentences | `#81a1c1` | | `low_fg` | Foreground for the shortest sentences | `#616e88` | | `high_fg` | Foreground for the longest sentences | `#eceff4` | | `debounce_ms` | Delay before refreshing after a change | `150` | | `show_stats` | Whether length markers are shown initially | `true` | The default palette is a Nord-family dark scheme: short sentences recede into the background, long sentences stand out brightly. ## 6. Structure and packaging - A standard Neovim plugin layout: a runtime file that registers the user commands on startup, and a module that holds all logic and is only loaded when a command is first used or `setup` is called. - The plugin must be installable by any common Neovim plugin manager with no build step and no dependencies beyond Neovim itself. - Registering the commands must be cheap; no scanning or highlighting happens until a buffer is explicitly enabled. ## 7. Non-goals - Grammar, spelling, or style checking. - Word-, syllable-, or readability-score metrics. - Any modification of buffer contents. - Support for terminals without true color. - Persisting enablement across sessions. ## 8. Acceptance criteria An implementation is correct if, on a Markdown file with YAML front matter, hard-wrapped paragraphs, links, images, and URLs: 1. Nothing is highlighted until `:ProseFlowToggle` is run. 2. After toggling, every prose sentence is colored, short sentences visibly darker than long ones, and the coloring continues across line breaks within a sentence. 3. Front matter, image markup, URLs, HTML tags, and the non-text parts of links are left uncolored; link text is colored with its sentence. 4. A `.` inside a URL does not split a sentence. 5. Each sentence shows exactly one right-margin number, all numbers sharing one column past the longest line, and the buffer text is not shifted. 6. Typing continuously does not stutter; the display catches up shortly after typing pauses, and reflects the edited text. 7. `:ProseFlowToggleStats` hides and restores the numbers in all enabled buffers without affecting the coloring. 8. Toggling off leaves the buffer visually identical to before it was ever toggled on.