Path: blob/main/extensions/copilot/src/platform/editing/common/textDocumentSnapshot.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { EndOfLine, TextDocument, TextLine, Uri } from 'vscode';6import { isNumber, isString } from '../../../util/vs/base/common/types';7import { isUriComponents, URI, UriComponents } from '../../../util/vs/base/common/uri';8import { DEFAULT_WORD_REGEXP, getWordAtText } from '../../../util/vs/editor/common/core/wordHelper';9import { Position, Range } from '../../../vscodeTypes';10import { PositionOffsetTransformer } from './positionOffsetTransformer';1112export interface ITextDocumentSnapshotJSON {13readonly uri: UriComponents;14readonly _text: string;15readonly languageId: string;16readonly version: number;17readonly eol: EndOfLine;18}1920export function isTextDocumentSnapshotJSON(thing: any): thing is ITextDocumentSnapshotJSON {21if (!thing || typeof thing !== 'object') {22return false;23}24return isUriComponents(thing.uri) && isString(thing._text) && isString(thing.languageId) && isNumber(thing.version) && isNumber(thing.eol);25}2627export class TextDocumentSnapshot {2829_textDocumentSnapshot: undefined;3031static create(doc: TextDocument): TextDocumentSnapshot {32return new TextDocumentSnapshot(33doc,34doc.uri,35doc.getText(),36doc.languageId,37doc.eol,38doc.version,39);40}4142static fromNewText(text: string, doc: TextDocument | TextDocumentSnapshot) {43return new TextDocumentSnapshot(44doc instanceof TextDocumentSnapshot ? doc.document : doc,45doc.uri,46text,47doc.languageId,48doc.eol,49doc.version + 1,50);51}5253static fromJSON(doc: TextDocument, json: ITextDocumentSnapshotJSON): TextDocumentSnapshot {54return new TextDocumentSnapshot(55doc,56URI.from(json.uri),57json._text,58json.languageId,59json.eol,60json.version,61);62}6364readonly document: TextDocument;65readonly uri: Uri;66readonly _text: string;67readonly languageId: string;68readonly version: number;69readonly eol: EndOfLine;7071private _transformer: PositionOffsetTransformer | null = null;72public get transformer(): PositionOffsetTransformer {73if (!this._transformer) {74this._transformer = new PositionOffsetTransformer(this._text);75}76return this._transformer;77}7879get fileName(): string {80return this.uri.fsPath;81}8283get isUntitled(): boolean {84return this.uri.scheme === 'untitled';85}8687get lineCount(): number {88return this.lines.length;89}9091private _lines: string[] | null = null;92get lines(): readonly string[] {93if (!this._lines) {94this._lines = this._text.split(/\r\n|\r|\n/g);95}96return this._lines;97}9899private constructor(document: TextDocument, uri: Uri, text: string, languageId: string, eol: EndOfLine, version: number) {100this.document = document;101this.uri = uri;102this._text = text;103this.languageId = languageId;104this.eol = eol;105this.version = version;106}107108lineAt(line: number): TextLine;109lineAt(position: Position): TextLine;110lineAt(lineOrPosition: number | Position): TextLine {111let line: number | undefined;112if (lineOrPosition instanceof Position) {113line = lineOrPosition.line;114} else if (typeof lineOrPosition === 'number') {115line = lineOrPosition;116} else {117throw new Error(`Invalid argument`);118}119if (line < 0 || line >= this.lines.length) {120throw new Error('Illegal value for `line`');121}122123return new SnapshotDocumentLine(line, this.lines[line], line === this.lines.length - 1);124}125126offsetAt(position: Position): number {127if (this.version === this.document.version) {128return this.document.offsetAt(position);129}130131position = this.validatePosition(position);132return this.transformer.getOffset(position);133}134135positionAt(offset: number): Position {136if (this.version === this.document.version) {137return this.document.positionAt(offset);138}139140offset = Math.floor(offset);141offset = Math.max(0, offset);142143return this.transformer.getPosition(offset);144}145146getText(range?: Range): string {147return range ? this._getTextInRange(range) : this._text;148}149150private _getTextInRange(_range: Range): string {151if (this.version === this.document.version) {152return this.document.getText(_range);153}154155const range = this.validateRange(_range);156157if (range.isEmpty) {158return '';159}160161const offsetRange = this.transformer.toOffsetRange(range);162return this._text.substring(offsetRange.start, offsetRange.endExclusive);163}164165getWordRangeAtPosition(_position: Position): Range | undefined {166const position = this.validatePosition(_position);167168const wordAtText = getWordAtText(169position.character + 1,170DEFAULT_WORD_REGEXP,171this.lines[position.line],1720173);174175if (wordAtText) {176return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);177}178return undefined;179}180181validateRange(range: Range): Range {182const start = this.validatePosition(range.start);183const end = this.validatePosition(range.end);184185if (start === range.start && end === range.end) {186return range;187}188return new Range(start.line, start.character, end.line, end.character);189}190191validatePosition(position: Position): Position {192if (this._text.length === 0) {193return position.with(0, 0);194}195196let { line, character } = position;197let hasChanged = false;198199if (line < 0) {200line = 0;201character = 0;202hasChanged = true;203} else if (line >= this.lines.length) {204line = this.lines.length - 1;205character = this.lines[line].length;206hasChanged = true;207} else {208const maxCharacter = this.lines[line].length;209if (character < 0) {210character = 0;211hasChanged = true;212} else if (character > maxCharacter) {213character = maxCharacter;214hasChanged = true;215}216}217218if (!hasChanged) {219return position;220}221return new Position(line, character);222}223224toJSON(): ITextDocumentSnapshotJSON {225return {226uri: this.uri.toJSON(),227languageId: this.languageId,228version: this.version,229eol: this.eol,230_text: this._text231};232}233}234235export class SnapshotDocumentLine implements TextLine {236private readonly _line: number;237private readonly _text: string;238private readonly _isLastLine: boolean;239240constructor(line: number, text: string, isLastLine: boolean) {241this._line = line;242this._text = text;243this._isLastLine = isLastLine;244}245246public get lineNumber(): number {247return this._line;248}249250public get text(): string {251return this._text;252}253254public get range(): Range {255return new Range(this._line, 0, this._line, this._text.length);256}257258public get rangeIncludingLineBreak(): Range {259if (this._isLastLine) {260return this.range;261}262return new Range(this._line, 0, this._line + 1, 0);263}264265public get firstNonWhitespaceCharacterIndex(): number {266//TODO@api, rename to 'leadingWhitespaceLength'267return /^(\s*)/.exec(this._text)![1].length;268}269270public get isEmptyOrWhitespace(): boolean {271return this.firstNonWhitespaceCharacterIndex === this._text.length;272}273}274275276