diff --git a/src/server/client.ts b/src/server/client.ts index 60486e80371..4cee592fb6c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -208,9 +208,9 @@ module ts.server { return response.body[0]; } - getNavigateToItems(searchTerm: string): NavigateToItem[] { + getNavigateToItems(searchValue: string): NavigateToItem[] { var args: protocol.NavtoRequestArgs = { - searchTerm, + searchValue, file: this.host.getScriptFileNames()[0] }; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5b5ee27d707..d7eda52ce38 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -65,6 +65,7 @@ module ts.server { ls: ts.LanguageService = null; compilationSettings: ts.CompilerOptions; filenameToScript: ts.Map = {}; + roots: ScriptInfo[] = []; constructor(public host: ServerHost, public project: Project) { } @@ -144,7 +145,7 @@ module ts.server { var scriptInfo = ts.lookUp(this.filenameToScript, info.fileName); if (!scriptInfo) { this.filenameToScript[info.fileName] = info; - return info; + this.roots.push(info); } } @@ -286,10 +287,12 @@ module ts.server { return this.filenameToSourceFile[info.fileName]; } - getSourceFileFromName(filename: string) { + getSourceFileFromName(filename: string, requireOpen?: boolean) { var info = this.projectService.getScriptInfo(filename); if (info) { - return this.getSourceFile(info); + if ((!requireOpen) || info.isOpen) { + return this.getSourceFile(info); + } } } @@ -324,7 +327,7 @@ module ts.server { // add a root file to project addRoot(info: ScriptInfo) { info.defaultProject = this; - return this.compilerService.host.addRoot(info); + this.compilerService.host.addRoot(info); } filesToString() { @@ -360,7 +363,7 @@ module ts.server { } interface ProjectServiceEventHandler { - (eventName: string, project: Project): void; + (eventName: string, project: Project, fileName: string): void; } export class ProjectService { @@ -392,7 +395,6 @@ module ts.server { } } - log(msg: string, type = "Err") { this.psLogger.msg(msg, type); } @@ -423,7 +425,20 @@ module ts.server { for (var i = 0, len = referencingProjects.length; i < len; i++) { referencingProjects[i].removeReferencedFile(info); } + for (var j = 0, flen = this.openFileRoots.length; j < flen; j++) { + var openFile = this.openFileRoots[j]; + if (this.eventHandler) { + this.eventHandler("context", openFile.defaultProject, openFile.fileName); + } + } + for (var j = 0, flen = this.openFilesReferenced.length; j < flen; j++) { + var openFile = this.openFilesReferenced[j]; + if (this.eventHandler) { + this.eventHandler("context", openFile.defaultProject, openFile.fileName); + } + } } + this.printProjects(); } @@ -503,19 +518,52 @@ module ts.server { info.close(); } - findReferencingProjects(info: ScriptInfo) { + findReferencingProjects(info: ScriptInfo, excludedProject?: Project) { var referencingProjects: Project[] = []; info.defaultProject = undefined; for (var i = 0, len = this.inferredProjects.length; i < len; i++) { this.inferredProjects[i].updateGraph(); - if (this.inferredProjects[i].getSourceFile(info)) { - info.defaultProject = this.inferredProjects[i]; - referencingProjects.push(this.inferredProjects[i]); + if (this.inferredProjects[i] != excludedProject) { + if (this.inferredProjects[i].getSourceFile(info)) { + info.defaultProject = this.inferredProjects[i]; + referencingProjects.push(this.inferredProjects[i]); + } } } return referencingProjects; } + updateProjectStructure() { + this.log("updating project structure from ...", "Info"); + this.printProjects(); + for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { + var refdFile = this.openFilesReferenced[i]; + refdFile.defaultProject.updateGraph(); + var sourceFile = refdFile.defaultProject.getSourceFile(refdFile); + if (!sourceFile) { + this.openFilesReferenced = copyListRemovingItem(refdFile, this.openFilesReferenced); + this.addOpenFile(refdFile); + } + } + var openFileRoots: ScriptInfo[] = []; + for (var i = 0, len = this.openFileRoots.length; i < len; i++) { + var rootFile = this.openFileRoots[i]; + var rootedProject = rootFile.defaultProject; + var referencingProjects = this.findReferencingProjects(rootFile, rootedProject); + if (referencingProjects.length == 0) { + rootFile.defaultProject = rootedProject; + openFileRoots.push(rootFile); + } + else { + // remove project from inferred projects list + this.inferredProjects = copyListRemovingItem(rootedProject, this.inferredProjects); + this.openFilesReferenced.push(rootFile); + } + } + this.openFileRoots = openFileRoots; + this.printProjects(); + } + getScriptInfo(filename: string) { filename = ts.normalizePath(filename); return ts.lookUp(this.filenameToScriptInfo, filename); @@ -621,6 +669,7 @@ module ts.server { this.psLogger.startGroup(); for (var i = 0, len = this.inferredProjects.length; i < len; i++) { var project = this.inferredProjects[i]; + project.updateGraph(); this.psLogger.info("Project " + i.toString()); this.psLogger.info(project.filesToString()); this.psLogger.info("-----------------------------------------------"); diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index b07fb20d48d..a4541d2666d 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -676,7 +676,11 @@ declare module ts.server.protocol { * Search term to navigate to from current location; term can * be '.*' or an identifier prefix. */ - searchTerm: string; + searchValue: string; + /** + * Optional limit on the number of items to return. + */ + maxResultCount?: number; } /** diff --git a/src/server/server.ts b/src/server/server.ts index 8921a4b4768..2b9040b4df3 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -82,7 +82,6 @@ module ts.server { private watchedFiles: WatchedFile[] = []; private nextFileToCheck = 0; private watchTimer: NodeJS.Timer; - private static fileDeleted = 34; // average async stat takes about 30 microseconds // set chunk size to do 30 files in < 1 millisecond @@ -111,13 +110,7 @@ module ts.server { fs.stat(watchedFile.fileName,(err, stats) => { if (err) { - var msg = err.message; - if (err.errno) { - msg += " errno: " + err.errno.toString(); - } - if (err.errno == WatchedFileSet.fileDeleted) { - watchedFile.callback(watchedFile.fileName); - } + watchedFile.callback(watchedFile.fileName); } else if (watchedFile.mtime.getTime() != stats.mtime.getTime()) { watchedFile.mtime = WatchedFileSet.getModifiedTime(watchedFile.fileName); diff --git a/src/server/session.ts b/src/server/session.ts index 19f6cd3645e..310f7950bb3 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -52,30 +52,6 @@ module ts.server { return 1; } } - - function sortNavItems(items: ts.NavigateToItem[]) { - return items.sort((a, b) => { - if (a.matchKind < b.matchKind) { - return -1; - } - else if (a.matchKind == b.matchKind) { - var lowa = a.name.toLowerCase(); - var lowb = b.name.toLowerCase(); - if (lowa < lowb) { - return -1; - } - else if (lowa == lowb) { - return 0; - } - else { - return 1; - } - } - else { - return 1; - } - }) - } function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) { return { @@ -138,7 +114,18 @@ module ts.server { changeSeq = 0; constructor(private host: ServerHost, private logger: Logger) { - this.projectService = new ProjectService(host, logger); + this.projectService = + new ProjectService(host, logger, (eventName,project,fileName) => { + this.handleEvent(eventName, project, fileName); + }); + } + + handleEvent(eventName: string, project: Project, fileName: string) { + if (eventName == "context") { + this.projectService.log("got context event, updating diagnostics for" + fileName, "Info"); + this.updateErrorCheck([{ fileName, project }], this.changeSeq, + (n) => n == this.changeSeq, 100); + } } logError(err: Error, cmd: string) { @@ -215,6 +202,14 @@ module ts.server { this.semanticCheck(file, project); } + updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { + setTimeout(() => { + if (matchSeq(seq)) { + this.projectService.updateProjectStructure(); + } + }, ms); + } + updateErrorCheck(checkList: PendingErrorCheck[], seq: number, matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200) { if (followMs > ms) { @@ -231,7 +226,7 @@ module ts.server { var checkOne = () => { if (matchSeq(seq)) { var checkSpec = checkList[index++]; - if (checkSpec.project.getSourceFileFromName(checkSpec.fileName)) { + if (checkSpec.project.getSourceFileFromName(checkSpec.fileName, true)) { this.syntacticCheck(checkSpec.fileName, checkSpec.project); this.immediateId = setImmediate(() => { this.semanticCheck(checkSpec.fileName, checkSpec.project); @@ -404,7 +399,7 @@ module ts.server { var position = compilerService.host.lineColToPosition(file, line, col); var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, position); if (!quickInfo) { - throw Errors.NoContent; + return undefined; } var displayString = ts.displayPartsToString(quickInfo.displayParts); @@ -494,7 +489,7 @@ module ts.server { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { - throw Errors.NoProject; + return undefined; } var compilerService = project.compilerService; @@ -559,6 +554,10 @@ module ts.server { compilerService.host.editScript(file, start, end, insertString); this.changeSeq++; } + // update project structure on idle commented out + // until we can have the host return only the root files + // from getScriptFileNames() + //this.updateProjectStructure(this.changeSeq, (n) => n == this.changeSeq); } } @@ -625,7 +624,7 @@ module ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - getNavigateToItems(searchTerm: string, fileName: string): protocol.NavtoItem[] { + getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -633,7 +632,7 @@ module ts.server { } var compilerService = project.compilerService; - var navItems = sortNavItems(compilerService.languageService.getNavigateToItems(searchTerm)); + var navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); if (!navItems) { throw Errors.NoContent; } @@ -690,6 +689,7 @@ module ts.server { try { var request = JSON.parse(message); var response: any; + var errorMessage: string; switch (request.command) { case CommandNames.Definition: { var defArgs = request.arguments; @@ -714,6 +714,9 @@ module ts.server { case CommandNames.Quickinfo: { var quickinfoArgs = request.arguments; response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); + if (!response) { + errorMessage = "No info at this location"; + } break; } case CommandNames.Format: { @@ -729,6 +732,9 @@ module ts.server { case CommandNames.Completions: { var completionsArgs = request.arguments; response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); + if (!response) { + errorMessage = "No completions at this location"; + } break; } case CommandNames.CompletionDetails: { @@ -765,7 +771,7 @@ module ts.server { } case CommandNames.Navto: { var navtoArgs = request.arguments; - response = this.getNavigateToItems(navtoArgs.searchTerm, navtoArgs.file); + response = this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount); break; } case CommandNames.Brace: { @@ -788,6 +794,9 @@ module ts.server { if (response) { this.output(response, request.command, request.seq); } + else if (errorMessage) { + this.output(undefined, request.command, request.seq, errorMessage); + } } catch (err) { if (err instanceof OperationCanceledException) {