<commit_before>/// <reference path="scriptVersionCache.ts"/>

namespace ts.server {

    /* @internal */
    export class TextStorage {
        /**
         * Generated only on demand (based on edits, or information requested)
         * The property text is set to undefined when edits happen on the cache
         */
        private svc: ScriptVersionCache | undefined;
        private svcVersion = 0;

        /**
         * Stores the text when there are no changes to the script version cache
         * The script version cache is generated on demand and text is still retained.
         * Only on edits to the script version cache, the text will be set to undefined
         */
        private text: string;
        /**
         * Line map for the text when there is no script version cache present
         */
        private lineMap: number[];
        private textVersion = 0;

        /**
         * True if the text is for the file thats open in the editor
         */
        public isOpen: boolean;
        /**
         * True if the text present is the text from the file on the disk
         */
        private ownFileText: boolean;
        /**
         * True when reloading contents of file from the disk is pending
         */
        private pendingReloadFromDisk: boolean;

        constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
        }

        public getVersion() {
            return this.svc
                ? `SVC-${this.svcVersion}-${this.svc.getSnapshotVersion()}`
                : `Text-${this.textVersion}`;
        }

        public hasScriptVersionCache_TestOnly() {
            return this.svc !== undefined;
        }

        public useScriptVersionCache_TestOnly() {
            this.switchToScriptVersionCache();
        }

        public useText(newText?: string) {
            this.svc = undefined;
            this.text = newText;
            this.lineMap = undefined;
            this.textVersion++;
        }

        public edit(start: number, end: number, newText: string) {
            this.switchToScriptVersionCache().edit(start, end - start, newText);
            this.ownFileText = false;
            this.text = undefined;
            this.lineMap = undefined;
        }

        /**
         * Set the contents as newText
         * returns true if text changed
         */
        public reload(newText: string) {
            Debug.assert(newText !== undefined);

            // Reload always has fresh content
            this.pendingReloadFromDisk = false;

            // If text changed set the text
            // This also ensures that if we had switched to version cache,
            // we are switching back to text.
            // The change to version cache will happen when needed
            // Thus avoiding the computation if there are no changes
            if (this.text !== newText) {
                this.useText(newText);
                // We cant guarantee new text is own file text
                this.ownFileText = false;
                return true;
            }
        }

        /**
         * Reads the contents from tempFile(if supplied) or own file and sets it as contents
         * returns true if text changed
         */
        public reloadWithFileText(tempFileName?: string) {
            const reloaded = this.reload(this.getFileText(tempFileName));
            this.ownFileText = !tempFileName || tempFileName === this.fileName;
            return reloaded;
        }

        /**
         * Reloads the contents from the file if there is no pending reload from disk or the contents of file are same as file text
         * returns true if text changed
         */
        public reloadFromDisk() {
            if (!this.pendingReloadFromDisk && !this.ownFileText) {
                return this.reloadWithFileText();
            }
            return false;
        }

        public delayReloadFromFileIntoText() {
            this.pendingReloadFromDisk = true;
        }

        public getSnapshot(): IScriptSnapshot {
            return this.useScriptVersionCacheIfValidOrOpen()
                ? this.svc.getSnapshot()
                : ScriptSnapshot.fromString(this.getOrLoadText());
        }

