Every operation implements IDocumentOperation, carries a JSON type discriminator, and returns an OperationResult. Operations are safe to use directly or within a DocumentPipeline.
| Navigation: Overview | Pipeline | Examples | Serialization |
public interface IDocumentOperation
{
string Type { get; } // JSON discriminator e.g. "INSERT_AT"
OperationResult Execute(TextDocument doc);
}
| Property | Type | Description |
|---|---|---|
Success |
bool |
Whether the operation succeeded. |
ErrorMessage |
string? |
Failure reason (null on success). |
CharsInserted |
int |
Characters added to the document. |
CharsDeleted |
int |
Characters removed from the document. |
DeltaChars |
int |
CharsInserted - CharsDeleted. Used by the pipeline for offset drift. |
MatchCount |
int |
For pattern ops: number of matches found/replaced. |
FoundMatches |
IReadOnlyList<string> |
Text of matches (FindAllOperation only). |
ExtractedText |
string? |
Extracted text (ExtractSectionOperation only). |
ResolvedOffset |
int |
Offset where the operation actually wrote. |
These implement IOffsetAwareOperation, which adds ApplyDrift(int delta). The pipeline calls this automatically to adjust offsets as earlier operations insert or delete characters.
new InsertAtOperation(int offset, string text)
Insert text at absolute character offset offset.
new InsertAtOperation(10, "hello")
// JSON: {"type":"INSERT_AT","offset":10,"text":"hello"}
new DeleteAtOperation(int offset, int length)
Delete length characters starting at offset.
new DeleteAtOperation(0, 5)
// JSON: {"type":"DELETE_AT","offset":0,"length":5}
new ReplaceAtOperation(int offset, int deleteLength, string text)
Delete deleteLength chars at offset, then insert text.
new ReplaceAtOperation(6, 5, "Everyone")
// JSON: {"type":"REPLACE_AT","offset":6,"deleteLength":5,"text":"Everyone"}
Anchors are resolved at execution time by searching for the anchor string in the current document. No offset arithmetic required.
new InsertAfterOperation
{
Anchor = "Work Experience",
Text = "\n- TextAPI developer",
Occurrence = 1, // default: 1st match
CaseSensitive = true // default: true
}
// JSON: {"type":"INSERT_AFTER","anchor":"Work Experience","text":"\n- TextAPI developer"}
new InsertBeforeOperation { Anchor = "## Section", Text = "<!-- new -->\n" }
new AppendOperation { Text = "\n\n// End of file" }
// Inserts at doc.Length
new PrependOperation { Text = "// Copyright 2025\n" }
// Inserts at offset 0
Replace the content between two anchor strings. Use IncludeAnchors = true to also replace the anchors themselves.
new ReplaceSectionOperation
{
StartAnchor = "/* BEGIN */",
EndAnchor = "/* END */",
Text = "new content here",
IncludeAnchors = false // default: replace only what's between the anchors
}
new DeleteSectionOperation
{
StartAnchor = "/* BEGIN */",
EndAnchor = "/* END */",
IncludeAnchors = true
}
new ReplaceAllOperation
{
Find = "TODO",
Replace = "DONE",
CaseSensitive = false, // default: true
WholeWord = false // default: false
}
// result.MatchCount = number of replacements made
new RegexReplaceOperation
{
Pattern = @"\d{3}-\d{3}-\d{4}",
Replacement = "[REDACTED]"
}
// Uses .NET regex. Replacement supports capture group refs: $1, ${name}
All line indexes are 0-based.
new InsertLineOperation { LineIndex = 3, Text = "new line content" }
// LineIndex 0 = before first line; LineCount = after last line
new DeleteLineOperation { LineIndex = 2 }
// Deletes the line and its trailing newline
new ReplaceLineOperation { LineIndex = 0, Text = "replacement content" }
// Replaces line content only; newline is preserved
new InsertAfterLineMatchOperation
{
Pattern = "def main",
LineText = " # Auto-inserted comment",
UseRegex = false,
Occurrence = 1
}
// Finds the Nth line whose text contains Pattern, inserts LineText after it
Note: Most transform operations use doc.Load() internally, which resets the undo stack and the change-tracker baseline. This is the correct trade-off for whole-document transformations. Use them as a final step in a pipeline when undo history doesn’t matter for the transformed result.
new NormaliseWhitespaceOperation()
// Collapses runs of spaces/tabs within each line to a single space.
// Newlines are untouched.
new TrimTrailingWhitespaceOperation()
// Removes trailing spaces/tabs from every line.
// Whole document
new ConvertCaseOperation { Mode = CaseMode.Upper }
// Between two anchors
new ConvertCaseOperation
{
Mode = CaseMode.TitleCase,
StartAnchor = "## Name",
EndAnchor = "## End"
}
// CaseMode: Upper | Lower | TitleCase
new SortLinesOperation
{
StartLine = 0, // default: 0
EndLine = null, // default: last line
Descending = false, // default: ascending
CaseSensitive = true // default: true
}
new DeduplicateLinesOperation
{
ConsecutiveOnly = false // false = remove all dupes; true = only adjacent dupes
}
new IndentOperation
{
StartLine = 2,
EndLine = 5,
Prefix = " ", // default: 4 spaces
Dedent = false // true = remove prefix instead of adding it
}
// Uses doc.Replace() per line (not doc.Load()) — undo stack is preserved
new WrapLinesOperation
{
MaxColumns = 80, // default: 80
StartLine = 0, // default: 0
EndLine = null // default: last line
}
// Hard-wraps long lines at word boundaries.
These operations never mutate the document. They always return Success = true.
var op = new FindAllOperation
{
Pattern = @"\bTODO\b",
UseRegex = true,
CaseSensitive = true,
WholeWord = false
};
var result = op.Execute(doc);
// result.MatchCount = number of matches
// result.FoundMatches = list of matched strings
var op = new ExtractSectionOperation
{
StartAnchor = "<!-- start -->",
EndAnchor = "<!-- end -->",
IncludeAnchors = false
};
var result = op.Execute(doc);
// result.ExtractedText = the text between the two anchors
var op = new ContainsOperation
{
Pattern = "TODO",
UseRegex = false
};
var result = op.Execute(doc);
// result.Success = true always
// result.MatchCount = 1 if found, 0 if not
// result.ErrorMessage = "Pattern not found" when MatchCount == 0
Cuts a section defined by two anchor strings and inserts it at a destination anchor. The destination anchor is re-resolved after the cut (positions shift).
new MoveSectionOperation
{
SourceStartAnchor = "[S]",
SourceEndAnchor = "[E]",
DestinationAnchor = "[DEST]",
InsertAfterDestination = true // default: true (insert after)
}
Swaps the content of two non-overlapping sections. Section 1 must appear before section 2.
new SwapSectionsOperation
{
Section1Start = "[S1]", Section1End = "[E1]",
Section2Start = "[S2]", Section2End = "[E2]"
}
// result.CharsInserted == result.CharsDeleted (symmetric swap)
| JSON Type | Class | Category |
|---|---|---|
INSERT_AT |
InsertAtOperation |
Offset |
DELETE_AT |
DeleteAtOperation |
Offset |
REPLACE_AT |
ReplaceAtOperation |
Offset |
INSERT_AFTER |
InsertAfterOperation |
Anchor |
INSERT_BEFORE |
InsertBeforeOperation |
Anchor |
APPEND |
AppendOperation |
Anchor |
PREPEND |
PrependOperation |
Anchor |
REPLACE_SECTION |
ReplaceSectionOperation |
Anchor |
DELETE_SECTION |
DeleteSectionOperation |
Anchor |
REPLACE_ALL |
ReplaceAllOperation |
Pattern |
REGEX_REPLACE |
RegexReplaceOperation |
Pattern |
INSERT_LINE |
InsertLineOperation |
Line |
DELETE_LINE |
DeleteLineOperation |
Line |
REPLACE_LINE |
ReplaceLineOperation |
Line |
INSERT_AFTER_LINE_MATCH |
InsertAfterLineMatchOperation |
Line |
NORMALISE_WHITESPACE |
NormaliseWhitespaceOperation |
Transform |
TRIM_TRAILING_WHITESPACE |
TrimTrailingWhitespaceOperation |
Transform |
CONVERT_CASE |
ConvertCaseOperation |
Transform |
SORT_LINES |
SortLinesOperation |
Transform |
DEDUPLICATE_LINES |
DeduplicateLinesOperation |
Transform |
INDENT |
IndentOperation |
Transform |
WRAP_LINES |
WrapLinesOperation |
Transform |
FIND_ALL |
FindAllOperation |
Query |
EXTRACT_SECTION |
ExtractSectionOperation |
Query |
CONTAINS |
ContainsOperation |
Query |
MOVE_SECTION |
MoveSectionOperation |
Structural |
SWAP_SECTIONS |
SwapSectionsOperation |
Structural |