Skip to content

Word-level diff

Expressive Code tints the added and removed lines of a diff block. Word-level diff also highlights the words that changed inside each pair of lines, so readers can find a one-word edit in a long line.

Readers see

const timeout = 5000;
const timeout = options.timeout ?? 5000;
const client = createClient({
retries: 3,
retries: options.retries,
});

You write

```diff lang="js"
-const timeout = 5000;
+const timeout = options.timeout ?? 5000;
const client = createClient({
- retries: 3,
+ retries: options.retries,
});
```

The new timeout line adds options.timeout ??, and 3 becomes options.retries. The plugin pairs each removed line with the added line below it, and compares them word by word. The changed words get a stronger tint and a bar under them. The rest of the line keeps the plain diff tint.

Syntax Where
A diff block Code block fence line
ins={range}, del={range} Code block fence line
[!code ++], [!code --] Comment
wordDiff=false Code block fence line
wordDiff.minSimilarity=<0-1> Code block fence line

Word-level diff applies to every removed line that has an added line below it, from any of these sources. You do not turn it on. wordDiff=false turns it off for one block.

The ins and del attributes give the same result as a diff block, without changing the language of the block:

server.js
const port = 3000;
const port = Number(process.env.PORT ?? 3000);

[!code --] and [!code ++] move with their line when you edit the code:

config.py
TIMEOUT = 30
TIMEOUT = 60

A rewritten line is too different to compare, so it keeps a plain diff tint:

const total = a + b;
export function computeGrandTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
  • Pairing lines:
    • The plugin pairs a run of removed lines with the run of added lines below it, one by one.
    • Each pair is compared token by token: words, runs of whitespace and single punctuation characters.
    • If a pair is less than 40% similar, it is too different to compare. The plugin leaves the whole-line tints in place and adds no word highlight.
  • Highlights:
    • The changed tokens get a stronger tint and a bar in the diff colour. Whitespace between two changed tokens is part of the highlight, so a run of changed words tints as one block.
    • Changed words have a bar under them, and removed words also have a line through them, so the change does not rely on colour.
    • The plugin corrects the colour of each changed word to 4.5:1 contrast on the tint. In dark themes, this can turn the syntax colours of the words close to plain text.
  • Without JavaScript:
    • Word-level diff needs no JavaScript in the browser.

Highlights the words that changed between a removed line and an added line. Set it to false to turn the feature off.

Type
false | object
Default
On

Pairs of lines less similar than this keep whole-line tints only. From 0 to 1. A code block can set its own on its fence line.

Type
number
Default
0.4

Some codebases rewrite lines often enough that the word-level highlight adds noise more often than it helps. Raise the threshold to show it only for close edits:

astro.config.mjs
codeblocks({
wordDiff: { minSimilarity: 0.7 },
});
  • Pairing is by position. The first removed line pairs with the first added line, the second with the second, and each later pair the same way. A block that adds or removes a different number of lines can pair the wrong lines.
  • The comparison works on the visible text of the line. It does not parse the language, so it cannot tell that total and sum are the same value with a new name.
  • The plugin does not compare a long pair of lines, such as two lines of minified code. If the number of tokens in the removed line multiplied by the number in the added line is more than 1,000,000, the whole-line tints stay.
  • Line states: tint a line as an error, a warning or a note, instead of a change.
  • Comment notation: the [!code ++] and [!code --] directives that word-level diff reads.