        public getLineInfo(line: number): AbsolutePositionAndLineText {
            return this.switchToScriptVersionCache().getLineInfo(line);
        }
        /**
         *  @param line 0 based index
         */
        lineToTextSpan(line: number): TextSpan {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                const lineMap = this.getLineMap();
                const start = lineMap[line]; // -1 since line is 1-based
                const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
                return createTextSpanFromBounds(start, end);
            }
            return this.svc.lineToTextSpan(line);
        }

        /**
         * @param line 1 based index
         * @param offset 1 based index
         */
        lineOffsetToPosition(line: number, offset: number): number {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
            }

            // TODO: assert this offset is actually on the line
            return this.svc.lineOffsetToPosition(line, offset);
        }

        positionToLineOffset(position: number): protocol.Location {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
                return { line: line + 1, offset: character + 1 };
            }
            return this.svc.positionToLineOffset(position);
        }

        private getFileText(tempFileName?: string) {
            return this.host.readFile(tempFileName || this.fileName) || "";
        }

        private switchToScriptVersionCache(): ScriptVersionCache {
            if (!this.svc || this.pendingReloadFromDisk) {
                this.svc = ScriptVersionCache.fromString(this.getOrLoadText());
                this.svcVersion++;
            }
            return this.svc;
        }

        private useScriptVersionCacheIfValidOrOpen(): ScriptVersionCache | undefined {
            // If this is open script, use the cache
            if (this.isOpen) {
                return this.switchToScriptVersionCache();
            }

            // Else if the svc is uptodate with the text, we are good
            return !this.pendingReloadFromDisk && this.svc;
        }

        private getOrLoadText() {
            if (this.text === undefined || this.pendingReloadFromDisk) {
                Debug.assert(!this.svc || this.pendingReloadFromDisk, "ScriptVersionCache should not be set when reloading from disk");
                this.reloadWithFileText();
            }
            return this.text;
        }

        private getLineMap() {
            Debug.assert(!this.svc, "ScriptVersionCache should not be set");
            return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
        }
    }

    /*@internal*/
    export function isDynamicFileName(fileName: NormalizedPath) {
        return fileName[0] === "^" || getBaseFileName(fileName)[0] === "^";
    }

    export class ScriptInfo {
        /**
         * All projects that include this file<commit_msg>Reload the text from file if there is pending reload of the script info before determining to use SVC Fixes #20806<commit_after>/// <reference path="scriptVersionCache.ts"/>

namespace ts.server {

    /* @internal */
    export class TextStorage {
        /**
         * Generated only on demand (based on edits, or information requested)
         * The property text is set to undefined when edits happen on the cache
         */
        private svc: ScriptVersionCache | undefined;
        private svcVersion = 0;

        /**
         * Stores the text when there are no changes to the script version cache
         * The script version cache is generated on demand and text is still retained.
         * Only on edits to the script version cache, the text will be set to undefined
         */
        private text: string;
        /**
         * Line map for the text when there is no script version cache present
         */
        private lineMap: number[];
        private textVersion = 0;

        /**
         * True if the text is for the file thats open in the editor
         */
        public isOpen: boolean;
        /**
         * True if the text present is the text from the file on the disk
         */
        private ownFileText: boolean;
        /**
         * True when reloading contents of file from the disk is pending
         */
        private pendingReloadFromDisk: boolean;

        constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
        }

        public getVersion() {
            return this.svc
                ? `SVC-${this.svcVersion}-${this.svc.getSnapshotVersion()}`
                : `Text-${this.textVersion}`;
        }

        public hasScriptVersionCache_TestOnly() {
            return this.svc !== undefined;
        }

        public useScriptVersionCache_TestOnly() {
            this.switchToScriptVersionCache();
        }

        public useText(newText?: string) {
            this.svc = undefined;
            this.text = newText;
            this.lineMap = undefined;
            this.textVersion++;
        }

        public edit(start: number, end: number, newText: string) {
            this.switchToScriptVersionCache().edit(start, end - start, newText);
            this.ownFileText = false;
            this.text = undefined;
            this.lineMap = undefined;
        }

        /**
         * Set the contents as newText
         * returns true if text changed
         */
        public reload(newText: string) {
            Debug.assert(newText !== undefined);

            // Reload always has fresh content
            this.pendingReloadFromDisk = false;

            // If text changed set the text
            // This also ensures that if we had switched to version cache,
            // we are switching back to text.
            // The change to version cache will happen when needed
            // Thus avoiding the computation if there are no changes
            if (this.text !== newText) {
                this.useText(newText);
                // We cant guarantee new text is own file text
                this.ownFileText = false;
                return true;
            }
        }

        /**
         * Reads the contents from tempFile(if supplied) or own file and sets it as contents
         * returns true if text changed
         */
        public reloadWithFileText(tempFileName?: string) {
            const reloaded = this.reload(this.getFileText(tempFileName));
            this.ownFileText = !tempFileName || tempFileName === this.fileName;
            return reloaded;
        }

        /**
         * Reloads the contents from the file if there is no pending reload from disk or the contents of file are same as file text
         * returns true if text changed
         */
        public reloadFromDisk() {
            if (!this.pendingReloadFromDisk && !this.ownFileText) {
                return this.reloadWithFileText();
            }
            return false;
        }

        public delayReloadFromFileIntoText() {
            this.pendingReloadFromDisk = true;
        }

        public getSnapshot(): IScriptSnapshot {
            return this.useScriptVersionCacheIfValidOrOpen()
                ? this.svc.getSnapshot()
                : ScriptSnapshot.fromString(this.getOrLoadText());
        }

        public getLineInfo(line: number): AbsolutePositionAndLineText {
            return this.switchToScriptVersionCache().getLineInfo(line);
        }
        /**
         *  @param line 0 based index
         */
        lineToTextSpan(line: number): TextSpan {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                const lineMap = this.getLineMap();
                const start = lineMap[line]; // -1 since line is 1-based
                const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
                return createTextSpanFromBounds(start, end);
            }
            return this.svc.lineToTextSpan(line);
        }

        /**
         * @param line 1 based index
         * @param offset 1 based index
         */
        lineOffsetToPosition(line: number, offset: number): number {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
            }

            // TODO: assert this offset is actually on the line
            return this.svc.lineOffsetToPosition(line, offset);
        }

        positionToLineOffset(position: number): protocol.Location {
            if (!this.useScriptVersionCacheIfValidOrOpen()) {
                const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
                return { line: line + 1, offset: character + 1 };
            }
            return this.svc.positionToLineOffset(position);
        }

        private getFileText(tempFileName?: string) {
            return this.host.readFile(tempFileName || this.fileName) || "";
        }

        private switchToScriptVersionCache(): ScriptVersionCache {
            if (!this.svc || this.pendingReloadFromDisk) {
                this.svc = ScriptVersionCache.fromString(this.getOrLoadText());
                this.svcVersion++;
            }
            return this.svc;
        }

        private useScriptVersionCacheIfValidOrOpen(): ScriptVersionCache | undefined {
            // If this is open script, use the cache
            if (this.isOpen) {
                return this.switchToScriptVersionCache();
            }

            // If there is pending reload from the disk then, reload the text
            if (this.pendingReloadFromDisk) {
                this.reloadWithFileText();
            }

            // At this point if svc is present its valid
            return this.svc;
        }

        private getOrLoadText() {
            if (this.text === undefined || this.pendingReloadFromDisk) {
                Debug.assert(!this.svc || this.pendingReloadFromDisk, "ScriptVersionCache should not be set when reloading from disk");
                this.reloadWithFileText();
            }
            return this.text;
        }

        private getLineMap() {
            Debug.assert(!this.svc, "ScriptVersionCache should not be set");
            return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
        }
    }

    /*@internal*/
    export function isDynamicFileName(fileName: NormalizedPath) {
        return fileName[0] === "^" || getBaseFileName(fileName)[0] === "^";
    }

    export class ScriptInfo {
        /**
         * All projects that include this file
{'fraction_non_alphanumeric': 0.08637654361751304, 'fraction_numerical': 0.0023112989500099056, 'mean_word_length': 1.7899778924097274, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3482066', 'n_tokens_mistral': 3788, 'n_tokens_neox': 3655, 'n_words': 1130}