# Lute parses Markdown into a syntax tree, not a stream of regex substitutions

> Lute is a Markdown engine written in Go with a JavaScript build, built around a full GFM and CommonMark implementation and an abstract syntax tree. It suits editors and applications that need formatting, spelling correction and multiple editor dialects from one core. It is a poor fit if you only need a small, fast HTML converter.

**88250/lute** — 🎼 一款结构化的 Markdown 引擎，支持 Go 和 JavaScript。A structured Markdown engine that supports Go and JavaScript. 

- Repository: https://github.com/88250/lute
- Website: https://ld246.com/tag/lute
- Stars: 1,679 · Forks: 175
- Language: Go
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/88250-lute

## The tree is the product, not the HTML

Most Markdown libraries are a pipeline: read a line, match a pattern, emit a tag, move on. Lute is built the other way round. The README states the goal plainly: parse the input Markdown text into an abstract syntax tree, then produce HTML or reformatted Markdown by walking that tree. That single design choice explains most of what the project does, including the features that would be strange additions to a line-based renderer.

Because the document exists as a tree, the engine can ask structural questions that a regex pass cannot answer reliably: whether a list is loose or tight, whether a heading was written as ATX or Setext, whether a fenced code block contains a fence long enough to close it. The author is explicit that regular expressions were rejected on maintainability grounds and on the claim that the core algorithm of a GFM and CommonMark compliant parser cannot be expressed as regular expressions at all, since the specification is too tangled for that. The feature list puts it as "zero regular expressions, very fast".

The practical consequence is that Lute offers operations a renderer does not need: reformatting the source, correcting spellings, converting HTML back to Markdown. Those all require a node-level view of the document rather than a stream of emitted tags.

## Spec compliance is the reason the project exists

The stated motivation is ambiguity. When two engines disagree on the same Markdown, every bug report becomes a spec question, so the project targets the latest GFM and CommonMark specifications and leans on them for Chinese text handling. The README argues this matters for more than pedantry: an engine that follows the spec produces the same output for the same input, which is what a collaborative editing product needs.

Several projects depend on that. Solo, Pipe and Sym need rendering that looks identical across three separate systems and, in the author's words, need performance to matter. Vditor needs a structured engine underneath a next generation Markdown editor. SiYuan and the ld246.com community site appear as users, and ld246.com carries all Markdown processing for the site.

One gap is worth naming. The security section is cut off mid-sentence, but the visible text states that Lute does not implement the GFM Disallowed Raw HTML extension. In practice that means raw HTML in the source is not filtered by this engine. If your Markdown comes from untrusted users, that is your problem to solve at a layer above Lute.

## Chinese text changes the rules, not just the glyphs

The Chinese language support is not a translation layer bolted on afterwards. Two behaviours exist because of how mixed Chinese and Latin text reads. Automatic link recognition is strengthened, so bare URLs are linked more reliably. And spaces are inserted automatically between Chinese and Western characters, so 一段中文English文本 becomes visually separated without the author typing them.

That second rule is the one with consequences. It runs in both directions of the pipeline: HTML can be converted back to Markdown with the same spacing behaviour, and the formatter applies it. The project's own formatting example notes explicitly that spaces are not inserted inside code blocks, which is the correct call and a useful boundary to know before you enable it broadly.

Term correction is a separate feature with the same flavour. Given a paragraph where the author wrote `github` and Github instead of GitHub, the corrector rewrites the casing. The README example turns JAVA、Javascript、GIT and Mysql、postgresql into Java、JavaScript、Git and MySQL、PostgreSQL. Useful in a Chinese résumé or technical article, and plainly opinionated, since it is a fixed mapping table of preferred spellings rather than a general spell checker.

## Format mode rewrites the source, which is the whole point and the main risk

The formatter takes untidy Markdown and returns a single house style. The README gives a long before and after example, and the differences are worth reading closely because they show what it decides is sloppy. A trailing `##` is stripped from an ATX heading. A Setext heading is rewritten as ATX. An unordered-looking ordered list starting at 0 is renumbered to 0, 1, 2. Block content jammed together gets blank lines inserted. Indented code blocks are converted to fenced blocks, which the author argues is less hidden and allows a language tag for highlighting, and therefore keeps syntax highlighting from silently failing. GFM tables get padding and alignment markers added.

Loose and tight lists are treated differently, and list items that contain more than one paragraph keep their paragraph structure while gaining a blank line before the nested fence.

The author's reason for building this is collaborative editing: when several people edit the same file, a uniform layout makes the result easier to read. That reason is sound. The risk is that formatting is a source rewrite, not a display option. Running it over user content changes bytes the user typed, and the corrections are opinionated. Nothing in the README documents a dry run, a diff mode, or a way to undo. If you need that safety net, you would have to build it.

## Where to get it, and what a first call touches

The README gives no installation commands. It points at the repository and at the project page, and the Go module path is declared in go.mod at the repository root, so that is where you look to pin a version:

```gomod
module github.com/88250/lute

go 1.26
```

Three direct dependencies are declared, and they tell you what the engine is made of: `github.com/alecthomas/chroma/v2` for syntax highlighting, `github.com/gopherjs/gopherjs` for the JavaScript build, and `golang.org/x/text` for text handling. Note the Go directive: 1.26. This is not a library you can drop into an older toolchain without bumping it.

Before writing integration code, read go.mod rather than the README for the API surface. The repository layout is the better map: `lex/` for the lexer, `parse/` for the parser, `ast/` for the node types, `render/` for output, `html/` for HTML rendering, and top level files such as `h2m.go` for HTML to Markdown conversion. A `benchmark/` directory and a `pprof/` directory exist for measurement work. `editor/` and `test/` are the remaining areas. That split tells you where to file a bug: a tokenisation problem is not the same report as a rendering one.

## Four editor dialects in one repository

The top-level files include `vditor_ir.go`, `vditor_sv.go`, `vditor_wysiwyg.go`, `protyle.go`, `protyle_tabs.go` and `markdown_table_rich.go`, showing that the repository includes editor-specific code alongside its Markdown engine.

For a note-taking or editor product that is exactly the right shape. For a server-side converter it is payload and surface area you are paying for and not using. The repository topics, gopherjs, markdown, protyle, siyuan and vditor, make the intended audience explicit.

## Maintenance, release rhythm and licence

The repository is not archived and the last push was on 2026-10-11, so activity is current. Releases are irregular, though. v1.7.8 shipped on 2026-09-23 and v1.7.7 on 2026-08-11, but v1.7.6 is dated 2024-01-29. The long and short gaps between some releases mean you should not assume a steady cadence.

The licence is listed as MulanPSL, which GitHub classifies as a custom licence. That classification matters practically: some corporate licence scanners will flag the repository and require a manual check before mirroring or vendoring. That is a paperwork task, not legal advice, but budget for it if the code ends up in a product.

A Go Report Card badge and a test workflow badge are present in the README. The performance claim is a link to an external benchmark article on ld246.com rather than numbers you can verify from the repository, and the robustness claim is a statement that ld246.com runs millions of parse and render requests per day on it.

## Conclusion

Lute fits teams that already write Go and need a spec-complete parser with formatting and CJK handling inside the same library, especially if they are building an editor like Vditor or a note tool like SiYuan. It does not fit a simple static site pipeline or any stack locked to CommonMark byte-exact output. Before adopting, check that the editor dialects you actually need are the ones you want, confirm the MulanPSL terms against your own licence review, and decide early whether Format mode rewriting user text is a feature or a hazard, since it changes source Markdown rather than just rendering it.

## FAQ

### How do I install Lute for a Go project?

The README documents no installation commands. The module path is declared in go.mod as github.com/88250/lute and the project targets Go 1.26, with chroma/v2, gopherjs and golang.org/x/text as direct dependencies.

### Does Lute handle Chinese text inside Markdown?

Yes. The feature list points at better support for Chinese contexts, with strengthened automatic link recognition and automatic insertion of spaces between Chinese and Western characters. Spaces are not inserted inside code blocks.

### Can Lute reformat badly written Markdown?

There is a Markdown format feature that rewrites untidy input into a uniform style, including renumbering ordered lists, converting indented code blocks to fenced ones, stripping trailing heading markers and padding GFM tables. The README does not document a dry run, diff mode or rollback.

### What is the licence and when was the last release?

The project ships under MulanPSL, which GitHub lists as a custom licence it cannot classify. v1.7.8 was released on 2026-09-23 and the last push was on 2026-10-11.

## Sources

- [88250/lute on GitHub](https://github.com/88250/lute)
- [Issues](https://github.com/88250/lute/issues)
- [Project website](https://ld246.com/tag/lute)
- [README](https://github.com/88250/lute/blob/master/README.md)
- [Releases](https://github.com/88250/lute/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/88250-lute
