# MakeupDiff

A [Makeup](https://hex.pm/packages/makeup) lexer for diffs and patches.

Diffs are a line-oriented format, so — unlike a lexer for a programming
language — this one classifies whole lines rather than individual syntactic
atoms.

## Supported dialects

- **Unified diffs** (`diff -u`, `git diff`, `hg diff`, `svn diff`), including
  `@@ -old,count +new,count @@ section` hunk headers and the `@@@ ... @@@`
  headers produced by combined (merge) diffs.
- **Context diffs** (`diff -c`): the `*** old` / `--- new` banner pair, the
  `***************` hunk separator, the `*** 1,5 ****` and `--- 1,7 ----`
  range lines, and the `!` change marker.
- **Normal diffs** (`diff` with no flags): `1,5c1,7` / `3d2` / `0a1` command
  lines and the `<` / `>` content markers.
- **Git extended headers**: `diff --git`, `index`, `old mode`, `new mode`,
  `new file mode`, `deleted file mode`, `copy from` / `copy to`,
  `rename from` / `rename to`, `similarity index`, `dissimilarity index`,
  `Binary files ... differ`, `GIT binary patch`.
- **Subversion / CVS scaffolding**: `Index: path`, the `====...` rule,
  `Property changes on:`, `RCS file:`, `retrieving revision`.
- **`git format-patch` mail preambles**: the mbox `From <sha> Mon Sep 17`
  line plus the usual `From:` / `Date:` / `Subject:` / MIME headers.
- The `\ No newline at end of file` marker.

## Token mapping

| Construct | Token type |
| --- | --- |
| added lines (`+`, `>`) | `:generic_inserted` |
| removed lines (`-`, `<`) | `:generic_deleted` |
| changed lines in context diffs (`!`) | `:generic_strong` |
| hunk headers, range lines, file banners | `:generic_subheading` |
| repository-level metadata (`diff --git`, `index`, ...) | `:generic_heading` |
| section hint trailing a unified hunk header | `:name_function` |
| mail headers of a `git format-patch` preamble | `:comment_preproc` |
| `\ No newline at end of file` | `:comment_special` |
| context lines and anything unrecognised | `:text` |

## Ambiguity of `---` and `+++`

A line starting with `---` is either the *old file* banner of a unified diff
or the removal of a line whose content begins with `--`; the same holds for
`+++`. The tokenizer emits a provisional token for both and resolves it in
the postprocessing pass from the surrounding lines:

- a `---` immediately followed by a `+++` is a banner pair;
- a `---` right after a repository-level heading, or at the very start of the
  input, is a banner;
- everything else is ordinary added/removed content.

So a literal `---` inside a hunk — right after an `@@` header, say — is
correctly highlighted as a deleted line rather than as a file banner.

## Installation

Add `makeup_patch` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:makeup_patch, "~> 0.1"}
  ]
end
```

The lexer will be automatically registered in Makeup for the language names
`"diff"`, `"udiff"` and `"patch"`, and for the file extensions `.diff` and
`.patch`.

## Usage

Once installed, ExDoc and any other tool using Makeup will automatically
syntax-highlight diff code blocks (tagged with `diff` as the language).

You can also use it directly:

```elixir
alias Makeup.Lexers.DiffLexer

DiffLexer.lex("""
--- a/lib/app.ex
+++ b/lib/app.ex
@@ -1,3 +1,3 @@ defmodule App do
   def hello do
-    :world
+    :diff
   end
""")
```

The lexer round-trips: for any input, `input |> DiffLexer.lex() |>
Makeup.Lexer.unlex()` returns the original string.

## License

MIT—see [LICENSE](https://github.com/am-kantox/makeup_patch/blob/main/LICENSE) for details.
