line counts, line iterators, line spans
This commit is contained in:
parent
a439f15c5f
commit
8f1c35b982
1 changed files with 64 additions and 0 deletions
64
src/index.ts
64
src/index.ts
|
|
@ -61,6 +61,10 @@ export class SourceText {
|
|||
return this.chars.length;
|
||||
}
|
||||
|
||||
get lineCount(): number {
|
||||
return this.lineStarts.length;
|
||||
}
|
||||
|
||||
sliceByCp(start: number, end: number): string {
|
||||
const startRef = this.chars[start];
|
||||
// Handle out of bounds gracefully
|
||||
|
|
@ -74,6 +78,31 @@ export class SourceText {
|
|||
return this.source.slice(startOff, endOff);
|
||||
}
|
||||
|
||||
// Returns a Span for the given line (1-based index).
|
||||
// If stripNewlines is true, the span will exclude trailing \r\n.
|
||||
getLineSpan(line: number, stripNewlines = true): Span {
|
||||
const range = this.getLineRange(line);
|
||||
let endIdx = range.end;
|
||||
|
||||
if (stripNewlines && endIdx > range.start) {
|
||||
// Look at the character just before endIdx
|
||||
const lastChar = this.chars[endIdx - 1].char;
|
||||
if (lastChar === NEW_LINE) {
|
||||
endIdx--;
|
||||
if (endIdx > range.start && this.chars[endIdx - 1].char === CARRIAGE_RETURN) {
|
||||
endIdx--;
|
||||
}
|
||||
} else if (lastChar === CARRIAGE_RETURN) {
|
||||
endIdx--;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: this.getLocation(range.start),
|
||||
end: this.getLocation(endIdx)
|
||||
};
|
||||
}
|
||||
|
||||
// Converts a linear Code Point Index into a SourceLocation (line, column, index).
|
||||
getLocation(index: CodePointIndex): SourceLocation {
|
||||
// Does binary search.
|
||||
|
|
@ -147,6 +176,11 @@ export function sourceText(s: string): SourceText {
|
|||
return new SourceText(s);
|
||||
}
|
||||
|
||||
// Creates a Span from two SourceLocations.
|
||||
export function span(start: SourceLocation, end: SourceLocation): Span {
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
export class SourceRegion {
|
||||
constructor(
|
||||
public readonly source: SourceText,
|
||||
|
|
@ -157,10 +191,40 @@ export class SourceRegion {
|
|||
return this.span.end.index - this.span.start.index;
|
||||
}
|
||||
|
||||
get lineCount(): number {
|
||||
return this.span.end.line - this.span.start.line + 1;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.source.sliceByCp(this.span.start.index, this.span.end.index);
|
||||
}
|
||||
|
||||
// Returns a Span for the given line (1-based index).
|
||||
getLineSpan(line: number, stripNewlines = true): Span {
|
||||
if (line < this.span.start.line || line > this.span.end.line) {
|
||||
throw new Error(`Line ${line} is outside of region lines ${this.span.start.line}-${this.span.end.line}`);
|
||||
}
|
||||
return this.source.getLineSpan(line, stripNewlines);
|
||||
}
|
||||
|
||||
// Iterates over all lines that intersect this region.
|
||||
// Yields a Span for each line.
|
||||
*lines(stripNewlines = true): IterableIterator<Span> {
|
||||
const startLine = this.span.start.line;
|
||||
const endLine = this.span.end.line;
|
||||
|
||||
for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
|
||||
yield this.getLineSpan(currentLine, stripNewlines);
|
||||
}
|
||||
}
|
||||
|
||||
forEachLine(callback: (span: Span, lineNo: number) => void, stripNewlines = true): void {
|
||||
let lineNo = this.span.start.line;
|
||||
for (const lineSpan of this.lines(stripNewlines)) {
|
||||
callback(lineSpan, lineNo++);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a sub-region within this region.
|
||||
// Validates that the new span is contained within the current region.
|
||||
subRegion(span: Span): SourceRegion {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue