Atom's Tree-sitter Bet: What Incremental Parsing Bought a Sunset Editor
Atom's 2022 switch to Tree-sitter added incremental parsing and structural queries, but the editor's archival froze the parser at v0.20.8. This post shows the integration's real impact, a package example using Tree-sitter queries, and how to verify your setup.
06 Apr 2026, 05:59 UTC

The problem: regex grammars don't scale
Atom's original syntax highlighting relied on TextMate-style grammars — regular expressions applied line-by-line against the entire buffer. Every keystroke triggered a full re-lex. On a 50 kLOC file you could feel the latency; on 100 kLOC the editor would visibly stutter. The core team needed a parsing model that updated in sub-millisecond time per edit without rewriting every language package from scratch.
The engineering decision: adopt Tree-sitter as a first-class runtime
Starting with v1.60 (mid-2022), Atom bundled Tree-sitter, a C library that builds a concrete syntax tree and updates it incrementally. The parser runs in the renderer process, not a separate language server, so there's no IPC hop. Edits are O(log n) where n is tree size, not buffer length. Atom ships ~30 compiled grammars (language-typescript, language-python, etc.) and loads additional ones from the language-<name> package namespace.
Grammars are written in a declarative DSL (grammar.js) and compiled to native .node modules (or WASM fallbacks). The integration exposes a LayeredDecoration API so packages can attach highlights, fold ranges, and code actions directly to syntax-node types — function_declaration, string_literal, call_expression — without regex hacks.
Worked example: a brace-insertion package that respects scope
Suppose you're writing a package that auto-inserts a closing brace when the user types {. With TextMate grammars you'd guess from regex scopes. With Tree-sitter you query the actual syntax tree:
// In your package's main module
const { Disposable } = require('atom')
module.exports = {
activate() {
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.commands.add('atom-text-editor', 'custom:insert-brace', () => {
const editor = atom.workspace.getActiveTextEditor()
if (!editor) return
const grammar = editor.getGrammar()
if (!grammar || !grammar.treeSitterParser) return // TextMate fallback
const buffer = editor.getBuffer()
const cursor = editor.getLastCursor()
const position = cursor.getBufferPosition()
// Parse current tree (incremental, cached)
const tree = grammar.treeSitterParser.parse(buffer.getText())
const rootNode = tree.rootNode
// Query: are we inside a function_declaration body?
const query = grammar.treeSitterParser.language.query(`
(function_declaration body: (statement_block) @block)
`)
const captures = query.captures(rootNode)
let insideFunction = false
for (const { node } of captures) {
if (node.startPosition.row <= position.row && node.endPosition.row >= position.row) {
insideFunction = true
break
}
}
if (insideFunction) {
editor.insertText('}')
cursor.moveLeft()
}
}))
},
deactivate() { this.subscriptions.dispose() }
}
The query language uses S-expression patterns: (function_declaration body: (statement_block) @block) captures the block node. The package checks whether the cursor row falls inside that node's range. No regex, no scope-name string matching — just structural identity.
What the numbers actually showed
On a typical 10 kLOC TypeScript file, Tree-sitter edits stayed under 2 ms per keystroke. Memory grew roughly linearly: a 100 kLOC TypeScript file consumed ~30 MB of heap for the syntax tree and parser state. That's acceptable for a desktop editor but noticeably heavier than Neovim's Lua runtime or VS Code's extension host, which both reuse parser instances more aggressively.
The migration path for existing TextMate grammars was a compatibility shim (language-textmate) that wraps tmLanguage JSON. It works — highlighting appears — but you forfeit incremental updates and structural queries. Packages that depended on private TextBuffer internals sometimes broke silently when run on forks like Pulsar or newer Electron versions.
The sunset constraint: pinned at Tree-sitter 0.20.8
Atom was archived in December 2022. No security patches, no Electron updates, and critically, no Tree-sitter version bumps. The bundled parser is pinned at ~0.20.8, which means:
- No
supertypenodes (added in 0.21) for polymorphic queries across languages - No inline queries or node subtypes introduced in 0.22+
- Native modules built with
node-gypcan fail on Node ≥ 18 or ARM64 without manual rebuilds apmis unmaintained; installing new language packages often requiresnpm installinside~/.atom/packages
VS Code and Neovim now lead Tree-sitter adoption. Both run newer parser versions and have richer ecosystems (Language Server Protocol integration, semantic tokens, etc.).
How to verify what you're running
If you're maintaining an Atom-based workflow (or a fork like Pulsar), check the actual parser version and loaded grammars:
- Open Atom v1.63 (last release) → Settings → Core → scroll to "Tree-sitter" section. Lists bundled grammars and parser version.
- Run
apm list --builtin | grep language-in a terminal to see which Tree-sitter grammars ship by default. - Inspect
~/.atom/packages/language-typescript/grammars/tree-sitter-typescript.wasm(or.node) to verify the native module exists. - In DevTools console, execute a live query:
atom.grammars.grammarForScopeName('source.ts').buffer.parse().rootNode.query('(function_declaration) @fn').captures - For memory profiling: open a 100 kLOC
.tsfile, then in DevTools → Memory take a heap snapshot; filter for "TreeSitter" strings.
Closing takeaway
Tree-sitter gave Atom a genuine architectural leap: structural editing became practical, latency dropped to imperceptible levels, and package authors gained a stable query API. The trade-off was memory overhead and a native-module maintenance burden that the project's sunset froze in place. If you're still on Atom, the integration works — but you're on a fixed parser version with no upgrade path. For new projects, the same Tree-sitter technology lives on in actively maintained editors where the parser version and ecosystem continue to evolve.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.