markdown-review

Core ↔ IDE contract

The IDE layer (pl.mdreview.ide) uses the diff engine and HTML generator only through the pl.mdreview.api.MarkdownDiffRenderer interface (MarkdownDiffApi.kt). The pl.mdreview.api package does not depend on the IntelliJ Platform.

What the core provides

  1. A class implementing MarkdownDiffRenderer (no-arg constructor) that combines the engine (M1) and the HTML generator (M2); currently pl.mdreview.render.CoreMarkdownDiffRenderer.
  2. The file src/main/resources/META-INF/services/pl.mdreview.api.MarkdownDiffRenderer with that class’s fully qualified name. Without it the IDE uses FallbackRenderer (raw text, paragraph diff), so the plugin still runs.

Input

RenderRequest(left, right, mode, options, leftBaseUri, rightBaseUri): texts with \n line separators, null = the file does not exist; mode = SIDE_BY_SIDE or MERGED; options.ignoreWhitespace (FR-7), options.collapseUnchanged, options.loadRemoteImages (NFR-8, default false: remote src in raw HTML is blanked), options.renderMermaid (P2.8, default true: each ` ```mermaid ` fence is one block; false diffs it line by line), options.pairingThreshold (minimum similarity of the content words, leaving out function words such as “the”, for a removed and an added block to pair as one modified block; default DiffOptions.DEFAULT_PAIRING_THRESHOLD, halved for a block that is the only one of its kind in its window). leftBaseUri / rightBaseUri (optional): URI of the directory holding each side’s file, ending with / (file:///home/me/project/docs/). Relative image paths on that side resolve against it; null keeps them as written. Absolute URLs, root-relative paths (/img.png) and fragments are never rewritten.

The IDE does not call render() for a side above LargeFiles.limit() (default SizeLimit.DEFAULT: 20,000 lines or 2 MB, NFR-4); it shows a notice with an Open Text Viewer button instead. render() is called off the EDT, inside a read action, possibly concurrently: no state, no IntelliJ APIs, no network.

Output: RenderedDiff(bodyHtml, css, blocks)

Supported Markdown

commonmark-java with these extensions: GFM tables, strikethrough, task list items, autolinks, footnotes, GitHub alerts, plus emoji shortcodes (EmojiExtension, GitHub names from src/main/resources/pl/mdreview/emoji.txt; unknown ones stay as text). Front matter is recognized before parsing. Block kinds (BlockKind):

Kind Element
HEADING, PARAGRAPH, LIST_ITEM, TABLE_ROW headings, paragraphs, list items, table rows
CODE_FENCE, CODE_LINE a code block’s opening fence (info string) and each of its lines
THEMATIC_BREAK ---, ***, ___; the syntax is not compared
HTML a raw HTML block, compared by its visible text
HTML_COMMENT an HTML block that is exactly one <!-- comment -->, compared by its text
DETAILS the opening of a <details> element that spans several blocks (its summary); its content blocks have BlockContext.details > 0, the closing </details> is not a block
FRONT_MATTER a line of YAML front matter
FOOTNOTE a footnote definition
ALERT the title line of a GitHub alert
MATH a block formula ($$ ... $$), compared exactly except for trailing whitespace; Block.lines holds its source lines for the source diff

Hard line breaks (two trailing spaces or \) are part of a block’s key and appear as \n in its word diff, so adding or removing one is a change; switching between the two syntaxes is not. With ignoreWhitespace they are ignored.

Math (MathExtension, P2.4): $...$ is an inline formula (InlineMath) when the opening $ is not followed by whitespace and the closing $ is not preceded by whitespace or followed by a digit, as on GitHub, so prices such as “$5 or $10” stay text; code spans and \$ come first. $$ on a line of its own opens a block formula (MathBlock) closed by a line ending in $$; it does not interrupt a paragraph, and $$...$$ within a line is an inline formula in display mode. In a block’s plain text an inline formula is its source with its dollars, and Block.atoms lists those ranges so the word diff keeps each formula whole.

What the IDE does