From a439f15c5f240c85d4dc5a29a08dbdf88ac79d4c Mon Sep 17 00:00:00 2001 From: Yura Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:17:30 +0200 Subject: [PATCH] Introduce SourceRegion --- src/index.ts | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 21fc37e..ded5edc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,6 +97,22 @@ export class SourceText { return { index, line, column }; } + // Creates a SourceRegion from a Span. + makeRegion(span: Span): SourceRegion { + // Basic validation + if (span.start.index < 0 || span.end.index > this.length) { + throw new Error(`Span out of bounds: ${span.start.index}-${span.end.index} (length: ${this.length})`); + } + return new SourceRegion(this, span); + } + + // Creates a SourceRegion covering the entire SourceText. + fullRegion(): SourceRegion { + const start = this.getLocation(0); + const end = this.getLocation(this.length); + return this.makeRegion({ start, end }); + } + // Returns the full text of a specific line (1-based index) getLineText(line: number): string { const lineIndex = line - 1; @@ -132,18 +148,29 @@ export function sourceText(s: string): SourceText { } export class SourceRegion { - // TODO - readonly sourceText: SourceText; - readonly span: Span; + constructor( + public readonly source: SourceText, + public readonly span: Span + ) {} - constructor(sourceText: SourceText, span: Span) { - this.sourceText = sourceText; - // TODO: What about span validation? - this.span = span; + get length(): number { + return this.span.end.index - this.span.start.index; + } + + toString(): string { + return this.source.sliceByCp(this.span.start.index, this.span.end.index); + } + + // Creates a sub-region within this region. + // Validates that the new span is contained within the current region. + subRegion(span: Span): SourceRegion { + if (span.start.index < this.span.start.index || span.end.index > this.span.end.index) { + throw new Error(`Sub-region span ${span.start.index}-${span.end.index} is not within parent region ${this.span.start.index}-${this.span.end.index}`); + } + return this.source.makeRegion(span); } } - export type Span = { start: SourceLocation; end: SourceLocation; @@ -190,7 +217,12 @@ export type LineView = { underline: string; // The literal "^^^" string for CLI usage }; -export function renderSpan(text: SourceText, span: Span, contextLines = 1): LineView[] { +export function renderRegion(region: SourceRegion, contextLines = 1): LineView[] { + return renderSpan(region, region.span, contextLines); +} + +export function renderSpan(region: SourceRegion, span: Span, contextLines = 1): LineView[] { + const text = region.source; const views: LineView[] = []; // Determine range of lines to show (including context)