isolate responsibilities of LSHost

This commit is contained in:
Vladimir Matveev 2016-05-31 16:34:14 -07:00
parent 16bfeb4922
commit 0f5e91bfe8
2 changed files with 99 additions and 109 deletions

View File

@ -48,7 +48,6 @@ namespace ts.server {
export class ScriptInfo {
svc: ScriptVersionCache;
children: ScriptInfo[] = []; // files referenced by this file
defaultProject: Project; // project to use by default for file
fileWatcher: FileWatcher;
formatCodeOptions: ts.FormatCodeOptions;
@ -135,20 +134,14 @@ namespace ts.server {
}
export class LSHost implements ts.LanguageServiceHost {
compilationSettings: ts.CompilerOptions;
filenameToScript: ts.FileMap<ScriptInfo>;
roots: ScriptInfo[] = [];
private compilationSettings: ts.CompilerOptions;
private resolvedModuleNames: ts.FileMap<Map<TimestampedResolvedModule>>;
private resolvedTypeReferenceDirectives: ts.FileMap<Map<TimestampedResolvedTypeReferenceDirective>>;
private moduleResolutionHost: ts.ModuleResolutionHost;
private getCanonicalFileName: (fileName: string) => string;
constructor(public host: ServerHost, public project: Project) {
this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
constructor(private host: ServerHost, private project: Project) {
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<TimestampedResolvedTypeReferenceDirective>>();
this.filenameToScript = createFileMap<ScriptInfo>();
this.moduleResolutionHost = {
fileExists: fileName => this.fileExists(fileName),
readFile: fileName => this.host.readFile(fileName),
@ -163,7 +156,7 @@ namespace ts.server {
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResult: (s: T) => R): R[] {
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.project.getCanonicalFileName);
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = {};
@ -225,13 +218,8 @@ namespace ts.server {
return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
}
getScriptInfoForRootFile(fileName: string): ScriptInfo {
const path = toPath(fileName, this.host.getCurrentDirectory(), this.getCanonicalFileName);
return this.filenameToScript.get(path);
}
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
const scriptInfo = this.getScriptInfo(filename);
const scriptInfo = this.project.getScriptInfo(filename);
if (scriptInfo) {
return scriptInfo.snap();
}
@ -250,11 +238,11 @@ namespace ts.server {
}
getScriptFileNames() {
return this.roots.map(root => root.fileName);
return this.project.getRootFiles();
}
getScriptKind(fileName: string) {
const info = this.getScriptInfo(fileName);
const info = this.project.getScriptInfo(fileName);
if (!info) {
return undefined;
}
@ -266,7 +254,7 @@ namespace ts.server {
}
getScriptVersion(filename: string) {
return this.getScriptInfo(filename).svc.latestVersion().toString();
return this.project.getScriptInfo(filename).svc.latestVersion().toString();
}
getCurrentDirectory(): string {
@ -275,73 +263,22 @@ namespace ts.server {
removeReferencedFile(info: ScriptInfo) {
if (!info.isOpen) {
this.filenameToScript.remove(info.path);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
}
getScriptInfo(filename: string): ScriptInfo {
const path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName);
let scriptInfo = this.filenameToScript.get(path);
if (!scriptInfo) {
scriptInfo = this.project.openReferencedFile(filename);
if (scriptInfo) {
this.filenameToScript.set(path, scriptInfo);
}
}
return scriptInfo;
}
addRoot(info: ScriptInfo) {
if (!this.filenameToScript.contains(info.path)) {
this.filenameToScript.set(info.path, info);
this.roots.push(info);
}
}
removeRoot(info: ScriptInfo) {
if (!this.filenameToScript.contains(info.path)) {
this.filenameToScript.remove(info.path);
this.roots = copyListRemovingItem(info, this.roots);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
}
saveTo(filename: string, tmpfilename: string) {
const script = this.getScriptInfo(filename);
if (script) {
const snap = script.snap();
this.host.writeFile(tmpfilename, snap.getText(0, snap.getLength()));
}
}
reloadScript(filename: string, tmpfilename: string, cb: () => any) {
const script = this.getScriptInfo(filename);
if (script) {
script.svc.reloadFromFile(tmpfilename, cb);
}
}
editScript(filename: string, start: number, end: number, newText: string) {
const script = this.getScriptInfo(filename);
if (script) {
script.editContent(start, end, newText);
return;
}
throw new Error("No script with name '" + filename + "'");
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
resolvePath(path: string): string {
const result = this.host.resolvePath(path);
return result;
return this.host.resolvePath(path);
}
fileExists(path: string): boolean {
const result = this.host.fileExists(path);
return result;
return this.host.fileExists(path);
}
directoryExists(path: string): boolean {
@ -356,7 +293,7 @@ namespace ts.server {
}
export class Project {
lsHost: LSHost;
private lsHost: LSHost;
languageService: LanguageService;
projectFilename: string;
projectFileWatcher: FileWatcher;
@ -369,7 +306,14 @@ namespace ts.server {
/** Used for configured projects which may have multiple open roots */
openRefCount = 0;
getCanonicalFileName: (fileName: string) => string;
private rootFiles: ScriptInfo[] = [];
private pathToScriptInfo: ts.FileMap<ScriptInfo>;
constructor(public projectService: ProjectService, documentRegistry: ts.DocumentRegistry, public projectOptions?: ProjectOptions) {
this.pathToScriptInfo = ts.createFileMap<ScriptInfo>();
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames);
if (projectOptions && projectOptions.files) {
// If files are listed explicitly, allow all extensions
projectOptions.compilerOptions.allowNonTsExtensions = true;
@ -401,7 +345,7 @@ namespace ts.server {
}
getRootFiles() {
return this.lsHost.roots.map(info => info.fileName);
return this.rootFiles.map(info => info.fileName);
}
getFileNames() {
@ -423,11 +367,14 @@ namespace ts.server {
}
isRoot(info: ScriptInfo) {
return this.lsHost.roots.some(root => root === info);
return this.rootFiles.some(root => root === info);
}
removeReferencedFile(info: ScriptInfo) {
this.lsHost.removeReferencedFile(info);
if (!info.isOpen) {
this.pathToScriptInfo.remove(info.path);
this.lsHost.removeReferencedFile(info);
}
this.updateGraph();
}
@ -456,12 +403,31 @@ namespace ts.server {
// add a root file to project
addRoot(info: ScriptInfo) {
this.lsHost.addRoot(info);
if (!this.pathToScriptInfo.contains(info.path)) {
this.pathToScriptInfo.set(info.path, info);
this.rootFiles.push(info);
}
}
// remove a root file from project
removeRoot(info: ScriptInfo) {
this.lsHost.removeRoot(info);
if (!this.pathToScriptInfo.contains(info.path)) {
this.pathToScriptInfo.remove(info.path);
this.rootFiles = copyListRemovingItem(info, this.rootFiles);
this.lsHost.removeRoot(info);
}
}
getScriptInfo(fileName: string) {
const path = toPath(fileName, this.projectService.host.getCurrentDirectory(), this.getCanonicalFileName);
let scriptInfo = this.pathToScriptInfo.get(path);
if (!scriptInfo) {
scriptInfo = this.openReferencedFile(fileName);
if (scriptInfo) {
this.pathToScriptInfo.set(path, scriptInfo);
}
}
return scriptInfo;
}
filesToString() {
@ -478,6 +444,30 @@ namespace ts.server {
this.lsHost.setCompilationSettings(projectOptions.compilerOptions);
}
}
saveTo(filename: string, tmpfilename: string) {
const script = this.getScriptInfo(filename);
if (script) {
const snap = script.snap();
this.projectService.host.writeFile(tmpfilename, snap.getText(0, snap.getLength()));
}
}
reloadScript(filename: string, tmpfilename: string, cb: () => any) {
const script = this.getScriptInfo(filename);
if (script) {
script.svc.reloadFromFile(tmpfilename, cb);
}
}
editScript(filename: string, start: number, end: number, newText: string) {
const script = this.getScriptInfo(filename);
if (script) {
script.editContent(start, end, newText);
return;
}
throw new Error("No script with name '" + filename + "'");
}
}
export interface ProjectOpenResult {
@ -1300,7 +1290,7 @@ namespace ts.server {
return errors;
}
else {
const oldFileNames = project.lsHost.roots.map(info => info.fileName);
const oldFileNames = project.getRootFiles();
const newFileNames = ts.filter(projectOptions.files, f => this.host.fileExists(f));
const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0);
const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0);

View File

@ -69,7 +69,7 @@ namespace ts.server {
}
function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic): protocol.Diagnostic {
const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName);
const scriptInfo = project.getScriptInfo(fileName);
return {
start: scriptInfo.positionToLineOffset(diag.start),
end: scriptInfo.positionToLineOffset(diag.start + diag.length),
@ -318,7 +318,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const definitions = project.languageService.getDefinitionAtPosition(file, position);
@ -327,12 +327,12 @@ namespace ts.server {
}
return definitions.map(def => {
const defScriptInfo = project.lsHost.getScriptInfoForRootFile(def.fileName);
const defScriptInfo = project.getScriptInfo(def.fileName);
return {
file: def.fileName,
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
}
};
});
}
@ -343,7 +343,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const definitions = project.languageService.getTypeDefinitionAtPosition(file, position);
@ -352,12 +352,12 @@ namespace ts.server {
}
return definitions.map(def => {
const defScriptInfo = project.lsHost.getScriptInfoForRootFile(def.fileName);
const defScriptInfo = project.getScriptInfo(def.fileName);
return {
file: def.fileName,
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
}
};
});
}
@ -369,7 +369,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfo(fileName);
const scriptInfo = project.getScriptInfo(fileName);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const occurrences = project.languageService.getOccurrencesAtPosition(fileName, position);
@ -380,7 +380,7 @@ namespace ts.server {
return occurrences.map(occurrence => {
const { fileName, isWriteAccess, textSpan } = occurrence;
const scriptInfo = project.lsHost.getScriptInfo(fileName);
const scriptInfo = project.getScriptInfo(fileName);
const start = scriptInfo.positionToLineOffset(textSpan.start);
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan));
return {
@ -400,7 +400,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfo(fileName);
const scriptInfo = project.getScriptInfo(fileName);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, filesToSearch);
@ -414,7 +414,7 @@ namespace ts.server {
function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem {
const { fileName, highlightSpans } = documentHighlights;
const scriptInfo = project.lsHost.getScriptInfo(fileName);
const scriptInfo = project.getScriptInfo(fileName);
return {
file: fileName,
highlightSpans: highlightSpans.map(convertHighlightSpan)
@ -454,7 +454,7 @@ namespace ts.server {
const defaultProject = projects[0];
// The rename info should be the same for every project
const scriptInfo = defaultProject.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = defaultProject.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const renameInfo = defaultProject.languageService.getRenameInfo(file, position);
if (!renameInfo) {
@ -477,12 +477,12 @@ namespace ts.server {
}
return renameLocations.map(location => {
const locationScriptInfo = project.lsHost.getScriptInfoForRootFile(location.fileName);
const locationScriptInfo = project.getScriptInfo(location.fileName);
return <protocol.FileSpan>{
file: location.fileName,
start: locationScriptInfo.positionToLineOffset(location.textSpan.start),
end: locationScriptInfo.positionToLineOffset(ts.textSpanEnd(location.textSpan)),
}
};
});
},
compareRenameLocation,
@ -537,7 +537,7 @@ namespace ts.server {
}
const defaultProject = projects[0];
const scriptInfo = defaultProject.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = defaultProject.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const nameInfo = defaultProject.languageService.getQuickInfoAtPosition(file, position);
if (!nameInfo) {
@ -557,7 +557,7 @@ namespace ts.server {
}
return references.map(ref => {
const refScriptInfo = project.lsHost.getScriptInfoForRootFile(ref.fileName);
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, "");
@ -610,7 +610,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const quickInfo = project.languageService.getQuickInfoAtPosition(file, position);
if (!quickInfo) {
@ -636,7 +636,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const startPosition = scriptInfo.lineOffsetToPosition(line, offset);
const endPosition = scriptInfo.lineOffsetToPosition(endLine, endOffset);
@ -664,7 +664,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const formatOptions = this.projectService.getFormatCodeOptions(file);
const edits = project.languageService.getFormattingEditsAfterKeystroke(file, position, key,
@ -737,7 +737,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const completions = project.languageService.getCompletionsAtPosition(file, position);
@ -761,7 +761,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
return entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => {
@ -780,7 +780,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const helpItems = project.languageService.getSignatureHelpItems(file, position);
if (!helpItems) {
@ -821,7 +821,7 @@ namespace ts.server {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (project) {
const scriptInfo = project.lsHost.getScriptInfoForRootFile(file);
const scriptInfo = project.getScriptInfo(file);
const start = scriptInfo.lineOffsetToPosition(line, offset);
const end = scriptInfo.lineOffsetToPosition(endLine, endOffset);
if (start >= 0) {
@ -839,7 +839,7 @@ namespace ts.server {
if (project) {
this.changeSeq++;
// make sure no changes happen before this one is finished
project.lsHost.reloadScript(file, tmpfile, () => {
project.reloadScript(file, tmpfile, () => {
this.output(undefined, CommandNames.Reload, reqSeq);
});
}
@ -851,7 +851,7 @@ namespace ts.server {
const project = this.projectService.getProjectForFile(file);
if (project) {
project.lsHost.saveTo(file, tmpfile);
project.saveTo(file, tmpfile);
}
}
@ -868,7 +868,7 @@ namespace ts.server {
return undefined;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName);
const scriptInfo = project.getScriptInfo(fileName);
return items.map(item => ({
text: item.text,
@ -915,7 +915,7 @@ namespace ts.server {
}
return navItems.map((navItem) => {
const scriptInfo = project.lsHost.getScriptInfoForRootFile(navItem.fileName);
const scriptInfo = project.getScriptInfo(navItem.fileName);
const start = scriptInfo.positionToLineOffset(navItem.textSpan.start);
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
@ -963,7 +963,7 @@ namespace ts.server {
throw Errors.NoProject;
}
const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName);
const scriptInfo = project.getScriptInfo(fileName);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const spans = project.languageService.getBraceMatchingAtPosition(file, position);