do not use ScriptVersionCache for closed files (#12777)

This commit is contained in:
Vladimir Matveev
2016-12-08 16:17:42 -08:00
committed by GitHub
parent 9dd769dc3c
commit 7da3383504
10 changed files with 304 additions and 77 deletions
+24 -27
View File
@@ -424,7 +424,7 @@ namespace ts.server {
this.handleDeletedFile(info);
}
else {
if (info && (!info.isOpen)) {
if (info && (!info.isScriptOpen())) {
// file has been changed which might affect the set of referenced files in projects that include
// this file and set of inferred projects
info.reloadFromFile();
@@ -440,7 +440,7 @@ namespace ts.server {
// TODO: handle isOpen = true case
if (!info.isOpen) {
if (!info.isScriptOpen()) {
this.filenameToScriptInfo.remove(info.path);
this.lastDeletedFile = info;
@@ -634,10 +634,9 @@ namespace ts.server {
// Closing file should trigger re-reading the file content from disk. This is
// because the user may chose to discard the buffer content before saving
// to the disk, and the server's version of the file can be out of sync.
info.reloadFromFile();
info.close();
removeItemFromSet(this.openFiles, info);
info.isOpen = false;
// collect all projects that should be removed
let projectsToRemove: Project[];
@@ -989,7 +988,7 @@ namespace ts.server {
}
if (toAdd) {
for (const f of toAdd) {
if (f.isOpen && isRootFileInInferredProject(f)) {
if (f.isScriptOpen() && isRootFileInInferredProject(f)) {
// if file is already root in some inferred project
// - remove the file from that project and delete the project if necessary
const inferredProject = f.containingProjects[0];
@@ -1089,32 +1088,31 @@ namespace ts.server {
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
let info = this.getScriptInfoForNormalizedPath(fileName);
if (!info) {
let content: string;
if (this.host.fileExists(fileName)) {
// by default pick whatever content was supplied as the argument
// if argument was not given - then for mixed content files assume that its content is empty string
content = fileContent || (hasMixedContent ? "" : this.host.readFile(fileName));
}
if (!content) {
if (openedByClient) {
content = "";
}
}
if (content !== undefined) {
info = new ScriptInfo(this.host, fileName, content, scriptKind, openedByClient, hasMixedContent);
// do not watch files with mixed content - server doesn't know how to interpret it
if (openedByClient || this.host.fileExists(fileName)) {
info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent);
this.filenameToScriptInfo.set(info.path, info);
if (!info.isOpen && !hasMixedContent) {
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
if (openedByClient) {
if (fileContent === undefined) {
// if file is opened by client and its content is not specified - use file text
fileContent = this.host.readFile(fileName) || "";
}
}
else {
// do not watch files with mixed content - server doesn't know how to interpret it
if (!hasMixedContent) {
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
}
}
}
}
if (info) {
if (fileContent !== undefined) {
info.reload(fileContent);
if (openedByClient && !info.isScriptOpen()) {
info.open(fileContent);
}
if (openedByClient) {
info.isOpen = true;
else if (fileContent !== undefined) {
info.reload(fileContent);
}
}
return info;
@@ -1230,7 +1228,6 @@ namespace ts.server {
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
if (info) {
this.closeOpenFile(info);
info.isOpen = false;
}
this.printProjects();
}
@@ -1255,7 +1252,7 @@ namespace ts.server {
if (openFiles) {
for (const file of openFiles) {
const scriptInfo = this.getScriptInfo(file.fileName);
Debug.assert(!scriptInfo || !scriptInfo.isOpen);
Debug.assert(!scriptInfo || !scriptInfo.isScriptOpen());
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind), file.hasMixedContent);
}
+1 -1
View File
@@ -169,7 +169,7 @@ namespace ts.server {
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
const scriptInfo = this.project.getScriptInfoLSHost(filename);
if (scriptInfo) {
return scriptInfo.snap();
return scriptInfo.getSnapshot();
}
}
+1 -1
View File
@@ -448,7 +448,7 @@ namespace ts.server {
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
if (info && (info.isOpen || !requireOpen)) {
if (info && (info.isScriptOpen() || !requireOpen)) {
return this.containsScriptInfo(info);
}
}
+190 -34
View File
@@ -2,6 +2,161 @@
namespace ts.server {
/* @internal */
export class TextStorage {
private svc: ScriptVersionCache | undefined;
private svcVersion = 0;
private text: string;
private lineMap: number[];
private textVersion = 0;
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
}
public getVersion() {
return this.svc
? `SVC-${this.svcVersion}-${this.svc.getSnapshot().version}`
: `Text-${this.textVersion}`;
}
public hasScriptVersionCache() {
return this.svc !== undefined;
}
public useScriptVersionCache(newText?: string) {
this.switchToScriptVersionCache(newText);
}
public useText() {
this.svc = undefined;
this.reloadFromFile();
}
public edit(start: number, end: number, newText: string) {
this.switchToScriptVersionCache().edit(start, end - start, newText);
}
public reload(text: string) {
if (this.svc) {
this.svc.reload(text);
}
else {
this.setText(text);
}
}
public reloadFromFile(tempFileName?: string) {
if (this.svc || (tempFileName !== this.fileName)) {
this.reload(this.getFileText(tempFileName))
}
else {
this.setText(undefined);
}
}
public getSnapshot(): IScriptSnapshot {
return this.svc
? this.svc.getSnapshot()
: ScriptSnapshot.fromString(this.getOrLoadText());
}
public getLineInfo(line: number) {
return this.switchToScriptVersionCache().getSnapshot().index.lineNumberToInfo(line);
}
/**
* @param line 0 based index
*/
lineToTextSpan(line: number) {
if (!this.svc) {
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 ts.createTextSpanFromBounds(start, end);
}
const index = this.svc.getSnapshot().index;
const lineInfo = index.lineNumberToInfo(line + 1);
let len: number;
if (lineInfo.leaf) {
len = lineInfo.leaf.text.length;
}
else {
const nextLineInfo = index.lineNumberToInfo(line + 2);
len = nextLineInfo.offset - lineInfo.offset;
}
return ts.createTextSpan(lineInfo.offset, len);
}
/**
* @param line 1 based index
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
if (!this.svc) {
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1);
}
const index = this.svc.getSnapshot().index;
const lineInfo = index.lineNumberToInfo(line);
// TODO: assert this offset is actually on the line
return (lineInfo.offset + offset - 1);
}
/**
* @param line 1-based index
* @param offset 1-based index
*/
positionToLineOffset(position: number): ILineInfo {
if (!this.svc) {
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
return { line: line + 1, offset: character + 1 };
}
const index = this.svc.getSnapshot().index;
const lineOffset = index.charOffsetToLineNumberAndPos(position);
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
}
private getFileText(tempFileName?: string) {
return this.host.readFile(tempFileName || this.fileName) || "";
}
private ensureNoScriptVersionCache() {
Debug.assert(!this.svc, "ScriptVersionCache should not be set");
}
private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
if (!this.svc) {
this.svc = ScriptVersionCache.fromString(this.host, newText !== undefined ? newText : this.getOrLoadText());
this.svcVersion++;
this.text = undefined;
}
return this.svc;
}
private getOrLoadText() {
this.ensureNoScriptVersionCache();
if (this.text === undefined) {
this.setText(this.getFileText());
}
return this.text;
}
private getLineMap() {
this.ensureNoScriptVersionCache();
return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText()));
}
private setText(newText: string) {
this.ensureNoScriptVersionCache();
if (newText === undefined || this.text !== newText) {
this.text = newText;
this.lineMap = undefined;
this.textVersion++;
}
}
}
export class ScriptInfo {
/**
* All projects that include this file
@@ -11,24 +166,46 @@ namespace ts.server {
readonly path: Path;
private fileWatcher: FileWatcher;
private svc: ScriptVersionCache;
private textStorage: TextStorage;
private isOpen: boolean;
// TODO: allow to update hasMixedContent from the outside
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
content: string,
readonly scriptKind: ScriptKind,
public isOpen = false,
public hasMixedContent = false) {
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
this.svc = ScriptVersionCache.fromString(host, content);
this.textStorage = new TextStorage(host, fileName);
if (hasMixedContent) {
this.textStorage.reload("");
}
this.scriptKind = scriptKind
? scriptKind
: getScriptKindFromFileName(fileName);
}
public isScriptOpen() {
return this.isOpen;
}
public open(newText: string) {
this.isOpen = true;
this.textStorage.useScriptVersionCache(newText);
this.markContainingProjectsAsDirty();
}
public close() {
this.isOpen = false;
this.textStorage.useText();
}
public getSnapshot() {
return this.textStorage.getSnapshot();
}
getFormatCodeSettings() {
return this.formatCodeSettings;
}
@@ -112,16 +289,16 @@ namespace ts.server {
}
getLatestVersion() {
return this.svc.latestVersion().toString();
return this.textStorage.getVersion();
}
reload(script: string) {
this.svc.reload(script);
this.textStorage.reload(script);
this.markContainingProjectsAsDirty();
}
saveTo(fileName: string) {
const snap = this.snap();
const snap = this.textStorage.getSnapshot();
this.host.writeFile(fileName, snap.getText(0, snap.getLength()));
}
@@ -130,22 +307,17 @@ namespace ts.server {
this.reload("");
}
else {
this.svc.reloadFromFile(tempFileName || this.fileName);
this.textStorage.reloadFromFile(tempFileName);
this.markContainingProjectsAsDirty();
}
}
snap() {
return this.svc.getSnapshot();
}
getLineInfo(line: number) {
const snap = this.snap();
return snap.index.lineNumberToInfo(line);
return this.textStorage.getLineInfo(line);
}
editContent(start: number, end: number, newText: string): void {
this.svc.edit(start, end - start, newText);
this.textStorage.edit(start, end, newText);
this.markContainingProjectsAsDirty();
}
@@ -159,17 +331,7 @@ namespace ts.server {
* @param line 1 based index
*/
lineToTextSpan(line: number) {
const index = this.snap().index;
const lineInfo = index.lineNumberToInfo(line + 1);
let len: number;
if (lineInfo.leaf) {
len = lineInfo.leaf.text.length;
}
else {
const nextLineInfo = index.lineNumberToInfo(line + 2);
len = nextLineInfo.offset - lineInfo.offset;
}
return ts.createTextSpan(lineInfo.offset, len);
return this.textStorage.lineToTextSpan(line);
}
/**
@@ -177,11 +339,7 @@ namespace ts.server {
* @param offset 1 based index
*/
lineOffsetToPosition(line: number, offset: number): number {
const index = this.snap().index;
const lineInfo = index.lineNumberToInfo(line);
// TODO: assert this offset is actually on the line
return (lineInfo.offset + offset - 1);
return this.textStorage.lineOffsetToPosition(line, offset);
}
/**
@@ -189,9 +347,7 @@ namespace ts.server {
* @param offset 1-based index
*/
positionToLineOffset(position: number): ILineInfo {
const index = this.snap().index;
const lineOffset = index.charOffsetToLineNumberAndPos(position);
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
return this.textStorage.positionToLineOffset(position);
}
}
}
+3 -2
View File
@@ -438,8 +438,9 @@ namespace ts.server {
}
}
getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
const oldSnap = <LineIndexSnapshot>oldSnapshot;
return this.getTextChangeRangeSinceVersion(oldSnap.version);
if (oldSnapshot instanceof LineIndexSnapshot) {
return this.getTextChangeRangeSinceVersion(oldSnapshot.version);
}
}
}
+3 -3
View File
@@ -709,7 +709,7 @@ namespace ts.server {
const displayString = ts.displayPartsToString(nameInfo.displayParts);
const nameSpan = nameInfo.textSpan;
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
const nameText = scriptInfo.snap().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
projects,
(project: Project) => {
@@ -722,7 +722,7 @@ namespace ts.server {
const refScriptInfo = project.getScriptInfo(ref.fileName);
const start = refScriptInfo.positionToLineOffset(ref.textSpan.start);
const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1);
const lineText = refScriptInfo.snap().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
@@ -1326,7 +1326,7 @@ namespace ts.server {
highPriorityFiles.push(fileNameInProject);
else {
const info = this.projectService.getScriptInfo(fileNameInProject);
if (!info.isOpen) {
if (!info.isScriptOpen()) {
if (fileNameInProject.indexOf(".d.ts") > 0)
veryLowPriorityFiles.push(fileNameInProject);
else