SourceText provides an efficient representation of source code with built-in line/column tracking, encoding information, and change tracking.
Static Factory Methods
From String
Creates source text from a string.
var text = SourceText.From(
"local x = 1\nprint(x)",
Encoding.UTF8,
SourceHashAlgorithm.Sha256
);
The text encoding. If null, the source text won’t be debuggable.
checksumAlgorithm
SourceHashAlgorithm
default:"Sha1"
Hash algorithm for PDB checksum. Values: Sha1, Sha256
A new SourceText instance
From Stream
Creates source text from a stream, auto-detecting encoding from BOM.
using var stream = File.OpenRead("script.lua");
var text = SourceText.From(
stream,
encoding: Encoding.UTF8, // Fallback if no BOM
checksumAlgorithm: SourceHashAlgorithm.Sha1,
throwIfBinaryDetected: true
);
The stream containing source text. Must be readable and seekable.
Encoding to use if no BOM is detected
checksumAlgorithm
SourceHashAlgorithm
default:"Sha1"
Hash algorithm for PDB checksum
If true, throws InvalidDataException when two consecutive NUL characters are detected
A new SourceText instance
The stream is left open after reading. Encoding is detected from BOM (Byte Order Mark) if present.
From Byte Array
Creates source text from a byte array.
byte[] bytes = File.ReadAllBytes("script.lua");
var text = SourceText.From(
bytes,
bytes.Length,
Encoding.UTF8
);
The byte array containing encoded text
Number of bytes to read from the buffer
Encoding to use if no BOM is detected
checksumAlgorithm
SourceHashAlgorithm
default:"Sha1"
Hash algorithm for PDB checksum
If true, throws when binary content is detected
Properties
Length
public int Length { get; }
The total number of characters in the text.
var text = SourceText.From("hello");
Console.WriteLine(text.Length); // 5
Lines
public TextLineCollection Lines { get; }
Collection of all lines in the text. Lines are indexed starting from 0.
var text = SourceText.From("line1\nline2\nline3");
Console.WriteLine(text.Lines.Count); // 3
Console.WriteLine(text.Lines[1].ToString()); // "line2"
Encoding
public Encoding? Encoding { get; }
The text encoding, or null if unspecified.
ChecksumAlgorithm
public SourceHashAlgorithm ChecksumAlgorithm { get; }
The hash algorithm used for PDB checksums.
Indexer
public char this[int position] { get; }
Gets the character at the specified position.
var text = SourceText.From("hello");
Console.WriteLine(text[0]); // 'h'
Console.WriteLine(text[4]); // 'o'
Methods
GetSubText
Extracts a portion of the text.
var text = SourceText.From("local x = 1");
var subText = text.GetSubText(new TextSpan(6, 1)); // "x"
var fromPosition = text.GetSubText(6); // "x = 1"
The span of text to extract
(Overload) Starting position to the end of the text
A new SourceText containing the specified portion
CopyTo
Copies a range of characters to a destination array.
var text = SourceText.From("hello world");
var chars = new char[5];
text.CopyTo(0, chars, 0, 5);
Console.WriteLine(new string(chars)); // "hello"
Starting position in source text
Destination character array
Starting position in destination array
Number of characters to copy
GetChecksum
Computes the hash checksum for the text.
var text = SourceText.From("local x = 1", Encoding.UTF8);
ImmutableArray<byte> checksum = text.GetChecksum();
The computed checksum bytes
WithChanges
Creates a new SourceText with specified changes applied.
var original = SourceText.From("local x = 1");
var changes = new[]
{
new TextChange(new TextSpan(6, 1), "y"), // Replace "x" with "y"
new TextChange(new TextSpan(10, 1), "2") // Replace "1" with "2"
};
var modified = original.WithChanges(changes);
Console.WriteLine(modified.ToString()); // "local y = 2"
changes
IEnumerable<TextChange>
required
The changes to apply. Changes must not overlap and should ideally be sorted.
A new SourceText with changes applied
Replace
Convenience method to replace a single text span.
var text = SourceText.From("local x = 1");
var modified = text.Replace(new TextSpan(6, 1), "result");
// or
var modified2 = text.Replace(6, 1, "result");
Console.WriteLine(modified.ToString()); // "local result = 1"
GetChangeRanges
Gets the changes between this text and an older version.
var oldText = SourceText.From("hello");
var newText = SourceText.From("hello world");
var ranges = newText.GetChangeRanges(oldText);
foreach (var range in ranges)
{
Console.WriteLine($"Changed {range.Span} to length {range.NewLength}");
}
The older version to compare against
return
IReadOnlyList<TextChangeRange>
The change ranges describing the differences
GetTextChanges
Gets the actual text changes between versions.
var oldText = SourceText.From("local x = 1");
var newText = SourceText.From("local y = 2");
var textChanges = newText.GetTextChanges(oldText);
foreach (var change in textChanges)
{
Console.WriteLine($"At {change.Span}: '{change.NewText}'");
}
Supporting Types
TextLine
Represents a single line of text.
var text = SourceText.From("line1\nline2");
TextLine line = text.Lines[0];
Console.WriteLine(line.Span); // TextSpan of the line
Console.WriteLine(line.Start); // Starting position
Console.WriteLine(line.End); // Ending position (exclusive)
Console.WriteLine(line.LineNumber); // 0-based line number
Console.WriteLine(line.ToString()); // "line1"
TextLineCollection
Collection of lines with efficient lookups.
var text = SourceText.From("a\nb\nc");
TextLineCollection lines = text.Lines;
Console.WriteLine(lines.Count); // 3
Console.WriteLine(lines[1].ToString()); // "b"
Console.WriteLine(lines.IndexOf(2)); // 1 (position 2 is on line 1)
TextLine line = lines.GetLineFromPosition(2); // Gets line containing position 2
LinePosition
Represents a line and character position (0-based).
var position = new LinePosition(line: 5, character: 10);
Console.WriteLine($"Line {position.Line}, Column {position.Character}");
Usage Examples
Read File and Track Lines
var text = SourceText.From(
File.ReadAllText("script.lua"),
Encoding.UTF8
);
Console.WriteLine($"Total lines: {text.Lines.Count}");
Console.WriteLine($"Total characters: {text.Length}");
for (int i = 0; i < text.Lines.Count; i++)
{
var line = text.Lines[i];
Console.WriteLine($"Line {i + 1}: {line}");
}
Find Position from Line/Column
var text = SourceText.From("line1\nline2\nline3");
// Get character at line 1, column 2 (0-based)
int lineIndex = 1;
int columnIndex = 2;
var line = text.Lines[lineIndex];
int position = line.Start + columnIndex;
char character = text[position];
Console.WriteLine($"Character: '{character}'"); // 'n' from "line2"
Apply Incremental Edits
var text = SourceText.From("local x = 1\nlocal y = 2");
// User types at position 11 (after "1")
var edit1 = text.Replace(11, 0, "0"); // "local x = 10"
// User deletes at position 6-7 (the "x")
var edit2 = edit1.Replace(6, 1, ""); // "local = 10"
// Get all changes from original to final
var changes = edit2.GetTextChanges(text);
foreach (var change in changes)
{
Console.WriteLine($"Changed [{change.Span.Start}..{change.Span.End}] to '{change.NewText}'");
}
Efficient Substring Operations
var text = SourceText.From("-- Comment\nlocal x = 1");
// Extract just the code (skip comment line)
var codeLine = text.Lines[1];
var codeOnly = text.GetSubText(codeLine.Span);
Console.WriteLine(codeOnly.ToString()); // "local x = 1"
Encoding Detection
using var stream = File.OpenRead("script.lua");
var text = SourceText.From(stream);
if (text.Encoding != null)
{
Console.WriteLine($"Detected encoding: {text.Encoding.EncodingName}");
}
else
{
Console.WriteLine("No encoding specified");
}
Line Tracking: Line information is computed lazily on first access and cached. Accessing Lines repeatedly is efficient.Large Files: Files larger than 40KB are handled with a specialized implementation to avoid Large Object Heap allocations.Immutability: SourceText is immutable. All mutation operations (WithChanges, Replace) return new instances.
See Also