Skip to content

Commit aeec611

Browse files
committed
Keep using text scriptsnapshot until edit request if its already present for open file
1 parent bf21f20 commit aeec611

File tree

87 files changed

+951
-1021
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+951
-1021
lines changed

src/server/editorServices.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ import {
169169
ProjectKind,
170170
ProjectOptions,
171171
ScriptInfo,
172-
ScriptInfoVersion,
173172
ServerHost,
174173
Session,
175174
SetTypings,
@@ -870,7 +869,7 @@ export class ProjectService {
870869
* it does not reset when creating script info again
871870
* (and could have potentially collided with version where contents mismatch)
872871
*/
873-
private readonly filenameToScriptInfoVersion = new Map<string, ScriptInfoVersion>();
872+
private readonly filenameToScriptInfoVersion = new Map<string, number>();
874873
// Set of all '.js' files ever opened.
875874
private readonly allJsFilesForOpenFileTelemetry = new Map<string, true>();
876875

@@ -1812,7 +1811,7 @@ export class ProjectService {
18121811

18131812
private deleteScriptInfo(info: ScriptInfo) {
18141813
this.filenameToScriptInfo.delete(info.path);
1815-
this.filenameToScriptInfoVersion.set(info.path, info.getVersion());
1814+
this.filenameToScriptInfoVersion.set(info.path, info.textStorage.version);
18161815
const realpath = info.getRealpathIfDifferent();
18171816
if (realpath) {
18181817
this.realpathToScriptInfos!.remove(realpath, info); // TODO: GH#18217
@@ -3001,7 +3000,7 @@ export class ProjectService {
30013000
// Opening closed script info
30023001
// either it was created just now, or was part of projects but was closed
30033002
this.stopWatchingScriptInfo(info);
3004-
info.open(fileContent!);
3003+
info.open(fileContent);
30053004
if (hasMixedContent) {
30063005
info.registerFileUpdate();
30073006
}
@@ -3075,7 +3074,7 @@ export class ProjectService {
30753074
const documentPositionMapper = getDocumentPositionMapper(
30763075
{ getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName, declarationInfo) },
30773076
declarationInfo.fileName,
3078-
declarationInfo.getLineInfo(),
3077+
declarationInfo.textStorage.getLineInfo(),
30793078
readMapFile
30803079
);
30813080
readMapFile = undefined; // Remove ref to project

src/server/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function countEachFileTypes(infos: ScriptInfo[], includeSizes = false): F
171171
deferred: 0, deferredSize: 0,
172172
};
173173
for (const info of infos) {
174-
const fileSize = includeSizes ? info.getTelemetryFileSize() : 0;
174+
const fileSize = includeSizes ? info.textStorage.getTelemetryFileSize() : 0;
175175
switch (info.scriptKind) {
176176
case ScriptKind.JS:
177177
result.js += 1;

src/server/scriptInfo.ts

Lines changed: 72 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,9 @@ import {
5353
} from "./_namespaces/ts.server";
5454
import * as protocol from "./protocol";
5555

56-
export interface ScriptInfoVersion {
57-
svc: number;
58-
text: number;
59-
}
60-
6156
/** @internal */
6257
export class TextStorage {
63-
version: ScriptInfoVersion;
58+
version: number;
6459

6560
/**
6661
* Generated only on demand (based on edits, or information requested)
@@ -74,6 +69,7 @@ export class TextStorage {
7469
* Only on edits to the script version cache, the text will be set to undefined
7570
*/
7671
private text: string | undefined;
72+
private textSnapshot: IScriptSnapshot | undefined;
7773
/**
7874
* Line map for the text when there is no script version cache present
7975
*/
@@ -100,24 +96,20 @@ export class TextStorage {
10096
*/
10197
private pendingReloadFromDisk = false;
10298

103-
constructor(private readonly host: ServerHost, private readonly info: ScriptInfo, initialVersion?: ScriptInfoVersion) {
104-
this.version = initialVersion || { svc: 0, text: 0 };
99+
constructor(private readonly host: ServerHost, private readonly info: ScriptInfo, initialVersion?: number) {
100+
this.version = initialVersion || 0;
105101
}
106102

107103
public getVersion() {
108104
return this.svc
109-
? `SVC-${this.version.svc}-${this.svc.getSnapshotVersion()}`
110-
: `Text-${this.version.text}`;
105+
? `SVC-${this.version}-${this.svc.getSnapshotVersion()}`
106+
: `Text-${this.version}`;
111107
}
112108

113109
public hasScriptVersionCache_TestOnly() {
114110
return this.svc !== undefined;
115111
}
116112

117-
public useScriptVersionCache_TestOnly() {
118-
this.switchToScriptVersionCache();
119-
}
120-
121113
private resetSourceMapInfo() {
122114
this.info.sourceFileLike = undefined;
123115
this.info.closeSourceMapFileWatcher();
@@ -131,16 +123,18 @@ export class TextStorage {
131123
public useText(newText: string) {
132124
this.svc = undefined;
133125
this.text = newText;
126+
this.textSnapshot = undefined;
134127
this.lineMap = undefined;
135128
this.fileSize = undefined;
136129
this.resetSourceMapInfo();
137-
this.version.text++;
130+
this.version++;
138131
}
139132

140133
public edit(start: number, end: number, newText: string) {
141134
this.switchToScriptVersionCache().edit(start, end - start, newText);
142135
this.ownFileText = false;
143136
this.text = undefined;
137+
this.textSnapshot = undefined;
144138
this.lineMap = undefined;
145139
this.fileSize = undefined;
146140
this.resetSourceMapInfo();
@@ -161,7 +155,12 @@ export class TextStorage {
161155
// we are switching back to text.
162156
// The change to version cache will happen when needed
163157
// Thus avoiding the computation if there are no changes
158+
if (!this.text && this.svc) {
159+
// Ensure we have text representing current state
160+
this.text = getSnapshotText(this.svc.getSnapshot());
161+
}
164162
if (this.text !== newText) {
163+
// Update the text
165164
this.useText(newText);
166165
// We cant guarantee new text is own file text
167166
this.ownFileText = false;
@@ -176,7 +175,9 @@ export class TextStorage {
176175
* returns true if text changed
177176
*/
178177
public reloadWithFileText(tempFileName?: string) {
179-
const { text: newText, fileSize } = this.getFileTextAndSize(tempFileName);
178+
const { text: newText, fileSize } = tempFileName || !this.info.isDynamicOrHasMixedContent() ?
179+
this.getFileTextAndSize(tempFileName) :
180+
{ text: "", fileSize: undefined };
180181
const reloaded = this.reload(newText);
181182
this.fileSize = fileSize; // NB: after reload since reload clears it
182183
this.ownFileText = !tempFileName || tempFileName === this.info.fileName;
@@ -216,46 +217,52 @@ export class TextStorage {
216217
}
217218

218219
public getSnapshot(): IScriptSnapshot {
219-
return this.useScriptVersionCacheIfValidOrOpen()
220-
? this.svc!.getSnapshot()
221-
: ScriptSnapshot.fromString(this.getOrLoadText());
220+
return this.tryUseScriptVersionCache()?.getSnapshot() ||
221+
(this.textSnapshot ??= ScriptSnapshot.fromString(Debug.checkDefined(this.text)));
222222
}
223223

224-
public getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
225-
return this.switchToScriptVersionCache().getAbsolutePositionAndLineText(line);
224+
public getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText {
225+
const svc = this.tryUseScriptVersionCache();
226+
if (svc) svc.getAbsolutePositionAndLineText(oneBasedLine);
227+
const lineMap = this.getLineMap();
228+
return oneBasedLine <= lineMap.length ?
229+
{
230+
absolutePosition: lineMap[oneBasedLine - 1],
231+
lineText: this.text!.substring(lineMap[oneBasedLine - 1], lineMap[oneBasedLine]),
232+
} :
233+
{
234+
absolutePosition: this.text!.length,
235+
lineText: undefined,
236+
};
226237
}
227238
/**
228239
* @param line 0 based index
229240
*/
230241
lineToTextSpan(line: number): TextSpan {
231-
if (!this.useScriptVersionCacheIfValidOrOpen()) {
232-
const lineMap = this.getLineMap();
233-
const start = lineMap[line]; // -1 since line is 1-based
234-
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text!.length;
235-
return createTextSpanFromBounds(start, end);
236-
}
237-
return this.svc!.lineToTextSpan(line);
242+
const svc = this.tryUseScriptVersionCache();
243+
if (svc) return svc.lineToTextSpan(line);
244+
const lineMap = this.getLineMap();
245+
const start = lineMap[line]; // -1 since line is 1-based
246+
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text!.length;
247+
return createTextSpanFromBounds(start, end);
238248
}
239249

240250
/**
241251
* @param line 1 based index
242252
* @param offset 1 based index
243253
*/
244254
lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number {
245-
if (!this.useScriptVersionCacheIfValidOrOpen()) {
246-
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text, allowEdits);
247-
}
248-
249-
// TODO: assert this offset is actually on the line
250-
return this.svc!.lineOffsetToPosition(line, offset);
255+
const svc = this.tryUseScriptVersionCache();
256+
return svc ?
257+
svc.lineOffsetToPosition(line, offset) :
258+
computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text, allowEdits);
251259
}
252260

253261
positionToLineOffset(position: number): protocol.Location {
254-
if (!this.useScriptVersionCacheIfValidOrOpen()) {
255-
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
256-
return { line: line + 1, offset: character + 1 };
257-
}
258-
return this.svc!.positionToLineOffset(position);
262+
const svc = this.tryUseScriptVersionCache();
263+
if (svc) return svc.positionToLineOffset(position);
264+
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
265+
return { line: line + 1, offset: character + 1 };
259266
}
260267

261268
private getFileTextAndSize(tempFileName?: string): { text: string, fileSize?: number } {
@@ -276,23 +283,29 @@ export class TextStorage {
276283
return { text: getText() };
277284
}
278285

279-
private switchToScriptVersionCache(): ScriptVersionCache {
286+
/** @internal */
287+
switchToScriptVersionCache(): ScriptVersionCache {
280288
if (!this.svc || this.pendingReloadFromDisk) {
281289
this.svc = ScriptVersionCache.fromString(this.getOrLoadText());
282-
this.version.svc++;
290+
this.textSnapshot = undefined;
291+
this.version++;
283292
}
284293
return this.svc;
285294
}
286295

287-
private useScriptVersionCacheIfValidOrOpen(): ScriptVersionCache | undefined {
288-
// If this is open script, use the cache
289-
if (this.isOpen) {
290-
return this.switchToScriptVersionCache();
296+
private tryUseScriptVersionCache(): ScriptVersionCache | undefined {
297+
if (!this.svc || this.pendingReloadFromDisk) {
298+
// Ensure updated text
299+
this.getOrLoadText();
291300
}
292301

293-
// If there is pending reload from the disk then, reload the text
294-
if (this.pendingReloadFromDisk) {
295-
this.reloadWithFileText();
302+
// If this is open script, use the cache
303+
if (this.isOpen) {
304+
if (!this.svc && !this.textSnapshot) {
305+
this.svc = ScriptVersionCache.fromString(Debug.checkDefined(this.text));
306+
this.textSnapshot = undefined;
307+
}
308+
return this.svc;
296309
}
297310

298311
// At this point if svc is present it's valid
@@ -309,14 +322,15 @@ export class TextStorage {
309322

310323
private getLineMap() {
311324
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
312-
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
325+
return this.lineMap || (this.lineMap = computeLineStarts(Debug.checkDefined(this.text)));
313326
}
314327

315328
getLineInfo(): LineInfo {
316-
if (this.svc) {
329+
const svc = this.tryUseScriptVersionCache();
330+
if (svc) {
317331
return {
318-
getLineCount: () => this.svc!.getLineCount(),
319-
getLineText: line => this.svc!.getAbsolutePositionAndLineText(line + 1).lineText!
332+
getLineCount: () => svc.getLineCount(),
333+
getLineText: line => svc.getAbsolutePositionAndLineText(line + 1).lineText!
320334
};
321335
}
322336
const lineMap = this.getLineMap();
@@ -353,7 +367,8 @@ export class ScriptInfo {
353367

354368
/** @internal */
355369
fileWatcher: FileWatcher | undefined;
356-
private textStorage: TextStorage;
370+
/** @internal */
371+
readonly textStorage: TextStorage;
357372

358373
/** @internal */
359374
readonly isDynamic: boolean;
@@ -391,29 +406,18 @@ export class ScriptInfo {
391406
readonly scriptKind: ScriptKind,
392407
public readonly hasMixedContent: boolean,
393408
readonly path: Path,
394-
initialVersion?: ScriptInfoVersion) {
409+
initialVersion?: number) {
395410
this.isDynamic = isDynamicFileName(fileName);
396411

397412
this.textStorage = new TextStorage(host, this, initialVersion);
398413
if (hasMixedContent || this.isDynamic) {
399-
this.textStorage.reload("");
400414
this.realpath = this.path;
401415
}
402416
this.scriptKind = scriptKind
403417
? scriptKind
404418
: getScriptKindFromFileName(fileName);
405419
}
406420

407-
/** @internal */
408-
getVersion() {
409-
return this.textStorage.version;
410-
}
411-
412-
/** @internal */
413-
getTelemetryFileSize() {
414-
return this.textStorage.getTelemetryFileSize();
415-
}
416-
417421
/** @internal */
418422
public isDynamicOrHasMixedContent() {
419423
return this.hasMixedContent || this.isDynamic;
@@ -423,7 +427,7 @@ export class ScriptInfo {
423427
return this.textStorage.isOpen;
424428
}
425429

426-
public open(newText: string) {
430+
public open(newText: string | undefined) {
427431
this.textStorage.isOpen = true;
428432
if (newText !== undefined &&
429433
this.textStorage.reload(newText)) {
@@ -434,12 +438,7 @@ export class ScriptInfo {
434438

435439
public close(fileExists = true) {
436440
this.textStorage.isOpen = false;
437-
if (this.isDynamicOrHasMixedContent() || !fileExists) {
438-
if (this.textStorage.reload("")) {
439-
this.markContainingProjectsAsDirty();
440-
}
441-
}
442-
else if (this.textStorage.reloadFromDisk()) {
441+
if (fileExists && this.textStorage.reloadFromDisk()) {
443442
this.markContainingProjectsAsDirty();
444443
}
445444
}
@@ -644,25 +643,13 @@ export class ScriptInfo {
644643
}
645644

646645
reloadFromFile(tempFileName?: NormalizedPath) {
647-
if (this.isDynamicOrHasMixedContent()) {
648-
this.textStorage.reload("");
646+
if (this.textStorage.reloadWithFileText(tempFileName)) {
649647
this.markContainingProjectsAsDirty();
650648
return true;
651649
}
652-
else {
653-
if (this.textStorage.reloadWithFileText(tempFileName)) {
654-
this.markContainingProjectsAsDirty();
655-
return true;
656-
}
657-
}
658650
return false;
659651
}
660652

661-
/** @internal */
662-
getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText {
663-
return this.textStorage.getAbsolutePositionAndLineText(line);
664-
}
665-
666653
editContent(start: number, end: number, newText: string): void {
667654
this.textStorage.edit(start, end, newText);
668655
this.markContainingProjectsAsDirty();
@@ -714,11 +701,6 @@ export class ScriptInfo {
714701
return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX;
715702
}
716703

717-
/** @internal */
718-
getLineInfo(): LineInfo {
719-
return this.textStorage.getLineInfo();
720-
}
721-
722704
/** @internal */
723705
closeSourceMapFileWatcher() {
724706
if (this.sourceMapFilePath && !isString(this.sourceMapFilePath)) {

0 commit comments

Comments
 (0)