-
Notifications
You must be signed in to change notification settings - Fork 0
SyntaxEditor
David Sisco edited this page Jan 6, 2026
·
3 revisions
The SyntaxEditor provides batched mutations with atomic commit and undo/redo support.
using TinyTokenizer.Ast;
var tree = SyntaxTree.Parse("hello world");
// Create editor from tree
var editor = tree.CreateEditor();
// Make changes
editor
.Replace(Query.Ident("hello"), "goodbye")
.Commit();When you have specific nodes, use them directly (preferred for known nodes):
var tree = SyntaxTree.Parse("a + b");
var idents = tree.Select(Query.AnyIdent).ToList(); // [Ident("a"), Ident("b")]
tree.CreateEditor()
.Replace(idents[0], "x") // Replace "a" node
.Replace(idents[1], "y") // Replace "b" node
.InsertBefore(idents[0], "(") // Insert before node
.InsertAfter(idents[1], ")") // Insert after node
.Commit();
// Result: "(x + y)"Use queries for pattern-based selection:
var tree = SyntaxTree.Parse("a + b");
tree.CreateEditor()
.Replace(Query.Ident("a"), "x") // Replace by query
.Replace(Query.Ident("b"), "y")
.InsertBefore(Query.AnyOperator.First(), "(")
.InsertAfter(Query.AnyOperator.First(), ")")
.Commit();
// Result: "(x + y)"Insert content relative to a node:
tree.CreateEditor()
.InsertBefore(node, "/* prefix */ ")
.InsertAfter(node, " /* suffix */")
.Commit();Use Query API queries:
tree.CreateEditor()
.InsertBefore(Query.Ident("foo").First(), "// comment\n")
.InsertAfter(Query.Ident("foo").First(), "();")
.Commit();Use .Start() and .End() on block queries to insert at block boundaries:
tree.CreateEditor()
.InsertAfter(Query.BraceBlock.First().Start(), "// entry\n")
.InsertBefore(Query.BraceBlock.First().End(), "// exit\n")
.Commit();
// Before: "function { body }"
// After: "function { // entry\nbody// exit\n }"| Position | Method | Description |
|---|---|---|
After {
|
InsertAfter(block.Start(), ...) |
Insert at beginning of block content |
Before }
|
InsertBefore(block.End(), ...) |
Insert at end of block content |
Before {
|
InsertBefore(block, ...) |
Insert before the entire block |
After }
|
InsertAfter(block, ...) |
Insert after the entire block |
## Replace Operations
### Replace with String
```csharp
tree.CreateEditor()
.Replace(Query.Ident("old"), "new")
.Commit();
Transform each matched node:
tree.CreateEditor()
.Replace(Query.AnyIdent, n => ((SyntaxToken)n).Text.ToUpper())
.Commit();
// Before: "hello world"
// After: "HELLO WORLD"Copy a node from another tree:
var sourceTree = SyntaxTree.Parse("replacement");
var newNode = sourceTree.Select(Query.AnyIdent).First(); // Ident("replacement")
tree.CreateEditor()
.Replace(Query.Ident("old"), newNode)
.Commit();Replace multiple nodes at once:
var tree = SyntaxTree.Parse("a b c");
var nodes = tree.Select(Query.AnyIdent).ToList(); // [Ident("a"), Ident("b"), Ident("c")]
tree.CreateEditor()
.Replace(nodes, "X") // Replace all with same value
.Commit();
// Result: "X X X"The Edit methods transform content while preserving surrounding whitespace:
var tree = SyntaxTree.Parse(" hello world ");
tree.CreateEditor()
.Edit(Query.AnyIdent, content => content.ToUpper())
.Commit();
// Result: " HELLO WORLD " (whitespace preserved)| Method | Transformer Input | Trivia Handling |
|---|---|---|
Replace(query, node => ...) |
Full SyntaxNode
|
You handle trivia |
Edit(query, content => ...) |
Content string only | Auto-preserved |
// Edit: transformer receives "hello" (no trivia)
tree.CreateEditor()
.Edit(Query.Ident("hello"), content => $"[{content}]")
.Commit();
// Replace: transformer receives full node with trivia
tree.CreateEditor()
.Replace(Query.Ident("hello"), node => $"[{((SyntaxToken)node).Text}]")
.Commit();var tree = SyntaxTree.Parse("keep remove keep");
var toRemove = tree.Select(Query.Ident("remove")).First(); // Ident("remove")
tree.CreateEditor()
.Remove(toRemove)
.Commit();
// Result: "keep keep"tree.CreateEditor()
.Remove(Query.AnyComment) // Remove all comments
.Commit();When you have a node but need a query:
var tree = SyntaxTree.Parse("foo baz");
var node = tree.Select(Query.Ident("foo")).First(); // Ident("foo")
tree.CreateEditor()
.Replace(Query.Exact(node), "bar") // Same as .Replace(node, "bar")
.Commit();
// Result: "bar baz"Changes aren't applied until Commit():
var editor = tree.CreateEditor()
.Replace(Query.Ident("a"), "x")
.Replace(Query.Ident("b"), "y");
// Tree unchanged here
editor.Commit(); // Now tree is updatedAll commits support undo/redo:
var tree = SyntaxTree.Parse("hello");
tree.CreateEditor()
.Replace(Query.Ident("hello"), "goodbye")
.Commit();
Console.WriteLine(tree.Root.ToFullString()); // "goodbye"
// Undo
tree.Undo();
Console.WriteLine(tree.Root.ToFullString()); // "hello"
// Redo
tree.Redo();
Console.WriteLine(tree.Root.ToFullString()); // "goodbye"Each Commit() creates an undo point:
tree.CreateEditor().Replace(q1, "a").Commit(); // State 1
tree.CreateEditor().Replace(q2, "b").Commit(); // State 2
tree.CreateEditor().Replace(q3, "c").Commit(); // State 3
tree.Undo(); // Back to State 2
tree.Undo(); // Back to State 1
tree.Redo(); // Forward to State 2var tree = SyntaxTree.Parse("function { body }");
var block = tree.Select(Query.BraceBlock).First(); // Block("{ body }")
tree.CreateEditor()
.InsertBefore(block, "/* before */ ")
.InsertAfter(block, " /* after */")
.Commit();
// Result: "function /* before */ { body } /* after */"tree.CreateEditor()
.Edit(Query.AnyIdent, s => s.ToLowerInvariant())
.Commit();tree.CreateEditor()
.InsertAfter(Query.BraceBlock.First().Start(), "\n console.log('enter');")
.InsertBefore(Query.BraceBlock.First().End(), "\n console.log('exit');")
.Commit();tree.CreateEditor()
.Replace(Query.Ident("oldName"), "newName")
.Commit();tree.CreateEditor()
.Remove(Query.AnyComment)
.Commit();- Query API — Building queries for editing
- TinyAst Guide — Syntax tree basics
- Trivia — Understanding whitespace preservation