From acd4914c0e1f745e5699e224e15706f804c241ea Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 31 Jan 2015 21:14:28 -0800 Subject: [PATCH 01/45] Rename the main node package filename to match the package name --- Jakefile | 8 ++++++-- package.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Jakefile b/Jakefile index 15661debff62b..2d6db20d38679 100644 --- a/Jakefile +++ b/Jakefile @@ -323,6 +323,7 @@ var tscFile = path.join(builtLocalDirectory, compilerFilename); compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false); var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js"); +var nodePackageFile = path.join(builtLocalDirectory, "typescript.js"); compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].concat(servicesSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, @@ -331,7 +332,10 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca /*outDir*/ undefined, /*preserveConstEnums*/ true, /*keepComments*/ false, - /*noResolve*/ false); + /*noResolve*/ false, + /*callback*/ function () { + jake.cpR(servicesFile, nodePackageFile, {silent: true}); + }); var nodeDefinitionsFile = path.join(builtLocalDirectory, "typescript.d.ts"); var standaloneDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts"); @@ -425,7 +429,7 @@ task("generate-spec", [specMd]) // Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory desc("Makes a new LKG out of the built js files"); task("LKG", ["clean", "release", "local"].concat(libraryTargets), function() { - var expectedFiles = [tscFile, servicesFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets); + var expectedFiles = [tscFile, servicesFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, internalNodeDefinitionsFile, internalStandaloneDefinitionsFile].concat(libraryTargets); var missingFiles = expectedFiles.filter(function (f) { return !fs.existsSync(f); }); diff --git a/package.json b/package.json index 2bd8970660631..94b5435be1965 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/Microsoft/TypeScript.git" }, "preferGlobal": true, - "main": "./bin/typescriptServices.js", + "main": "./bin/typescript.js", "bin": { "tsc": "./bin/tsc" }, From 9735b74def6e1f6e35788bc9a09ca8323ad7eb41 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 16:12:33 -0800 Subject: [PATCH 02/45] Add support for stat and modified time on sys --- src/compiler/sys.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 5f10a747d421e..c666ca991cfe1 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -18,6 +18,8 @@ module ts { readDirectory(path: string, extension?: string): string[]; getMemoryUsage? (): number; exit(exitCode?: number): void; + getModififedTime? (fileName: string): Date; + stat? (fileName: string, callback?: (err: any, stats: any) => any): void; } export interface FileWatcher { @@ -303,6 +305,13 @@ module ts { }, exit(exitCode?: number): void { process.exit(exitCode); + }, + getModififedTime(fileName: string): Date { + var stats = _fs.statSync(fileName); + return stats.mtime; + }, + stat(fileName: string, callback?: (err: any, stats: any) => any) { + _fs.stat(fileName, callback); } }; } From 27a908478570c4a36d0a80f7f94d043b34238969 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 16:13:04 -0800 Subject: [PATCH 03/45] Add indexer on the Formatting options interface --- src/services/services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/services.ts b/src/services/services.ts index 56e68b2f5a138..05884b9bb987f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -991,6 +991,7 @@ module ts { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + [s: string]: boolean | number| string; } export interface DefinitionInfo { From 17f19b26a03a2f7fc1c5c35214c27d174e05f375 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 19:42:44 -0800 Subject: [PATCH 04/45] Initial checkin for server code --- src/server/client.ts | 392 +++++++ src/server/editorServices.ts | 1850 ++++++++++++++++++++++++++++++++++ src/server/node.d.ts | 677 +++++++++++++ src/server/protocol.ts | 1309 ++++++++++++++++++++++++ src/server/protodef.d.ts | 591 +++++++++++ src/server/server.ts | 134 +++ 6 files changed, 4953 insertions(+) create mode 100644 src/server/client.ts create mode 100644 src/server/editorServices.ts create mode 100644 src/server/node.d.ts create mode 100644 src/server/protocol.ts create mode 100644 src/server/protodef.d.ts create mode 100644 src/server/server.ts diff --git a/src/server/client.ts b/src/server/client.ts new file mode 100644 index 0000000000000..1fb93329e03d5 --- /dev/null +++ b/src/server/client.ts @@ -0,0 +1,392 @@ +/// + +module ts.server { + + export interface SessionClientHost extends LanguageServiceHost { + lineColToPosition(fileName: string, line: number, col: number): number; + positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter; + } + + export class SessionClient implements LanguageService { + private session: Session; + private sequence: number; + private lastReply: string; + + constructor(private host: SessionClientHost) { + this.sequence = 0; + + this.session = new Session({ + args: [], + newLine: host.getNewLine(), + useCaseSensitiveFileNames: true, + write: (s) => this.lastReply = s, + readFile: (fileName): string => { + var snapshot = host.getScriptSnapshot(fileName); + return snapshot && snapshot.getText(0, snapshot.getLength()); + }, + writeFile: (name, text, writeByteOrderMark) => { + }, + resolvePath: (path) => path, + fileExists: (path) => !!host.getScriptSnapshot(path), + directoryExists: (path) => false, + getExecutingFilePath: () => "", + exit: (exitCode) => { }, + createDirectory: (directoryName: string) => { }, + + getCurrentDirectory: () => host.getCurrentDirectory(), + readDirectory: (path: string, extension?: string) => [], + getModififedTime: (fileName) => new Date(), + stat: (path, callback) => { throw new Error("Not implemented Yet."); }, + }, { + close: () => { }, + info: (m) => this.host.log(m), + msg: (m) => this.host.log(m), + endGroup: () => { }, + perftrc: (m) => this.host.log(m), + startGroup: () => { } + }, /* useProtocol */ true, /*prettyJSON*/ true); + } + + private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { + return this.host.lineColToPosition(fileName, lineCol.line, lineCol.col); + } + + private getFileLength(fileName: string): number { + return this.host.getScriptSnapshot(fileName).getLength(); + } + + private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { + var lineCol = this.host.positionToZeroBasedLineCol(fileName, position); + return { + line: lineCol.line + 1, + col: lineCol.character + 1 + }; + } + + private convertCodeEditsToTextChange(fileName: string, codeEdit: ServerProtocol.CodeEdit): ts.TextChange { + var start = this.lineColToPosition(fileName, codeEdit.start); + var end = this.lineColToPosition(fileName, codeEdit.end); + + return { + span: ts.createTextSpanFromBounds(start, end), + newText: codeEdit.newText + }; + } + + private processRequest(command: "open", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.OpenRequest; + private processRequest(command: "close", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.CloseRequest; + private processRequest(command: "change", arguments: ServerProtocol.ChangeRequestArgs): ServerProtocol.ChangeRequest; + private processRequest(command: "quickinfo", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.QuickInfoRequest; + private processRequest(command: "format", arguments: ServerProtocol.FormatRequestArgs): ServerProtocol.FormatRequest; + private processRequest(command: "formatonkey", arguments: ServerProtocol.FormatOnKeyRequestArgs): ServerProtocol.FormatRequest; + private processRequest(command: "definition", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.DefinitionRequest; + private processRequest(command: "references", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.ReferencesRequest; + private processRequest(command: "completions", arguments: ServerProtocol.CompletionsRequestArgs): ServerProtocol.CompletionsRequest; + private processRequest(command: "navto", arguments: ServerProtocol.NavtoRequestArgs): ServerProtocol.NavtoRequest; + private processRequest(command: "saveto", arguments: ServerProtocol.SavetoRequestArgs): ServerProtocol.SavetoRequest; + private processRequest(command: string, arguments: any): ServerProtocol.Request; + private processRequest(command: string, arguments: any): ServerProtocol.Request { + var request: ServerProtocol.Request = { + seq: this.sequence++, + type: "request", + command: command, + arguments: arguments + }; + + this.session.executeJSONcmd(JSON.stringify(request)); + + return request; + } + + private processResponse(request: ServerProtocol.Request): T { + debugger; + + // Read the content length + var contentLengthPrefix = "Content-Length: "; + var lines = this.lastReply.split("\r\n"); + Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); + + var contentLengthText = lines[0]; + Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); + var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); + + // Read the body + var responseBody = lines[2]; + + // Verify content length + Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); + + try { + var response: T = JSON.parse(responseBody); + } + catch (e) { + throw new Error("Malformed response: Failed to parse server response: " + this.lastReply + ". \r\n Error detailes: " + e.message); + } + + // verify the sequence numbers + Debug.assert(response.request_seq === request.seq, "Malformed response: response sequance number did not match request sequence number."); + + // unmarshal errors + if (!response.success) { + throw new Error("Error " + response.message); + } + + Debug.assert(!!response.body, "Malformed response: Unexpected empty response body."); + + return response; + } + + openFile(fileName: string): void { + this.processRequest("open", { + file: fileName + }); + } + + closeFile(fileName: string): void { + this.processRequest("close", { + file: fileName + }); + } + + changeFile(fileName: string, start: number, end: number, newText: string): void { + var lineCol = this.positionToOneBasedLineCol(fileName, start); + + this.processRequest("change", { + file: fileName, + line: lineCol.line, + col: lineCol.col, + insertLen: end - start, + deleteLen: end - start, + insertString: newText + }); + } + + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { + var request = this.processRequest("quickinfo", { + file: fileName, + line: 0, + col: 1 + }); + + var response = this.processResponse(request); + + var start = this.lineColToPosition(fileName, response.body.start); + var end = this.lineColToPosition(fileName, response.body.end); + + return { + kind: response.body.kind, + kindModifiers: response.body.kindModifiers, + textSpan: ts.createTextSpanFromBounds(start, end), + displayParts: undefined, + documentation: undefined, + documentationString: response.body.documentation, + displayString: response.body.displayString + }; + } + + getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var request = this.processRequest("completions", { + file: fileName, + line: lineCol.line, + col: lineCol.col, + }); + + var response = this.processResponse(request); + + return { + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: response.body.map(entry => ({ kind: entry.kind, kindModifiers: entry.kindModifiers, name: entry.name })) + }; + } + + getNavigateToItems(seatchTerm: string): NavigateToItem[] { + var request = this.processRequest("navto", { seatchTerm }); + + var response = this.processResponse(request); + return response.body.map(entry => { + var start = this.lineColToPosition(entry.file.toString(), entry.start); + var end = this.lineColToPosition(entry.file.toString(), entry.end); + return { + name: entry.name, + containerName: entry.containerName, + containerKind: entry.containerKind, + kind: entry.kind, + kindModifiers: entry.kindModifiers, + matchKind: entry.matchKind, + fileName: entry.file.toString(), + textSpan: ts.createTextSpanFromBounds(start, end) + }; + }); + } + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { + var startLineCol = this.positionToOneBasedLineCol(fileName, start); + var endLineCol = this.positionToOneBasedLineCol(fileName, end); + // TODO: handle FormatCodeOptions + var request = this.processRequest("format", { + file: fileName, + line: startLineCol.line, + col: startLineCol.col, + endLine: endLineCol.line, + endCol: endLineCol.col, + }); + + var response = this.processResponse(request); + + return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); + } + + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return this.getFormattingEditsForRange(fileName, 0, this.getFileLength(fileName), options); + } + + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { + var lineCol = this.positionToOneBasedLineCol(fileName, position); + // TODO: handle FormatCodeOptions + var request = this.processRequest("formatonkey", { + file: fileName, + line: lineCol.line, + col: lineCol.col, + key: key + }); + + var response = this.processResponse(request); + return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); + } + + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var request = this.processRequest("definition", { + file: fileName, + line: lineCol.line, + col: lineCol.col, + }); + + var response = this.processResponse(request); + return response.body.map(entry => { + var start = this.lineColToPosition(fileName, entry.start); + var end = this.lineColToPosition(fileName, entry.end); + return { + containerKind: "", + containerName: "", + fileName: entry.file, + textSpan: ts.createTextSpanFromBounds(start, end), + kind: "", + name: "" + }; + }); + } + + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var request = this.processRequest("references", { + file: fileName, + line: lineCol.line, + col: lineCol.col, + }); + + var response = this.processResponse(request); + + return response.body.refs.map(entry => { + var start = this.lineColToPosition(fileName, entry.start); + var end = this.lineColToPosition(fileName, entry.end); + return { + fileName: entry.file, + textSpan: ts.createTextSpanFromBounds(start, end), + isWriteAccess: false, + }; + }); + } + + getEmitOutput(fileName: string): EmitOutput { + throw new Error("Not Implemented Yet."); + } + + getSyntacticDiagnostics(fileName: string): Diagnostic[] { + throw new Error("Not Implemented Yet."); + } + + getSemanticDiagnostics(fileName: string): Diagnostic[] { + throw new Error("Not Implemented Yet."); + } + + getCompilerOptionsDiagnostics(): Diagnostic[] { + throw new Error("Not Implemented Yet."); + } + + getRenameInfo(fileName: string, position: number): RenameInfo { + throw new Error("Not Implemented Yet."); + } + + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { + throw new Error("Not Implemented Yet."); + } + + getNavigationBarItems(fileName: string): NavigationBarItem[] { + throw new Error("Not Implemented Yet."); + } + + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan { + throw new Error("Not Implemented Yet."); + } + + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan { + throw new Error("Not Implemented Yet."); + } + + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems { + throw new Error("Not Implemented Yet."); + } + + getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + throw new Error("Not Implemented Yet."); + } + + getOutliningSpans(fileName: string): OutliningSpan[] { + throw new Error("Not Implemented Yet."); + } + + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { + throw new Error("Not Implemented Yet."); + } + + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { + throw new Error("Not Implemented Yet."); + } + + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number { + throw new Error("Not Implemented Yet."); + } + + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { + throw new Error("Not Implemented Yet."); + } + + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { + throw new Error("Not Implemented Yet."); + } + + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { + throw new Error("Not Implemented Yet."); + } + + + getProgram(): Program { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + getSourceFile(fileName: string): SourceFile { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + cleanupSemanticCache(): void { + throw new Error("cleanupSemanticCache is not available through the server layer."); + } + + dispose(): void { + throw new Error("dispose is not available through the server layer."); + } + } +} diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts new file mode 100644 index 0000000000000..933435f93a1e9 --- /dev/null +++ b/src/server/editorServices.ts @@ -0,0 +1,1850 @@ +/// +/// +/// + +module ts.server { + export interface Logger { + close(): void; + perftrc(s: string): void; + info(s: string): void; + startGroup(): void; + endGroup(): void; + msg(s: string, type?: string): void; + } + + var measurePerf = false; + var lineCollectionCapacity = 4; + var indentStrings: string[] = []; + var indentBase = " "; + function getIndent(indentAmt: number) { + if (!indentStrings[indentAmt]) { + indentStrings[indentAmt] = ""; + for (var i = 0; i < indentAmt; i++) { + indentStrings[indentAmt] += indentBase; + } + } + return indentStrings[indentAmt]; + } + + export function printLine(s: string) { + ts.sys.write(s + '\n'); + } + + function showLines(s: string) { + var strBuilder = ""; + for (var i = 0, len = s.length; i < len; i++) { + if (s.charCodeAt(i) == 10) { + strBuilder += '\\n'; + } + else if (s.charCodeAt(i) == 13) { + strBuilder += '\\r'; + } + else { + strBuilder += s.charAt(i); + } + } + return strBuilder; + } + + function calibrateTimer() { + var count = 20; + var total = 0; + for (var i = 0; i < count; i++) { + var start = process.hrtime(); + var elapsed = process.hrtime(start); + var elapsedNano = 1e9 * elapsed[0] + elapsed[1]; + total += elapsedNano; + } + } + + export class ScriptInfo { + svc: ScriptVersionCache; + children: ScriptInfo[] = []; // files referenced by this file + + defaultProject: Project; // project to use by default for file + mtime: Date; + + constructor(private host: ServerHost, public filename: string, public content: string, public isOpen = false) { + this.svc = ScriptVersionCache.fromString(content); + if (!isOpen) { + this.mtime = this.host.getModififedTime(filename); + } + } + + close() { + this.isOpen = false; + this.mtime = this.host.getModififedTime(this.filename); + } + + addChild(childInfo: ScriptInfo) { + this.children.push(childInfo); + } + + snap() { + return this.svc.getSnapshot(); + } + + getText() { + var snap = this.snap(); + return snap.getText(0, snap.getLength()); + } + + getLineInfo(line: number) { + var snap = this.snap(); + return snap.index.lineNumberToInfo(line); + } + + editContent(start: number, end: number, newText: string): void { + this.svc.edit(start, end - start, newText); + } + + getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { + return this.svc.getTextChangesBetweenVersions(startVersion, endVersion); + } + + getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { + return this.snap().getChangeRange(oldSnapshot); + } + } + + export class CancellationToken { + public static None = new CancellationToken(); + + requestPending = false; + + constructor() { + } + + cancel() { + this.requestPending = true; + } + + reset() { + this.requestPending = false; + } + + public isCancellationRequested() { + var temp = this.requestPending; + return temp; + } + } + + export class LSHost implements ts.LanguageServiceHost { + ls: ts.LanguageService = null; + compilationSettings: ts.CompilerOptions; + filenameToScript: ts.Map = {}; + + constructor(public host: ServerHost, public project: Project, private cancellationToken: CancellationToken = CancellationToken.None) { + } + + getDefaultLibFileName() { + var nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath())); + + if (this.compilationSettings && this.compilationSettings.target == ts.ScriptTarget.ES6) { + return nodeModuleBinDir + "/lib.es6.d.ts"; + } + else { + return nodeModuleBinDir + "/lib.d.ts"; + } + } + + cancel() { + this.cancellationToken.cancel(); + } + + reset() { + this.cancellationToken.reset(); + } + + getScriptSnapshot(filename: string): ts.IScriptSnapshot { + var scriptInfo = this.getScriptInfo(filename); + if (scriptInfo) { + return scriptInfo.snap(); + } + } + + setCompilationSettings(opt: ts.CompilerOptions) { + this.compilationSettings = opt; + } + + lineAffectsRefs(filename: string, line: number) { + var info = this.getScriptInfo(filename); + var lineInfo = info.getLineInfo(line); + if (lineInfo && lineInfo.text) { + var regex = /reference|import|\/\*|\*\//; + return regex.test(lineInfo.text); + } + } + + getCompilationSettings() { + // change this to return active project settings for file + return this.compilationSettings; + } + + getScriptFileNames() { + var filenames: string[] = []; + for (var filename in this.filenameToScript) { + if (this.filenameToScript[filename] && this.filenameToScript[filename].isOpen) { + filenames.push(filename); + } + } + return filenames; + } + + getScriptVersion(filename: string) { + return this.getScriptInfo(filename).svc.latestVersion().toString(); + } + + getCancellationToken(): ts.CancellationToken { + return this.cancellationToken; + } + + getCurrentDirectory(): string { + return ""; + } + + getScriptIsOpen(filename: string) { + return this.getScriptInfo(filename).isOpen; + } + + removeReferencedFile(info: ScriptInfo) { + if (!info.isOpen) { + this.filenameToScript[info.filename] = undefined; + } + } + + getScriptInfo(filename: string): ScriptInfo { + var scriptInfo = ts.lookUp(this.filenameToScript, filename); + if (!scriptInfo) { + scriptInfo = this.project.openReferencedFile(filename); + if (scriptInfo) { + this.filenameToScript[scriptInfo.filename] = scriptInfo; + } + } + else { + } + return scriptInfo; + } + + addRoot(info: ScriptInfo) { + var scriptInfo = ts.lookUp(this.filenameToScript, info.filename); + if (!scriptInfo) { + this.filenameToScript[info.filename] = info; + return info; + } + } + + saveTo(filename: string, tmpfilename: string) { + var script = this.getScriptInfo(filename); + if (script) { + var snap = script.snap(); + this.host.writeFile(tmpfilename, snap.getText(0, snap.getLength())); + } + } + + reloadScript(filename: string, tmpfilename: string, cb: () => any) { + var script = this.getScriptInfo(filename); + if (script) { + script.svc.reloadFromFile(tmpfilename, cb); + } + } + + editScript(filename: string, start: number, end: number, newText: string) { + var script = this.getScriptInfo(filename); + if (script) { + script.editContent(start, end, newText); + return; + } + + throw new Error("No script with name '" + filename + "'"); + } + + resolvePath(path: string): string { + var start = new Date().getTime(); + var result = this.host.resolvePath(path); + return result; + } + + fileExists(path: string): boolean { + var start = new Date().getTime(); + var result = this.host.fileExists(path); + return result; + } + + directoryExists(path: string): boolean { + return this.host.directoryExists(path); + } + + /** + * @param line 1 based index + */ + lineToTextSpan(filename: string, line: number): ts.TextSpan { + var script: ScriptInfo = this.filenameToScript[filename]; + var index = script.snap().index; + + var lineInfo = index.lineNumberToInfo(line + 1); + var len: number; + if (lineInfo.leaf) { + len = lineInfo.leaf.text.length; + } + else { + var nextLineInfo = index.lineNumberToInfo(line + 2); + len = nextLineInfo.col - lineInfo.col; + } + return ts.createTextSpan(lineInfo.col, len); + } + + /** + * @param line 1 based index + * @param col 1 based index + */ + lineColToPosition(filename: string, line: number, col: number): number { + var script: ScriptInfo = this.filenameToScript[filename]; + var index = script.snap().index; + + var lineInfo = index.lineNumberToInfo(line); + // TODO: assert this column is actually on the line + return (lineInfo.col + col - 1); + } + + /** + * @param line 1-based index + * @param col 1-based index + */ + positionToLineCol(filename: string, position: number): ILineInfo { + var script: ScriptInfo = this.filenameToScript[filename]; + var index = script.snap().index; + var lineCol = index.charOffsetToLineNumberAndPos(position); + return { line: lineCol.line, col: lineCol.col + 1 }; + } + } + + // assumes normalized paths + function getAbsolutePath(filename: string, directory: string) { + var rootLength = ts.getRootLength(filename); + if (rootLength > 0) { + return filename; + } + else { + var splitFilename = filename.split('/'); + var splitDir = directory.split('/'); + var i = 0; + var dirTail = 0; + var sflen = splitFilename.length; + while ((i < sflen) && (splitFilename[i].charAt(0) == '.')) { + var dots = splitFilename[i]; + if (dots == '..') { + dirTail++; + } + else if (dots != '.') { + return undefined; + } + i++; + } + return splitDir.slice(0, splitDir.length - dirTail).concat(splitFilename.slice(i)).join('/'); + } + } + + export interface ProjectOptions { + // these fields can be present in the project file + files?: string[]; + formatCodeOptions?: ts.FormatCodeOptions; + compilerOptions?: ts.CompilerOptions; + } + + export class Project { + compilerService: CompilerService; + projectOptions: ProjectOptions; + projectFilename: string; + program: ts.Program; + filenameToSourceFile: ts.Map = {}; + updateGraphSeq = 0; + + constructor(public projectService: ProjectService) { + this.compilerService = new CompilerService(this); + } + + openReferencedFile(filename: string) { + return this.projectService.openFile(filename, false); + } + + getSourceFile(info: ScriptInfo) { + return this.filenameToSourceFile[info.filename]; + } + + getSourceFileFromName(filename: string) { + var info = this.projectService.getScriptInfo(filename); + if (info) { + return this.getSourceFile(info); + } + } + + removeReferencedFile(info: ScriptInfo) { + this.compilerService.host.removeReferencedFile(info); + this.updateGraph(); + } + + updateFileMap() { + this.filenameToSourceFile = {}; + var sourceFiles = this.program.getSourceFiles(); + for (var i = 0, len = sourceFiles.length; i < len; i++) { + var normFilename = ts.normalizePath(sourceFiles[i].fileName); + this.filenameToSourceFile[normFilename] = sourceFiles[i]; + } + } + + finishGraph() { + this.updateGraph(); + this.compilerService.languageService.getNavigateToItems(".*"); + } + + updateGraph() { + this.program = this.compilerService.languageService.getProgram(); + this.updateFileMap(); + } + + isConfiguredProject() { + return this.projectFilename; + } + + // add a root file to project + addRoot(info: ScriptInfo) { + info.defaultProject = this; + return this.compilerService.host.addRoot(info); + } + + filesToString() { + var strBuilder = ""; + ts.forEachValue(this.filenameToSourceFile, + sourceFile => { strBuilder += sourceFile.fileName + "\n"; }); + return strBuilder; + } + + setProjectOptions(projectOptions: ProjectOptions) { + this.projectOptions = projectOptions; + if (projectOptions.compilerOptions) { + this.compilerService.setCompilerOptions(projectOptions.compilerOptions); + } + // TODO: format code options } + } + + export interface ProjectOpenResult { + success?: boolean; + errorMsg?: string; + project?: Project; + } + + function copyListRemovingItem(item: T, list: T[]) { + var copiedList: T[] = []; + for (var i = 0, len = list.length; i < len; i++) { + if (list[i] != item) { + copiedList.push(list[i]); + } + } + return copiedList; + } + + // REVIEW: for now this implementation uses polling. + // The advantage of polling is that it works reliably + // on all os and with network mounted files. + // For 90 referenced files, the average time to detect + // changes is 2*msInterval (by default 5 seconds). + // The overhead of this is .04 percent (1/2500) with + // average pause of < 1 millisecond (and max + // pause less than 1.5 milliseconds); question is + // do we anticipate reference sets in the 100s and + // do we care about waiting 10-20 seconds to detect + // changes for large reference sets? If so, do we want + // to increase the chunk size or decrease the interval + // time dynamically to match the large reference set? + export class WatchedFileSet { + watchedFiles: ScriptInfo[] = []; + nextFileToCheck = 0; + watchTimer: NodeJS.Timer; + + // average async stat takes about 30 microseconds + // set chunk size to do 30 files in < 1 millisecond + constructor(private host: ServerHost, public fileEvent: (info: ScriptInfo, eventName: string) => void, + public msInterval = 2500, public chunkSize = 30) { + } + + checkWatchedFileChanged(checkedIndex: number, stats: NodeJS.fs.Stats) { + var info = this.watchedFiles[checkedIndex]; + if (info && (!info.isOpen)) { + if (info.mtime.getTime() != stats.mtime.getTime()) { + info.svc.reloadFromFile(info.filename); + } + } + } + + fileDeleted(info: ScriptInfo) { + if (this.fileEvent) { + this.fileEvent(info, "deleted"); + } + } + + static fileDeleted = 34; + + poll(checkedIndex: number) { + var watchedFile = this.watchedFiles[checkedIndex]; + if (!watchedFile) { + return; + } + if (measurePerf) { + var start = process.hrtime(); + } + this.host.stat(watchedFile.filename,(err, stats) => { + if (err) { + var msg = err.message; + if (err.errno) { + msg += " errno: " + err.errno.toString(); + } + if (err.errno == WatchedFileSet.fileDeleted) { + this.fileDeleted(watchedFile); + } + } + else { + this.checkWatchedFileChanged(checkedIndex, stats); + } + }); + if (measurePerf) { + var elapsed = process.hrtime(start); + var elapsedNano = 1e9 * elapsed[0] + elapsed[1]; + } + } + + // this implementation uses polling and + // stat due to inconsistencies of fs.watch + // and efficiency of stat on modern filesystems + startWatchTimer() { + this.watchTimer = setInterval(() => { + var count = 0; + var nextToCheck = this.nextFileToCheck; + var firstCheck = -1; + while ((count < this.chunkSize) && (nextToCheck != firstCheck)) { + this.poll(nextToCheck); + if (firstCheck < 0) { + firstCheck = nextToCheck; + } + nextToCheck++; + if (nextToCheck == this.watchedFiles.length) { + nextToCheck = 0; + } + count++; + } + this.nextFileToCheck = nextToCheck; + }, this.msInterval); + } + + // TODO: remove watch file if opened by editor or no longer referenced + // assume normalized and absolute pathname + addFile(info: ScriptInfo) { + this.watchedFiles.push(info); + if (this.watchedFiles.length == 1) { + this.startWatchTimer(); + } + } + + removeFile(info: ScriptInfo) { + this.watchedFiles = copyListRemovingItem(info, this.watchedFiles); + } + } + + interface ProjectServiceEventHandler { + (eventName: string, project: Project): void; + } + + export class ProjectService { + filenameToScriptInfo: ts.Map = {}; + // open, non-configured files in two lists + openFileRoots: ScriptInfo[] = []; + openFilesReferenced: ScriptInfo[] = []; + // projects covering open files + inferredProjects: Project[] = []; + watchedFileSet: WatchedFileSet; + + constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { + if (measurePerf) { + calibrateTimer(); + } + ts.disableIncrementalParsing = true; + this.watchedFileSet = new WatchedFileSet(this.host,(info, eventName) => { + if (eventName == "deleted") { + this.fileDeletedInFilesystem(info); + } + }); + } + + log(msg: string, type = "Err") { + this.psLogger.msg(msg, type); + } + + closeLog() { + this.psLogger.close(); + } + + createInferredProject(root: ScriptInfo) { + var iproj = new Project(this); + iproj.addRoot(root); + iproj.finishGraph(); + this.inferredProjects.push(iproj); + return iproj; + } + + fileDeletedInFilesystem(info: ScriptInfo) { + this.psLogger.info(info.filename + " deleted"); + this.watchedFileSet.removeFile(info); + + if (!info.isOpen) { + this.filenameToScriptInfo[info.filename] = undefined; + var referencingProjects = this.findReferencingProjects(info); + for (var i = 0, len = referencingProjects.length; i < len; i++) { + referencingProjects[i].removeReferencedFile(info); + } + } + this.printProjects(); + } + + addOpenFile(info: ScriptInfo) { + this.findReferencingProjects(info); + if (info.defaultProject) { + this.openFilesReferenced.push(info); + } + else { + // create new inferred project p with the newly opened file as root + info.defaultProject = this.createInferredProject(info); + var openFileRoots: ScriptInfo[] = []; + // for each inferred project root r + for (var i = 0, len = this.openFileRoots.length; i < len; i++) { + var r = this.openFileRoots[i]; + // if r referenced by the new project + if (info.defaultProject.getSourceFile(r)) { + // remove project rooted at r + this.inferredProjects = + copyListRemovingItem(r.defaultProject, this.inferredProjects); + // put r in referenced open file list + this.openFilesReferenced.push(r); + // set default project of r to the new project + r.defaultProject = info.defaultProject; + } + else { + // otherwise, keep r as root of inferred project + openFileRoots.push(r); + } + } + this.openFileRoots = openFileRoots; + this.openFileRoots.push(info); + } + } + + closeOpenFile(info: ScriptInfo) { + var openFileRoots: ScriptInfo[] = []; + var removedProject: Project; + for (var i = 0, len = this.openFileRoots.length; i < len; i++) { + // if closed file is root of project + if (info == this.openFileRoots[i]) { + // remove that project and remember it + removedProject = info.defaultProject; + } + else { + openFileRoots.push(this.openFileRoots[i]); + } + } + this.openFileRoots = openFileRoots; + if (removedProject) { + // remove project from inferred projects list + this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects); + var openFilesReferenced: ScriptInfo[] = []; + var orphanFiles: ScriptInfo[] = []; + // for all open, referenced files f + for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { + var f = this.openFilesReferenced[i]; + // if f was referenced by the removed project, remember it + if (f.defaultProject == removedProject) { + f.defaultProject = undefined; + orphanFiles.push(f); + } + else { + // otherwise add it back to the list of referenced files + openFilesReferenced.push(f); + } + } + this.openFilesReferenced = openFilesReferenced; + // treat orphaned files as newly opened + for (var i = 0, len = orphanFiles.length; i < len; i++) { + this.addOpenFile(orphanFiles[i]); + } + } + else { + this.openFilesReferenced = copyListRemovingItem(info, this.openFilesReferenced); + } + info.close(); + } + + findReferencingProjects(info: ScriptInfo) { + 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]); + } + } + return referencingProjects; + } + + getScriptInfo(filename: string) { + filename = ts.normalizePath(filename); + return ts.lookUp(this.filenameToScriptInfo, filename); + } + + /** + * @param filename is absolute pathname + */ + openFile(filename: string, openedByClient = false) { + filename = ts.normalizePath(filename); + var info = ts.lookUp(this.filenameToScriptInfo, filename); + if (!info) { + var content: string; + if (this.host.fileExists(filename)) { + content = this.host.readFile(filename); + } + if (!content) { + if (openedByClient) { + content = ""; + } + } + if (content !== undefined) { + info = new ScriptInfo(this.host, filename, content, openedByClient); + this.filenameToScriptInfo[filename] = info; + if (!info.isOpen) { + this.watchedFileSet.addFile(info); + } + } + } + if (info) { + if (openedByClient) { + info.isOpen = true; + } + } + return info; + } + + /** + * Open file whose contents is managed by the client + * @param filename is absolute pathname + */ + + openClientFile(filename: string) { + // TODO: tsconfig check + var info = this.openFile(filename, true); + this.addOpenFile(info); + this.printProjects(); + return info; + } + + /** + * Close file whose contents is managed by the client + * @param filename is absolute pathname + */ + + closeClientFile(filename: string) { + // TODO: tsconfig check + var info = ts.lookUp(this.filenameToScriptInfo, filename); + if (info) { + this.closeOpenFile(info); + info.isOpen = false; + } + this.printProjects(); + } + + getProjectsReferencingFile(filename: string) { + var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + if (scriptInfo) { + var projects: Project[] = []; + for (var i = 0, len = this.inferredProjects.length; i < len; i++) { + if (this.inferredProjects[i].getSourceFile(scriptInfo)) { + projects.push(this.inferredProjects[i]); + } + } + return projects; + } + } + + getProjectForFile(filename: string) { + var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + if (scriptInfo) { + return scriptInfo.defaultProject; + } + } + + printProjectsForFile(filename: string) { + var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + if (scriptInfo) { + this.psLogger.startGroup(); + this.psLogger.info("Projects for " + filename) + var projects = this.getProjectsReferencingFile(filename); + for (var i = 0, len = projects.length; i < len; i++) { + this.psLogger.info("Inferred Project " + i.toString()); + } + this.psLogger.endGroup(); + } + else { + this.psLogger.info(filename + " not in any project"); + } + } + + printProjects() { + this.psLogger.startGroup(); + for (var i = 0, len = this.inferredProjects.length; i < len; i++) { + var project = this.inferredProjects[i]; + this.psLogger.info("Project " + i.toString()); + this.psLogger.info(project.filesToString()); + this.psLogger.info("-----------------------------------------------"); + } + this.psLogger.info("Open file roots: ") + for (var i = 0, len = this.openFileRoots.length; i < len; i++) { + this.psLogger.info(this.openFileRoots[i].filename); + } + this.psLogger.info("Open files referenced: ") + for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { + this.psLogger.info(this.openFilesReferenced[i].filename); + } + this.psLogger.endGroup(); + } + + openConfigFile(configFilename: string): ProjectOpenResult { + configFilename = ts.normalizePath(configFilename); + // file references will be relative to dirPath (or absolute) + var dirPath = ts.getDirectoryPath(configFilename); + var rawConfig = ts.readConfigFile(configFilename); + if (!rawConfig) { + return { errorMsg: "tsconfig syntax error" }; + } + else { + // REVIEW: specify no base path so can get absolute path below + var parsedCommandLine = ts.parseConfigFile(rawConfig); + + if (parsedCommandLine.errors) { + // TODO: gather diagnostics and transmit + return { errorMsg: "tsconfig option errors" }; + } + else if (parsedCommandLine.fileNames) { + var proj = this.createProject(configFilename); + for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) { + var rootFilename = parsedCommandLine.fileNames[i]; + var normRootFilename = ts.normalizePath(rootFilename); + normRootFilename = getAbsolutePath(normRootFilename, dirPath); + if (this.host.fileExists(normRootFilename)) { + var info = this.openFile(normRootFilename); + proj.addRoot(info); + } + else { + return { errorMsg: "specified file " + rootFilename + " not found" }; + } + } + var projectOptions: ProjectOptions = { + files: parsedCommandLine.fileNames, + compilerOptions: parsedCommandLine.options + }; + if (rawConfig.formatCodeOptions) { + projectOptions.formatCodeOptions = rawConfig.formatCodeOptions; + } + proj.setProjectOptions(projectOptions); + return { success: true, project: proj }; + } + else { + return { errorMsg: "no files found" }; + } + } + } + + createProject(projectFilename: string) { + var eproj = new Project(this); + eproj.projectFilename = projectFilename; + return eproj; + } + + } + + export class CompilerService { + cancellationToken = new CancellationToken(); + host: LSHost; + languageService: ts.LanguageService; + classifier: ts.Classifier; + settings = ts.getDefaultCompilerOptions(); + documentRegistry = ts.createDocumentRegistry(); + formatCodeOptions: ts.FormatCodeOptions = CompilerService.defaultFormatCodeOptions; + + constructor(public project: Project) { + this.host = new LSHost(project.projectService.host, project, this.cancellationToken); + // override default ES6 (remove when compiler default back at ES5) + this.settings.target = ts.ScriptTarget.ES5; + this.host.setCompilationSettings(this.settings); + this.languageService = ts.createLanguageService(this.host, this.documentRegistry); + this.classifier = ts.createClassifier(); + } + + setCompilerOptions(opt: ts.CompilerOptions) { + this.settings = opt; + this.host.setCompilationSettings(opt); + } + + isExternalModule(filename: string): boolean { + var sourceFile = this.languageService.getSourceFile(filename); + return ts.isExternalModule(sourceFile); + } + + static defaultFormatCodeOptions: ts.FormatCodeOptions = { + IndentSize: 4, + TabSize: 4, + NewLineCharacter: ts.sys.newLine, + ConvertTabsToSpaces: true, + InsertSpaceAfterCommaDelimiter: true, + InsertSpaceAfterSemicolonInForStatements: true, + InsertSpaceBeforeAndAfterBinaryOperators: true, + InsertSpaceAfterKeywordsInControlFlowStatements: true, + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + PlaceOpenBraceOnNewLineForFunctions: false, + PlaceOpenBraceOnNewLineForControlBlocks: false, + } + + } + + export interface LineCollection { + charCount(): number; + lineCount(): number; + isLeaf(): boolean; + walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void; + print(indentAmt: number): void; + } + + export interface ILineInfo { + line: number; + col: number; + text?: string; + leaf?: LineLeaf; + } + + export enum CharRangeSection { + PreStart, + Start, + Entire, + Mid, + End, + PostEnd + } + + export interface ILineIndexWalker { + goSubtree: boolean; + done: boolean; + leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void; + pre? (relativeStart: number, relativeLength: number, lineCollection: LineCollection, + parent: LineNode, nodeType: CharRangeSection): LineCollection; + post? (relativeStart: number, relativeLength: number, lineCollection: LineCollection, + parent: LineNode, nodeType: CharRangeSection): LineCollection; + } + + class BaseLineIndexWalker implements ILineIndexWalker { + goSubtree = true; + done = false; + leaf(rangeStart: number, rangeLength: number, ll: LineLeaf) { + } + } + + class EditWalker extends BaseLineIndexWalker { + lineIndex = new LineIndex(); + // path to start of range + startPath: LineCollection[]; + endBranch: LineCollection[] = []; + branchNode: LineNode; + // path to current node + stack: LineNode[]; + state = CharRangeSection.Entire; + lineCollectionAtBranch: LineCollection; + initialText = ""; + trailingText = ""; + suppressTrailingText = false; + + constructor() { + super(); + this.lineIndex.root = new LineNode(); + this.startPath = [this.lineIndex.root]; + this.stack = [this.lineIndex.root]; + } + + insertLines(insertedText: string) { + if (this.suppressTrailingText) { + this.trailingText = ""; + } + if (insertedText) { + insertedText = this.initialText + insertedText + this.trailingText; + } + else { + insertedText = this.initialText + this.trailingText; + } + var lm = LineIndex.linesFromText(insertedText); + var lines = lm.lines; + if (lines.length > 1) { + if (lines[lines.length - 1] == "") { + lines.length--; + } + } + var branchParent: LineNode; + var lastZeroCount: LineCollection; + + for (var k = this.endBranch.length - 1; k >= 0; k--) { + (this.endBranch[k]).updateCounts(); + if (this.endBranch[k].charCount() == 0) { + lastZeroCount = this.endBranch[k]; + if (k > 0) { + branchParent = this.endBranch[k - 1]; + } + else { + branchParent = this.branchNode; + } + } + } + if (lastZeroCount) { + branchParent.remove(lastZeroCount); + } + + // path at least length two (root and leaf) + var insertionNode = this.startPath[this.startPath.length - 2]; + var leafNode = this.startPath[this.startPath.length - 1]; + var len = lines.length; + + if (len > 0) { + leafNode.text = lines[0]; + + if (len > 1) { + var insertedNodes = new Array(len - 1); + var startNode = leafNode; + for (var i = 1, len = lines.length; i < len; i++) { + insertedNodes[i - 1] = new LineLeaf(lines[i]); + } + var pathIndex = this.startPath.length - 2; + while (pathIndex >= 0) { + insertionNode = this.startPath[pathIndex]; + insertedNodes = insertionNode.insertAt(startNode, insertedNodes); + pathIndex--; + startNode = insertionNode; + } + var insertedNodesLen = insertedNodes.length; + while (insertedNodesLen > 0) { + var newRoot = new LineNode(); + newRoot.add(this.lineIndex.root); + insertedNodes = newRoot.insertAt(this.lineIndex.root, insertedNodes); + insertedNodesLen = insertedNodes.length; + this.lineIndex.root = newRoot; + } + this.lineIndex.root.updateCounts(); + } + else { + for (var j = this.startPath.length - 2; j >= 0; j--) { + (this.startPath[j]).updateCounts(); + } + } + } + else { + // no content for leaf node, so delete it + insertionNode.remove(leafNode); + for (var j = this.startPath.length - 2; j >= 0; j--) { + (this.startPath[j]).updateCounts(); + } + } + + return this.lineIndex; + } + + post(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection): LineCollection { + // have visited the path for start of range, now looking for end + // if range is on single line, we will never make this state transition + if (lineCollection == this.lineCollectionAtBranch) { + this.state = CharRangeSection.End; + } + // always pop stack because post only called when child has been visited + this.stack.length--; + return undefined; + } + + pre(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection) { + // currentNode corresponds to parent, but in the new tree + var currentNode = this.stack[this.stack.length - 1]; + + if ((this.state == CharRangeSection.Entire) && (nodeType == CharRangeSection.Start)) { + // if range is on single line, we will never make this state transition + this.state = CharRangeSection.Start; + this.branchNode = currentNode; + this.lineCollectionAtBranch = lineCollection; + } + + var child: LineCollection; + function fresh(node: LineCollection): LineCollection { + if (node.isLeaf()) { + return new LineLeaf(""); + } + else return new LineNode(); + } + switch (nodeType) { + case CharRangeSection.PreStart: + this.goSubtree = false; + if (this.state != CharRangeSection.End) { + currentNode.add(lineCollection); + } + break; + case CharRangeSection.Start: + if (this.state == CharRangeSection.End) { + this.goSubtree = false; + } + else { + child = fresh(lineCollection); + currentNode.add(child); + this.startPath[this.startPath.length] = child; + } + break; + case CharRangeSection.Entire: + if (this.state != CharRangeSection.End) { + child = fresh(lineCollection); + currentNode.add(child); + this.startPath[this.startPath.length] = child; + } + else { + if (!lineCollection.isLeaf()) { + child = fresh(lineCollection); + currentNode.add(child); + this.endBranch[this.endBranch.length] = child; + } + } + break; + case CharRangeSection.Mid: + this.goSubtree = false; + break; + case CharRangeSection.End: + if (this.state != CharRangeSection.End) { + this.goSubtree = false; + } + else { + if (!lineCollection.isLeaf()) { + child = fresh(lineCollection); + currentNode.add(child); + this.endBranch[this.endBranch.length] = child; + } + } + break; + case CharRangeSection.PostEnd: + this.goSubtree = false; + if (this.state != CharRangeSection.Start) { + currentNode.add(lineCollection); + } + break; + } + if (this.goSubtree) { + this.stack[this.stack.length] = child; + } + return lineCollection; + } + // just gather text from the leaves + leaf(relativeStart: number, relativeLength: number, ll: LineLeaf) { + if (this.state == CharRangeSection.Start) { + this.initialText = ll.text.substring(0, relativeStart); + } + else if (this.state == CharRangeSection.Entire) { + this.initialText = ll.text.substring(0, relativeStart); + this.trailingText = ll.text.substring(relativeStart + relativeLength); + } + else { + // state is CharRangeSection.End + this.trailingText = ll.text.substring(relativeStart + relativeLength); + } + } + } + + // text change information + export class TextChange { + constructor(public pos: number, public deleteLen: number, public insertedText?: string) { + } + + getTextChangeRange() { + return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen), + this.insertedText ? this.insertedText.length : 0); + } + } + + export class ScriptVersionCache { + changes: TextChange[] = []; + versions: LineIndexSnapshot[] = []; + minVersion = 0; // no versions earlier than min version will maintain change history + private currentVersion = 0; + + static changeNumberThreshold = 8; + static changeLengthThreshold = 256; + + // REVIEW: can optimize by coalescing simple edits + edit(pos: number, deleteLen: number, insertedText?: string) { + this.changes[this.changes.length] = new TextChange(pos, deleteLen, insertedText); + if ((this.changes.length > ScriptVersionCache.changeNumberThreshold) || + (deleteLen > ScriptVersionCache.changeLengthThreshold) || + (insertedText && (insertedText.length > ScriptVersionCache.changeLengthThreshold))) { + this.getSnapshot(); + } + } + + latest() { + return this.versions[this.currentVersion]; + } + + latestVersion() { + if (this.changes.length > 0) { + this.getSnapshot(); + } + return this.currentVersion; + } + + reloadFromFile(filename: string, cb?: () => any) { + var content = ts.sys.readFile(filename); + this.reload(content); + if (cb) + cb(); + } + + // reload whole script, leaving no change history behind reload + reload(script: string) { + this.currentVersion++; + this.changes = []; // history wiped out by reload + var snap = new LineIndexSnapshot(this.currentVersion, this); + this.versions[this.currentVersion] = snap; + snap.index = new LineIndex(); + var lm = LineIndex.linesFromText(script); + snap.index.load(lm.lines); + // REVIEW: could use linked list + for (var i = this.minVersion; i < this.currentVersion; i++) { + this.versions[i] = undefined; + } + this.minVersion = this.currentVersion; + + } + + getSnapshot() { + var snap = this.versions[this.currentVersion]; + if (this.changes.length > 0) { + var snapIndex = this.latest().index; + for (var i = 0, len = this.changes.length; i < len; i++) { + var change = this.changes[i]; + snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText); + } + snap = new LineIndexSnapshot(this.currentVersion + 1, this); + snap.index = snapIndex; + snap.changesSincePreviousVersion = this.changes; + this.currentVersion = snap.version; + this.versions[snap.version] = snap; + this.changes = []; + } + return snap; + } + + getTextChangesBetweenVersions(oldVersion: number, newVersion: number) { + if (oldVersion < newVersion) { + if (oldVersion >= this.minVersion) { + var textChangeRanges: ts.TextChangeRange[] = []; + for (var i = oldVersion + 1; i <= newVersion; i++) { + var snap = this.versions[i]; + for (var j = 0, len = snap.changesSincePreviousVersion.length; j < len; j++) { + var textChange = snap.changesSincePreviousVersion[j]; + textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange(); + } + } + return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges); + } + else { + return undefined; + } + } + else { + return ts.unchangedTextChangeRange; + } + } + + static fromString(script: string) { + var svc = new ScriptVersionCache(); + var snap = new LineIndexSnapshot(0, svc); + svc.versions[svc.currentVersion] = snap; + snap.index = new LineIndex(); + var lm = LineIndex.linesFromText(script); + snap.index.load(lm.lines); + return svc; + } + } + + export class LineIndexSnapshot implements ts.IScriptSnapshot { + index: LineIndex; + changesSincePreviousVersion: TextChange[] = []; + + constructor(public version: number, public cache: ScriptVersionCache) { + } + + getText(rangeStart: number, rangeEnd: number) { + return this.index.getText(rangeStart, rangeEnd - rangeStart); + } + + getLength() { + return this.index.root.charCount(); + } + + // this requires linear space so don't hold on to these + getLineStartPositions(): number[] { + var starts: number[] = [-1]; + var count = 1; + var pos = 0; + this.index.every((ll, s, len) => { + starts[count++] = pos; + pos += ll.text.length; + return true; + }, 0); + return starts; + } + + getLineMapper() { + return ((line: number) => { + return this.index.lineNumberToInfo(line).col; + }); + } + + getTextChangeRangeSinceVersion(scriptVersion: number) { + if (this.version <= scriptVersion) { + return ts.unchangedTextChangeRange; + } + else { + return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version); + } + } + getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { + var oldSnap = oldSnapshot; + return this.getTextChangeRangeSinceVersion(oldSnap.version); + } + } + + + export class LineIndex { + root: LineNode; + // set this to true to check each edit for accuracy + checkEdits = false; + + charOffsetToLineNumberAndPos(charOffset: number) { + return this.root.charOffsetToLineNumberAndPos(1, charOffset); + } + + lineNumberToInfo(lineNumber: number): ILineInfo { + var lineCount = this.root.lineCount(); + if (lineNumber <= lineCount) { + var lineInfo = this.root.lineNumberToInfo(lineNumber, 0); + lineInfo.line = lineNumber; + return lineInfo; + } + else { + return { + line: lineNumber, + col: this.root.charCount() + } + } + } + + print() { + printLine("index TC " + this.root.charCount() + " TL " + this.root.lineCount()); + this.root.print(0); + printLine(""); + } + + load(lines: string[]) { + if (lines.length > 0) { + var leaves: LineLeaf[] = []; + for (var i = 0, len = lines.length; i < len; i++) { + leaves[i] = new LineLeaf(lines[i]); + } + this.root = LineIndex.buildTreeFromBottom(leaves); + } + else { + this.root = new LineNode(); + } + } + + walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) { + this.root.walk(rangeStart, rangeLength, walkFns); + } + + getText(rangeStart: number, rangeLength: number) { + var accum = ""; + if (rangeLength > 0) { + this.walk(rangeStart, rangeLength, { + goSubtree: true, + done: false, + leaf: (relativeStart: number, relativeLength: number, ll: LineLeaf) => { + accum = accum.concat(ll.text.substring(relativeStart, relativeStart + relativeLength)); + } + }); + } + return accum; + } + + every(f: (ll: LineLeaf, s: number, len: number) => boolean, rangeStart: number, rangeEnd?: number) { + if (!rangeEnd) { + rangeEnd = this.root.charCount(); + } + var walkFns = { + goSubtree: true, + done: false, + leaf: function (relativeStart: number, relativeLength: number, ll: LineLeaf) { + if (!f(ll, relativeStart, relativeLength)) { + this.done = true; + } + } + } + this.walk(rangeStart, rangeEnd - rangeStart, walkFns); + return !walkFns.done; + } + + edit(pos: number, deleteLength: number, newText?: string) { + function editFlat(source: string, s: number, dl: number, nt = "") { + return source.substring(0, s) + nt + source.substring(s + dl, source.length); + } + if (this.root.charCount() == 0) { + // TODO: assert deleteLength == 0 + if (newText) { + this.load(LineIndex.linesFromText(newText).lines); + return this; + } + } + else { + if (this.checkEdits) { + var checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText); + } + var walker = new EditWalker(); + if (deleteLength > 0) { + // check whether last characters deleted are line break + var e = pos + deleteLength; + var lineInfo = this.charOffsetToLineNumberAndPos(e); + if ((lineInfo && (lineInfo.col == 0))) { + // move range end just past line that will merge with previous line + deleteLength += lineInfo.text.length; + // store text by appending to end of insertedText + if (newText) { + newText = newText + lineInfo.text; + } + else { + newText = lineInfo.text; + } + } + } + else if (pos >= this.root.charCount()) { + // insert at end + var endString = this.getText(pos - 1, 1); + if (newText) { + newText = endString + newText; + } + else { + newText = endString; + } + pos = pos - 1; + deleteLength = 0; + walker.suppressTrailingText = true; + } + this.root.walk(pos, deleteLength, walker); + walker.insertLines(newText); + if (this.checkEdits) { + var updatedText = this.getText(0, this.root.charCount()); + Debug.assert(checkText == updatedText, "buffer edit mismatch"); + } + return walker.lineIndex; + } + } + + static buildTreeFromBottom(nodes: LineCollection[]): LineNode { + var nodeCount = Math.ceil(nodes.length / lineCollectionCapacity); + var interiorNodes: LineNode[] = []; + var nodeIndex = 0; + for (var i = 0; i < nodeCount; i++) { + interiorNodes[i] = new LineNode(); + var charCount = 0; + var lineCount = 0; + for (var j = 0; j < lineCollectionCapacity; j++) { + if (nodeIndex < nodes.length) { + interiorNodes[i].add(nodes[nodeIndex]); + charCount += nodes[nodeIndex].charCount(); + lineCount += nodes[nodeIndex].lineCount(); + } + else { + break; + } + nodeIndex++; + } + interiorNodes[i].totalChars = charCount; + interiorNodes[i].totalLines = lineCount; + } + if (interiorNodes.length == 1) { + return interiorNodes[0]; + } + else { + return this.buildTreeFromBottom(interiorNodes); + } + } + + static linesFromText(text: string) { + var lineStarts = ts.computeLineStarts(text); + + if (lineStarts.length == 0) { + return { lines: [], lineMap: lineStarts }; + } + var lines = new Array(lineStarts.length); + var lc = lineStarts.length - 1; + for (var lmi = 0; lmi < lc; lmi++) { + lines[lmi] = text.substring(lineStarts[lmi], lineStarts[lmi + 1]); + } + + var endText = text.substring(lineStarts[lc]); + if (endText.length > 0) { + lines[lc] = endText; + } + else { + lines.length--; + } + return { lines: lines, lineMap: lineStarts }; + } + } + + export class LineNode implements LineCollection { + totalChars = 0; + totalLines = 0; + children: LineCollection[] = []; + + isLeaf() { + return false; + } + + print(indentAmt: number) { + var strBuilder = getIndent(indentAmt); + strBuilder += ("node ch " + this.children.length + " TC " + this.totalChars + " TL " + this.totalLines + " :"); + printLine(strBuilder); + for (var ch = 0, clen = this.children.length; ch < clen; ch++) { + this.children[ch].print(indentAmt + 1); + } + } + + updateCounts() { + this.totalChars = 0; + this.totalLines = 0; + for (var i = 0, len = this.children.length; i < len; i++) { + var child = this.children[i]; + this.totalChars += child.charCount(); + this.totalLines += child.lineCount(); + } + } + + execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) { + if (walkFns.pre) { + walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType); + } + if (walkFns.goSubtree) { + this.children[childIndex].walk(rangeStart, rangeLength, walkFns); + if (walkFns.post) { + walkFns.post(rangeStart, rangeLength, this.children[childIndex], this, nodeType); + } + } + else { + walkFns.goSubtree = true; + } + return walkFns.done; + } + + skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) { + if (walkFns.pre && (!walkFns.done)) { + walkFns.pre(relativeStart, relativeLength, this.children[childIndex], this, nodeType); + walkFns.goSubtree = true; + } + } + + walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) { + // assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars) + var childIndex = 0; + var child = this.children[0]; + var childCharCount = child.charCount(); + // find sub-tree containing start + var adjustedStart = rangeStart; + while (adjustedStart >= childCharCount) { + this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart); + adjustedStart -= childCharCount; + child = this.children[++childIndex]; + childCharCount = child.charCount(); + } + // Case I: both start and end of range in same subtree + if ((adjustedStart + rangeLength) <= childCharCount) { + if (this.execWalk(adjustedStart, rangeLength, walkFns, childIndex, CharRangeSection.Entire)) { + return; + } + } + else { + // Case II: start and end of range in different subtrees (possibly with subtrees in the middle) + if (this.execWalk(adjustedStart, childCharCount - adjustedStart, walkFns, childIndex, CharRangeSection.Start)) { + return; + } + var adjustedLength = rangeLength - (childCharCount - adjustedStart); + child = this.children[++childIndex]; + if (!child) { + this.print(2); + } + childCharCount = child.charCount(); + while (adjustedLength > childCharCount) { + if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) { + return; + } + adjustedLength -= childCharCount; + child = this.children[++childIndex]; + childCharCount = child.charCount(); + } + if (adjustedLength > 0) { + if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) { + return; + } + } + } + // Process any subtrees after the one containing range end + if (walkFns.pre) { + var clen = this.children.length; + if (childIndex < (clen - 1)) { + for (var ej = childIndex + 1; ej < clen; ej++) { + this.skipChild(0, 0, ej, walkFns, CharRangeSection.PostEnd); + } + } + } + } + + charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo { + var childInfo = this.childFromCharOffset(lineNumber, charOffset); + if (!childInfo.child) { + return { + line: lineNumber, + col: charOffset, + } + } + else if (childInfo.childIndex < this.children.length) { + if (childInfo.child.isLeaf()) { + return { + line: childInfo.lineNumber, + col: childInfo.charOffset, + text: ((childInfo.child)).text, + leaf: ((childInfo.child)) + }; + } + else { + var lineNode = (childInfo.child); + return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset); + } + } + else { + var lineInfo = this.lineNumberToInfo(this.lineCount(), 0); + return { line: this.lineCount(), col: lineInfo.leaf.charCount() }; + } + } + + lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo { + var childInfo = this.childFromLineNumber(lineNumber, charOffset); + if (!childInfo.child) { + return { + line: lineNumber, + col: charOffset + } + } + else if (childInfo.child.isLeaf()) { + return { + line: lineNumber, + col: childInfo.charOffset, + text: ((childInfo.child)).text, + leaf: ((childInfo.child)) + } + } + else { + var lineNode = (childInfo.child); + return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset); + } + } + + childFromLineNumber(lineNumber: number, charOffset: number) { + var child: LineCollection; + var relativeLineNumber = lineNumber; + for (var i = 0, len = this.children.length; i < len; i++) { + child = this.children[i]; + var childLineCount = child.lineCount(); + if (childLineCount >= relativeLineNumber) { + break; + } + else { + relativeLineNumber -= childLineCount; + charOffset += child.charCount(); + } + } + return { + child: child, + childIndex: i, + relativeLineNumber: relativeLineNumber, + charOffset: charOffset + }; + } + + childFromCharOffset(lineNumber: number, charOffset: number) { + var child: LineCollection; + for (var i = 0, len = this.children.length; i < len; i++) { + child = this.children[i]; + if (child.charCount() > charOffset) { + break; + } + else { + charOffset -= child.charCount(); + lineNumber += child.lineCount(); + } + } + return { + child: child, + childIndex: i, + charOffset: charOffset, + lineNumber: lineNumber + } + } + + splitAfter(childIndex: number) { + var splitNode: LineNode; + var clen = this.children.length; + childIndex++; + var endLength = childIndex; + if (childIndex < clen) { + splitNode = new LineNode(); + while (childIndex < clen) { + splitNode.add(this.children[childIndex++]); + } + splitNode.updateCounts(); + } + this.children.length = endLength; + return splitNode; + } + + remove(child: LineCollection) { + var childIndex = this.findChildIndex(child); + var clen = this.children.length; + if (childIndex < (clen - 1)) { + for (var i = childIndex; i < (clen - 1); i++) { + this.children[i] = this.children[i + 1]; + } + } + this.children.length--; + } + + findChildIndex(child: LineCollection) { + var childIndex = 0; + var clen = this.children.length; + while ((this.children[childIndex] != child) && (childIndex < clen)) childIndex++; + return childIndex; + } + + insertAt(child: LineCollection, nodes: LineCollection[]) { + var childIndex = this.findChildIndex(child); + var clen = this.children.length; + var nodeCount = nodes.length; + // if child is last and there is more room and only one node to place, place it + if ((clen < lineCollectionCapacity) && (childIndex == (clen - 1)) && (nodeCount == 1)) { + this.add(nodes[0]); + this.updateCounts(); + return []; + } + else { + var shiftNode = this.splitAfter(childIndex); + var nodeIndex = 0; + childIndex++; + while ((childIndex < lineCollectionCapacity) && (nodeIndex < nodeCount)) { + this.children[childIndex++] = nodes[nodeIndex++]; + } + var splitNodes: LineNode[] = []; + var splitNodeCount = 0; + if (nodeIndex < nodeCount) { + splitNodeCount = Math.ceil((nodeCount - nodeIndex) / lineCollectionCapacity); + splitNodes = new Array(splitNodeCount); + var splitNodeIndex = 0; + for (var i = 0; i < splitNodeCount; i++) { + splitNodes[i] = new LineNode(); + } + var splitNode = splitNodes[0]; + while (nodeIndex < nodeCount) { + splitNode.add(nodes[nodeIndex++]); + if (splitNode.children.length == lineCollectionCapacity) { + splitNodeIndex++; + splitNode = splitNodes[splitNodeIndex]; + } + } + for (i = splitNodes.length - 1; i >= 0; i--) { + if (splitNodes[i].children.length == 0) { + splitNodes.length--; + } + } + } + if (shiftNode) { + splitNodes[splitNodes.length] = shiftNode; + } + this.updateCounts(); + for (i = 0; i < splitNodeCount; i++) { + (splitNodes[i]).updateCounts(); + } + return splitNodes; + } + } + + // assume there is room for the item; return true if more room + add(collection: LineCollection) { + this.children[this.children.length] = collection; + return (this.children.length < lineCollectionCapacity); + } + + charCount() { + return this.totalChars; + } + + lineCount() { + return this.totalLines; + } + } + + export class LineLeaf implements LineCollection { + udata: any; + + constructor(public text: string) { + + } + + setUdata(data: any) { + this.udata = data; + } + + getUdata() { + return this.udata; + } + + isLeaf() { + return true; + } + + walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) { + walkFns.leaf(rangeStart, rangeLength, this); + } + + charCount() { + return this.text.length; + } + + lineCount() { + return 1; + } + + print(indentAmt: number) { + var strBuilder = getIndent(indentAmt); + printLine(strBuilder + showLines(this.text)); + } + } +} \ No newline at end of file diff --git a/src/server/node.d.ts b/src/server/node.d.ts new file mode 100644 index 0000000000000..2002f973a3789 --- /dev/null +++ b/src/server/node.d.ts @@ -0,0 +1,677 @@ +// Type definitions for Node.js v0.10.1 +// Project: http://nodejs.org/ +// Definitions by: Microsoft TypeScript , DefinitelyTyped +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/************************************************ +* * +* Node.js v0.10.1 API * +* * +************************************************/ + +/************************************************ +* * +* GLOBAL * +* * +************************************************/ +declare var process: NodeJS.Process; +declare var global: any; + +declare var __filename: string; +declare var __dirname: string; + +declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearTimeout(timeoutId: NodeJS.Timer): void; +declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearInterval(intervalId: NodeJS.Timer): void; +declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; +declare function clearImmediate(immediateId: any): void; + +declare var require: { + (id: string): any; + resolve(id: string): string; + cache: any; + extensions: any; + main: any; +}; + +declare var module: { + exports: any; + require(id: string): any; + id: string; + filename: string; + loaded: boolean; + parent: any; + children: any[]; +}; + +// Same as module.exports +declare var exports: any; +declare var SlowBuffer: { + new (str: string, encoding?: string): Buffer; + new (size: number): Buffer; + new (size: Uint8Array): Buffer; + new (array: any[]): Buffer; + prototype: Buffer; + isBuffer(obj: any): boolean; + byteLength(string: string, encoding?: string): number; + concat(list: Buffer[], totalLength?: number): Buffer; +}; + + +// Buffer class +interface Buffer extends NodeBuffer { } +interface BufferConstructor { + new (str: string, encoding ?: string): Buffer; + new (size: number): Buffer; + new (size: Uint8Array): Buffer; + new (array: any[]): Buffer; + prototype: Buffer; + isBuffer(obj: any): boolean; + byteLength(string: string, encoding ?: string): number; + concat(list: Buffer[], totalLength ?: number): Buffer; +} +declare var Buffer: BufferConstructor; + +/************************************************ +* * +* GLOBAL INTERFACES * +* * +************************************************/ +declare module NodeJS { + export interface ErrnoException extends Error { + errno?: any; + code?: string; + path?: string; + syscall?: string; + } + + export interface EventEmitter { + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } + + export interface ReadableStream extends EventEmitter { + readable: boolean; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: ReadableStream): ReadableStream; + } + + export interface WritableStream extends EventEmitter { + writable: boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface ReadWriteStream extends ReadableStream, WritableStream { } + + export interface Process extends EventEmitter { + stdout: WritableStream; + stderr: WritableStream; + stdin: ReadableStream; + argv: string[]; + execPath: string; + abort(): void; + chdir(directory: string): void; + cwd(): string; + env: any; + exit(code?: number): void; + getgid(): number; + setgid(id: number): void; + setgid(id: string): void; + getuid(): number; + setuid(id: number): void; + setuid(id: string): void; + version: string; + versions: { + http_parser: string; + node: string; + v8: string; + ares: string; + uv: string; + zlib: string; + openssl: string; + }; + config: { + target_defaults: { + cflags: any[]; + default_configuration: string; + defines: string[]; + include_dirs: string[]; + libraries: string[]; + }; + variables: { + clang: number; + host_arch: string; + node_install_npm: boolean; + node_install_waf: boolean; + node_prefix: string; + node_shared_openssl: boolean; + node_shared_v8: boolean; + node_shared_zlib: boolean; + node_use_dtrace: boolean; + node_use_etw: boolean; + node_use_openssl: boolean; + target_arch: string; + v8_no_strict_aliasing: number; + v8_use_snapshot: boolean; + visibility: string; + }; + }; + kill(pid: number, signal?: string): void; + pid: number; + title: string; + arch: string; + platform: string; + memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; }; + nextTick(callback: Function): void; + umask(mask?: number): number; + uptime(): number; + hrtime(time?: number[]): number[]; + + // Worker + send? (message: any, sendHandle?: any): void; + } + + export interface Timer { + ref(): void; + unref(): void; + } +} + + +/** + * @deprecated + */ +interface NodeBuffer { + [index: number]: number; + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): any; + length: number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + readUInt8(offset: number, noAsset?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + writeUInt8(value: number, offset: number, noAssert?: boolean): void; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): void; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): void; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): void; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): void; + writeInt8(value: number, offset: number, noAssert?: boolean): void; + writeInt16LE(value: number, offset: number, noAssert?: boolean): void; + writeInt16BE(value: number, offset: number, noAssert?: boolean): void; + writeInt32LE(value: number, offset: number, noAssert?: boolean): void; + writeInt32BE(value: number, offset: number, noAssert?: boolean): void; + writeFloatLE(value: number, offset: number, noAssert?: boolean): void; + writeFloatBE(value: number, offset: number, noAssert?: boolean): void; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): void; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): void; + fill(value: any, offset?: number, end?: number): void; +} + +declare module NodeJS { + export interface Path { + normalize(p: string): string; + join(...paths: any[]): string; + resolve(...pathSegments: any[]): string; + relative(from: string, to: string): string; + dirname(p: string): string; + basename(p: string, ext?: string): string; + extname(p: string): string; + sep: string; + } +} + +declare module NodeJS { + export interface ReadLineInstance extends EventEmitter { + setPrompt(prompt: string, length: number): void; + prompt(preserveCursor?: boolean): void; + question(query: string, callback: Function): void; + pause(): void; + resume(): void; + close(): void; + write(data: any, key?: any): void; + } + export interface ReadLineOptions { + input: NodeJS.ReadableStream; + output: NodeJS.WritableStream; + completer?: Function; + terminal?: boolean; + } + + export interface ReadLine { + createInterface(options: ReadLineOptions): ReadLineInstance; + } +} + +declare module NodeJS { + module events { + export class EventEmitter implements NodeJS.EventEmitter { + static listenerCount(emitter: EventEmitter, event: string): number; + + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } + } +} + +declare module NodeJS { + module stream { + + export interface Stream extends events.EventEmitter { + pipe(destination: T, options?: { end?: boolean; }): T; + } + + export interface ReadableOptions { + highWaterMark?: number; + encoding?: string; + objectMode?: boolean; + } + + export class Readable extends events.EventEmitter implements NodeJS.ReadableStream { + readable: boolean; + constructor(opts?: ReadableOptions); + _read(size: number): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + } + + export interface WritableOptions { + highWaterMark?: number; + decodeStrings?: boolean; + } + + export class Writable extends events.EventEmitter implements NodeJS.WritableStream { + writable: boolean; + constructor(opts?: WritableOptions); + _write(data: Buffer, encoding: string, callback: Function): void; + _write(data: string, encoding: string, callback: Function): void; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface DuplexOptions extends ReadableOptions, WritableOptions { + allowHalfOpen?: boolean; + } + + // Note: Duplex extends both Readable and Writable. + export class Duplex extends Readable implements NodeJS.ReadWriteStream { + writable: boolean; + constructor(opts?: DuplexOptions); + _write(data: Buffer, encoding: string, callback: Function): void; + _write(data: string, encoding: string, callback: Function): void; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface TransformOptions extends ReadableOptions, WritableOptions { } + + // Note: Transform lacks the _read and _write methods of Readable/Writable. + export class Transform extends events.EventEmitter implements NodeJS.ReadWriteStream { + readable: boolean; + writable: boolean; + constructor(opts?: TransformOptions); + _transform(chunk: Buffer, encoding: string, callback: Function): void; + _transform(chunk: string, encoding: string, callback: Function): void; + _flush(callback: Function): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export class PassThrough extends Transform { } + } +} + +declare module NodeJS { + module fs { + interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + blksize: number; + blocks: number; + atime: Date; + mtime: Date; + ctime: Date; + } + interface FSWatcher extends events.EventEmitter { + close(): void; + } + + export interface ReadStream extends stream.Readable { } + export interface WriteStream extends stream.Writable { } + + export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function renameSync(oldPath: string, newPath: string): void; + export function truncate(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncate(path: string, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncateSync(path: string, len?: number): void; + export function ftruncate(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncate(fd: number, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncateSync(fd: number, len?: number): void; + export function chown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chownSync(path: string, uid: number, gid: number): void; + export function fchown(fd: number, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function lchown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchownSync(path: string, uid: number, gid: number): void; + export function chmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmodSync(path: string, mode: number): void; + export function chmodSync(path: string, mode: string): void; + export function fchmod(fd: number, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmod(fd: number, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmodSync(fd: number, mode: number): void; + export function fchmodSync(fd: number, mode: string): void; + export function lchmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmodSync(path: string, mode: number): void; + export function lchmodSync(path: string, mode: string): void; + export function stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function lstat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function fstat(fd: number, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function statSync(path: string): Stats; + export function lstatSync(path: string): Stats; + export function fstatSync(fd: number): Stats; + export function link(srcpath: string, dstpath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function linkSync(srcpath: string, dstpath: string): void; + export function symlink(srcpath: string, dstpath: string, type?: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function symlinkSync(srcpath: string, dstpath: string, type?: string): void; + export function readlink(path: string, callback?: (err: NodeJS.ErrnoException, linkString: string) => any): void; + export function readlinkSync(path: string): string; + export function realpath(path: string, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; + export function realpath(path: string, cache: { [path: string]: string }, callback: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; + export function realpathSync(path: string, cache?: { [path: string]: string }): string; + export function unlink(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function unlinkSync(path: string): void; + export function rmdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function rmdirSync(path: string): void; + export function mkdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdir(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdir(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdirSync(path: string, mode?: number): void; + export function mkdirSync(path: string, mode?: string): void; + export function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; + export function readdirSync(path: string): string[]; + export function close(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function closeSync(fd: number): void; + export function open(path: string, flags: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: number, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function openSync(path: string, flags: string, mode?: number): number; + export function openSync(path: string, flags: string, mode?: string): number; + export function utimes(path: string, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimes(path: string, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimesSync(path: string, atime: number, mtime: number): void; + export function utimesSync(path: string, atime: Date, mtime: Date): void; + export function futimes(fd: number, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimes(fd: number, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimesSync(fd: number, atime: number, mtime: number): void; + export function futimesSync(fd: number, atime: Date, mtime: Date): void; + export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fsyncSync(fd: number): void; + export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; + export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void; + export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + export function readFileSync(filename: string, encoding: string): string; + export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string; + export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; + export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void; + export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void; + export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void; + export function watch(filename: string, listener?: (event: string, filename: string) => any): FSWatcher; + export function watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher; + export function exists(path: string, callback?: (exists: boolean) => void): void; + export function existsSync(path: string): boolean; + export function createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: string; + mode?: number; + bufferSize?: number; + }): ReadStream; + export function createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: string; + mode?: string; + bufferSize?: number; + }): ReadStream; + export function createWriteStream(path: string, options?: { + flags?: string; + encoding?: string; + string?: string; + }): WriteStream; + } +} + +declare module NodeJS { + module path { + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + } +} + +declare module NodeJS { + module _debugger { + export interface Packet { + raw: string; + headers: string[]; + body: Message; + } + + export interface Message { + seq: number; + type: string; + } + + export interface RequestInfo { + command: string; + arguments: any; + } + + export interface Request extends Message, RequestInfo { + } + + export interface Event extends Message { + event: string; + body?: any; + } + + export interface Response extends Message { + request_seq: number; + success: boolean; + /** Contains error message if success == false. */ + message?: string; + /** Contains message body if success == true. */ + body?: any; + } + + export interface BreakpointMessageBody { + type: string; + target: number; + line: number; + } + + export class Protocol { + res: Packet; + state: string; + execute(data: string): void; + serialize(rq: Request): string; + onResponse: (pkt: Packet) => void; + } + + export var NO_FRAME: number; + export var port: number; + + export interface ScriptDesc { + name: string; + id: number; + isNative?: boolean; + handle?: number; + type: string; + lineOffset?: number; + columnOffset?: number; + lineCount?: number; + } + + export interface Breakpoint { + id: number; + scriptId: number; + script: ScriptDesc; + line: number; + condition?: string; + scriptReq?: string; + } + + export interface RequestHandler { + (err: boolean, body: Message, res: Packet): void; + request_seq?: number; + } + + export interface ResponseBodyHandler { + (err: boolean, body?: any): void; + request_seq?: number; + } + + export interface ExceptionInfo { + text: string; + } + + export interface BreakResponse { + script?: ScriptDesc; + exception?: ExceptionInfo; + sourceLine: number; + sourceLineText: string; + sourceColumn: number; + } + + export function SourceInfo(body: BreakResponse): string; + + export class Client extends events.EventEmitter { + protocol: Protocol; + scripts: ScriptDesc[]; + handles: ScriptDesc[]; + breakpoints: Breakpoint[]; + currentSourceLine: number; + currentSourceColumn: number; + currentSourceLineText: string; + currentFrame: number; + currentScript: string; + + connect(port: number, host: string): void; + req(req: any, cb: RequestHandler): void; + reqFrameEval(code: string, frame: number, cb: RequestHandler): void; + mirrorObject(obj: any, depth: number, cb: ResponseBodyHandler): void; + setBreakpoint(rq: BreakpointMessageBody, cb: RequestHandler): void; + clearBreakpoint(rq: Request, cb: RequestHandler): void; + listbreakpoints(cb: RequestHandler): void; + reqSource(from: number, to: number, cb: RequestHandler): void; + reqScripts(cb: any): void; + reqContinue(cb: RequestHandler): void; + } + } +} \ No newline at end of file diff --git a/src/server/protocol.ts b/src/server/protocol.ts new file mode 100644 index 0000000000000..abbce0b358881 --- /dev/null +++ b/src/server/protocol.ts @@ -0,0 +1,1309 @@ +/// +/// +/// +/// +/// + +module ts { + export interface NavigationBarItem { + displayString?: string; + docString?: string; + } + + export interface System { + getModififedTime? (fileName: string): Date; + stat? (path: string, callback?: (err: any, stats: any) => any): void; + } +} + +module ts.server { + var paddedLength = 8; + + var typeNames = ["interface", "class", "enum", "module", "alias", "type"]; + + var spaceCache = [" ", " ", " ", " "]; + + interface StackTraceError extends Error { + stack?: string; + } + + function generateSpaces(n: number): string { + if (!spaceCache[n]) { + var strBuilder = ""; + for (var i = 0; i < n; i++) { + strBuilder += " "; + } + spaceCache[n] = strBuilder; + } + return spaceCache[n]; + } + + function isTypeName(name: string, suffix?: string) { + for (var i = 0, len = typeNames.length; i < len; i++) { + if (typeNames[i] == name) { + return true; + } + else if (suffix && ((typeNames[i] + suffix) == name)) { + return true; + } + } + return false; + } + + function parseTypeName(displayParts: ts.SymbolDisplayPart[]) { + var len = displayParts.length; + for (var i = len - 1; i >= 0; i--) { + if (isTypeName(displayParts[i].kind, "Name")) { + return displayParts[i].text; + } + } + return undefined; + } + + function findExactMatchType(items: ts.NavigateToItem[]) { + for (var i = 0, len = items.length; i < len; i++) { + var navItem = items[i]; + if (navItem.matchKind == "exact") { + if (isTypeName(navItem.kind)) { + return navItem; + } + } + } + } + + interface FileStart { + file: string; + start: ILineInfo; + } + + function compareNumber(a: number, b: number) { + if (a < b) { + return -1; + } + else if (a == b) { + return 0; + } + else return 1; + } + + function printObject(obj: any) { + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + console.log(p + ": " + obj[p]); + } + } + } + + function compareFileStart(a: FileStart, b: FileStart) { + if (a.file < b.file) { + return -1; + } + else if (a.file == b.file) { + var n = compareNumber(a.start.line, b.start.line); + if (n == 0) { + return compareNumber(a.start.col, b.start.col); + } + else return n; + } + else { + 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 SourceInfo(body: NodeJS._debugger.BreakResponse) { + // var result = body.exception ? 'exception in ' : 'break in '; + + // if (body.script) { + // if (body.script.name) { + // var name = body.script.name, + // dir = path.resolve() + '/'; + + // // Change path to relative, if possible + // if (name.indexOf(dir) === 0) { + // name = name.slice(dir.length); + // } + + // result += name; + // } else { + // result += '[unnamed]'; + // } + // } + // result += ':'; + // result += body.sourceLine + 1; + + // if (body.exception) result += '\n' + body.exception.text; + + // return result; + //} + + class JsDebugSession { + host = 'localhost'; + port = 5858; + + constructor(public client: NodeJS._debugger.Client) { + this.init(); + } + + cont(cb: NodeJS._debugger.RequestHandler) { + this.client.reqContinue(cb); + } + + listSrc() { + this.client.reqScripts((err: any) => { + if (err) { + console.log("rscr error: " + err); + } + else { + console.log("req scripts"); + for (var id in this.client.scripts) { + var script = this.client.scripts[id]; + if ((typeof script === "object") && script.name) { + console.log(id + ": " + script.name); + } + } + } + }); + } + + findScript(file: string) { + if (file) { + var script: NodeJS._debugger.ScriptDesc; + var scripts = this.client.scripts; + var keys: any[] = Object.keys(scripts); + var ambiguous = false; + for (var v = 0; v < keys.length; v++) { + var id = keys[v]; + if (scripts[id] && + scripts[id].name && + scripts[id].name.indexOf(file) !== -1) { + if (script) { + ambiguous = true; + } + script = scripts[id]; + } + } + return { script: script, ambiguous: ambiguous }; + } + } + + // TODO: condition + setBreakpointOnLine(line: number, file?: string) { + if (!file) { + file = this.client.currentScript; + } + var script: NodeJS._debugger.ScriptDesc; + var scriptResult = this.findScript(file); + if (scriptResult) { + if (scriptResult.ambiguous) { + // TODO: send back error + script = undefined; + } + else { + script = scriptResult.script; + } + } + // TODO: set breakpoint when script not loaded + if (script) { + var brkmsg: NodeJS._debugger.BreakpointMessageBody = { + type: 'scriptId', + target: script.id, + line: line - 1, + } + this.client.setBreakpoint(brkmsg,(err, bod) => { + // TODO: remember breakpoint + if (err) { + console.log("Error: set breakpoint: " + err); + } + }); + } + + } + + init() { + var connectionAttempts = 0; + this.client.on('break',(res: NodeJS._debugger.Event) => { + this.handleBreak(res.body); + }); + this.client.on('exception',(res: NodeJS._debugger.Event) => { + this.handleBreak(res.body); + }); + this.client.on('error',() => { + setTimeout(() => { + ++connectionAttempts; + this.client.connect(this.port, this.host); + }, 500); + }); + this.client.once('ready',() => { + }); + this.client.on('unhandledResponse',() => { + }); + this.client.connect(this.port, this.host); + } + + evaluate(code: string) { + var frame = this.client.currentFrame; + this.client.reqFrameEval(code, frame,(err, bod) => { + if (err) { + console.log("Error: evaluate: " + err); + return; + } + + console.log("Value: " + bod.toString()); + if (typeof bod === "object") { + printObject(bod); + } + + // Request object by handles (and it's sub-properties) + this.client.mirrorObject(bod, 3,(err, mirror) => { + if (mirror) { + if (typeof mirror === "object") { + printObject(mirror); + } + console.log(mirror.toString()); + } + else { + console.log("undefined"); + } + }); + + }); + } + + handleBreak(breakInfo: NodeJS._debugger.BreakResponse) { + this.client.currentSourceLine = breakInfo.sourceLine; + this.client.currentSourceLineText = breakInfo.sourceLineText; + this.client.currentSourceColumn = breakInfo.sourceColumn; + this.client.currentFrame = 0; + this.client.currentScript = breakInfo.script && breakInfo.script.name; + + //console.log(SourceInfo(breakInfo)); + // TODO: watchers + } + } + + interface FileRange { + file?: string; + start: ILineInfo; + end: ILineInfo; + } + + interface FileRanges { + file: string; + locs: FileRange[]; + } + + function formatDiag(file: string, project: Project, diag: ts.Diagnostic) { + return { + start: project.compilerService.host.positionToLineCol(file, diag.start), + len: diag.length, + text: diag.messageText, + }; + } + + interface PendingErrorCheck { + filename: string; + project: Project; + } + + function allEditsBeforePos(edits: ts.TextChange[], pos: number) { + for (var i = 0, len = edits.length; i < len; i++) { + if (ts.textSpanEnd(edits[i].span) >= pos) { + return false; + } + } + return true; + } + + module CommandNames { + export var Abbrev = "abbrev"; + export var Change = "change"; + export var Close = "close"; + export var Completions = "completions"; + export var Definition = "definition"; + export var Format = "format"; + export var Formatonkey = "formatonkey"; + export var Geterr = "geterr"; + export var Navto = "navto"; + export var Open = "open"; + export var Quickinfo = "quickinfo"; + export var References = "references"; + export var Reload = "reload"; + export var Rename = "rename"; + export var Saveto = "saveto"; + export var Type = "type"; + export var Unknown = "unknown"; + } + + export interface ServerHost extends ts.System { + getDebuggerClient? (): NodeJS._debugger.Client; + } + + export class Session { + projectService: ProjectService; + debugSession: JsDebugSession; + pendingOperation = false; + fileHash: ts.Map = {}; + abbrevTable: ts.Map; + fetchedAbbrev = false; + nextFileId = 1; + errorTimer: NodeJS.Timer; + immediateId: any; + changeSeq = 0; + + constructor(private host: ServerHost, private logger: Logger, protected useProtocol: boolean, protected prettyJSON: boolean) { + this.projectService = new ProjectService(host, logger); + this.initAbbrevTable(); + } + + logError(err: Error, cmd: string) { + var typedErr = err; + var msg = "Exception on executing command " + cmd; + if (typedErr.message) { + msg += ":\n" + typedErr.message; + if (typedErr.stack) { + msg += "\n" + typedErr.stack; + } + } + this.projectService.log(msg); + } + + sendLineToClient(line: string) { + this.host.write(line + this.host.newLine); + } + + send(msg: NodeJS._debugger.Message) { + var json: string; + if (this.prettyJSON) { + json = JSON.stringify(msg, null, " "); + } + else { + json = JSON.stringify(msg); + } + this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) + + '\r\n\r\n' + json); + } + + event(info: any, eventName: string) { + var ev: NodeJS._debugger.Event = { + seq: 0, + type: "event", + event: eventName, + body: info, + }; + this.send(ev); + } + + response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { + var res: ServerProtocol.Response = { + seq: 0, + type: "response", + command: cmdName, + request_seq: reqSeq, + success: !errorMsg, + } + if (!errorMsg) { + res.body = info; + } + else { + res.message = errorMsg; + } + this.send(res); + } + + initAbbrevTable() { + this.abbrevTable = { + name: "n", + kind: "k", + fileName: "f", + containerName: "cn", + containerKind: "ck", + kindModifiers: "km", + start: "s", + end: "e", + line: "l", + col: "c", + "interface": "i", + "function": "fn", + }; + } + + encodeFilename(filename: string): ServerProtocol.EncodedFile { + if (!this.fetchedAbbrev) { + return filename; + } + else { + var id = ts.lookUp(this.fileHash, filename); + if (!id) { + id = this.nextFileId++; + this.fileHash[filename] = id; + return { id: id, file: filename }; + } + else { + return id; + } + } + } + + abbreviate(obj: any) { + if (this.fetchedAbbrev && (!this.prettyJSON)) { + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + var sub = ts.lookUp(this.abbrevTable, p); + if (sub) { + obj[sub] = obj[p]; + obj[p] = undefined; + } + } + } + } + } + + output(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { + if (this.useProtocol) { + this.response(info, cmdName, reqSeq, errorMsg); + } + else if (this.prettyJSON) { + if (!errorMsg) { + this.sendLineToClient(JSON.stringify(info, null, " ").trim()); + } + else { + this.sendLineToClient(JSON.stringify(errorMsg)); + } + } else { + if (!errorMsg) { + var infoStr = JSON.stringify(info).trim(); + // [8 digits of length,infoStr] + '\n' + var len = infoStr.length + paddedLength + 4; + var lenStr = len.toString(); + var padLen = paddedLength - lenStr.length; + for (var i = 0; i < padLen; i++) { + lenStr = '0' + lenStr; + } + this.sendLineToClient("[" + lenStr + "," + infoStr + "]"); + } + else { + this.sendLineToClient(JSON.stringify("error: " + errorMsg)); + } + } + } + + semanticCheck(file: string, project: Project) { + var diags = project.compilerService.languageService.getSemanticDiagnostics(file); + if (diags) { + var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); + this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag"); + } + } + + syntacticCheck(file: string, project: Project) { + var diags = project.compilerService.languageService.getSyntacticDiagnostics(file); + if (diags) { + var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); + this.event({ file: file, diagnostics: bakedDiags }, "syntaxDiag"); + } + } + + errorCheck(file: string, project: Project) { + this.syntacticCheck(file, project); + this.semanticCheck(file, project); + } + + updateErrorCheck(checkList: PendingErrorCheck[], seq: number, + matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200) { + if (followMs > ms) { + followMs = ms; + } + if (this.errorTimer) { + clearTimeout(this.errorTimer); + } + if (this.immediateId) { + clearImmediate(this.immediateId); + this.immediateId = undefined; + } + var index = 0; + var checkOne = () => { + if (matchSeq(seq)) { + var checkSpec = checkList[index++]; + if (checkSpec.project.getSourceFileFromName(checkSpec.filename)) { + this.syntacticCheck(checkSpec.filename, checkSpec.project); + this.immediateId = setImmediate(() => { + this.semanticCheck(checkSpec.filename, checkSpec.project); + this.immediateId = undefined; + if (checkList.length > index) { + this.errorTimer = setTimeout(checkOne, followMs); + } + else { + this.errorTimer = undefined; + } + }); + } + } + } + if ((checkList.length > index) && (matchSeq(seq))) { + this.errorTimer = setTimeout(checkOne, ms); + } + } + + goToDefinition(line: number, col: number, rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var locs = compilerService.languageService.getDefinitionAtPosition(file, pos); + if (locs) { + var info: ServerProtocol.CodeSpan[] = locs.map(def => ({ + file: def && def.fileName, + start: def && + compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), + end: def && + compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) + })); + this.output(info, CommandNames.Definition, reqSeq); + } + else { + this.output(undefined, CommandNames.Definition, reqSeq, "could not find def"); + } + } + else { + this.output(undefined, CommandNames.Definition, reqSeq, "no project for " + file); + } + } + + rename(line: number, col: number, rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var renameInfo = compilerService.languageService.getRenameInfo(file, pos); + if (renameInfo) { + if (renameInfo.canRename) { + var renameLocs = compilerService.languageService.findRenameLocations(file, pos, false, false); + if (renameLocs) { + var bakedRenameLocs = renameLocs.map(loc=> ({ + file: loc.fileName, + start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), + end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), + })).sort((a, b) => { + if (a.file < b.file) { + return -1; + } + else if (a.file > b.file) { + return 1; + } + else { + // reverse sort assuming no overlap + if (a.start.line < b.start.line) { + return 1; + } + else if (a.start.line > b.start.line) { + return -1; + } + else { + return b.start.col - a.start.col; + } + } + }).reduce((accum: FileRanges[], cur: FileRange) => { + var curFileAccum: FileRanges; + if (accum.length > 0) { + curFileAccum = accum[accum.length - 1]; + if (curFileAccum.file != cur.file) { + curFileAccum = undefined; + } + } + if (!curFileAccum) { + curFileAccum = { file: cur.file, locs: [] }; + accum.push(curFileAccum); + } + curFileAccum.locs.push({ start: cur.start, end: cur.end }); + return accum; + }, []); + this.output({ info: renameInfo, locs: bakedRenameLocs }, CommandNames.Rename, reqSeq); + } + else { + this.output({ info: renameInfo, locs: [] }, CommandNames.Rename, reqSeq); + } + } + else { + this.output(undefined, CommandNames.Rename, reqSeq, renameInfo.localizedErrorMessage); + } + } + else { + this.output(undefined, CommandNames.Rename, reqSeq, "no rename information at cursor"); + } + } + } + + findReferences(line: number, col: number, rawfile: string, reqSeq = 0) { + // TODO: get all projects for this file; report refs for all projects deleting duplicates + // can avoid duplicates by eliminating same ref file from subsequent projects + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var refs = compilerService.languageService.getReferencesAtPosition(file, pos); + if (refs) { + var nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); + if (nameInfo) { + var displayString = ts.displayPartsToString(nameInfo.displayParts); + var nameSpan = nameInfo.textSpan; + var nameColStart = + compilerService.host.positionToLineCol(file, nameSpan.start).col; + var nameText = + compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); + var bakedRefs: ServerProtocol.ReferencesResponseItem[] = refs.map((ref) => { + var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start); + var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); + var snap = compilerService.host.getScriptSnapshot(ref.fileName); + var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); + return { + file: ref.fileName, + start: start, + lineText: lineText, + end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), + }; + }).sort(compareFileStart); + var response: ServerProtocol.ReferencesResponseBody = { + refs: bakedRefs, + symbolName: nameText, + symbolStartCol: nameColStart, + symbolDisplayString: displayString + }; + this.output(response, CommandNames.References, reqSeq); + } + else { + this.output(undefined, CommandNames.References, reqSeq, "no references at this position"); + } + } + else { + this.output(undefined, CommandNames.References, reqSeq, "no references at this position"); + } + } + } + + // TODO: implement this as ls api that can return multiple def sites + goToType(line: number, col: number, rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); + var typeLoc: any; + + if (quickInfo && (quickInfo.kind == "var") || (quickInfo.kind == "local var")) { + var typeName = parseTypeName(quickInfo.displayParts); + if (typeName) { + var navItems = compilerService.languageService.getNavigateToItems(typeName); + var navItem = findExactMatchType(navItems); + if (navItem) { + typeLoc = { + file: navItem.fileName, + start: compilerService.host.positionToLineCol(navItem.fileName, + navItem.textSpan.start), + end: compilerService.host.positionToLineCol(navItem.fileName, + ts.textSpanEnd(navItem.textSpan)), + }; + } + } + } + if (typeLoc) { + this.output([typeLoc], CommandNames.Type, reqSeq); + } + else { + this.output(undefined, CommandNames.Type, reqSeq, "no info at this location"); + } + } + else { + this.output(undefined, CommandNames.Type, reqSeq, "no project for " + file); + } + } + + openClientFile(rawfile: string) { + var file = ts.normalizePath(rawfile); + this.projectService.openClientFile(file); + } + + quickInfo(line: number, col: number, rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); + if (quickInfo) { + var displayString = ts.displayPartsToString(quickInfo.displayParts); + var docString = ts.displayPartsToString(quickInfo.documentation); + var qi: ServerProtocol.QuickInfoResponseBody = { + kind: quickInfo.kind, + kindModifiers: quickInfo.kindModifiers, + start: compilerService.host.positionToLineCol(file, quickInfo.textSpan.start), + end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(quickInfo.textSpan)), + displayString: displayString, + documentation: docString, + }; + this.output(qi, CommandNames.Quickinfo, reqSeq); + } + else { + this.output(undefined, CommandNames.Quickinfo, reqSeq, "no info") + } + } + } + + format(line: number, col: number, endLine: number, endCol: number, rawfile: string, cmd: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var endPos = compilerService.host.lineColToPosition(file, endLine, endCol); + var edits: ts.TextChange[]; + // TODO: avoid duplicate code (with formatonkey) + try { + edits = compilerService.languageService.getFormattingEditsForRange(file, pos, endPos, + compilerService.formatCodeOptions); + } + catch (err) { + this.logError(err, cmd); + edits = undefined; + } + if (edits) { + var bakedEdits: ServerProtocol.CodeEdit[] = edits.map((edit) => { + return { + start: compilerService.host.positionToLineCol(file, + edit.span.start), + end: compilerService.host.positionToLineCol(file, + ts.textSpanEnd(edit.span)), + newText: edit.newText ? edit.newText : "" + }; + }); + this.output(bakedEdits, CommandNames.Format, reqSeq); + } + else { + this.output(undefined, CommandNames.Format, reqSeq, "no edits") + } + } + } + + formatOnKey(line: number, col: number, key: string, rawfile: string, cmd: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var edits: ts.TextChange[]; + try { + edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, pos, key, + compilerService.formatCodeOptions); + if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, pos))) { + // TODO: get these options from host + var editorOptions: ts.EditorOptions = { + IndentSize: 4, + TabSize: 4, + NewLineCharacter: "\n", + ConvertTabsToSpaces: true, + }; + var indentPosition = compilerService.languageService.getIndentationAtPosition(file, pos, editorOptions); + var spaces = generateSpaces(indentPosition); + if (indentPosition > 0) { + edits.push({ span: ts.createTextSpanFromBounds(pos, pos), newText: spaces }); + } + } + } + catch (err) { + this.logError(err, cmd); + edits = undefined; + } + if (edits) { + var bakedEdits: ServerProtocol.CodeEdit[] = edits.map((edit) => { + return { + start: compilerService.host.positionToLineCol(file, + edit.span.start), + end: compilerService.host.positionToLineCol(file, + ts.textSpanEnd(edit.span)), + newText: edit.newText ? edit.newText : "" + }; + }); + this.output(bakedEdits, CommandNames.Formatonkey, reqSeq); + } + else { + this.output(undefined, CommandNames.Formatonkey, reqSeq, "no edits") + } + } + } + + completions(line: number, col: number, prefix: string, rawfile: string, cmd: string, reqSeq = 0) { + if (!prefix) { + prefix = ""; + } + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + var completions: ts.CompletionInfo = undefined; + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + if (pos >= 0) { + try { + completions = compilerService.languageService.getCompletionsAtPosition(file, pos); + } + catch (err) { + this.logError(err, cmd); + completions = undefined; + } + if (completions) { + var compressedEntries: ServerProtocol.CompletionItem[] = + completions.entries.reduce((accum: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { + if (entry.name.indexOf(prefix) == 0) { + var protoEntry = {}; + protoEntry.name = entry.name; + protoEntry.kind = entry.kind; + if (entry.kindModifiers && (entry.kindModifiers.length > 0)) { + protoEntry.kindModifiers = entry.kindModifiers; + } + var details = compilerService.languageService.getCompletionEntryDetails(file, pos, entry.name); + if (details && (details.documentation) && (details.documentation.length > 0)) { + protoEntry.documentation = details.documentation; + } + if (details && (details.displayParts) && (details.displayParts.length > 0)) { + protoEntry.displayParts = details.documentation; + } + accum.push(protoEntry); + } + return accum; + }, []); + this.output(compressedEntries, CommandNames.Completions, reqSeq); + } + } + } + if (!completions) { + this.output(undefined, CommandNames.Completions, reqSeq, "no completions"); + } + } + + geterr(ms: number, files: string[]) { + var checkList = files.reduce((accum: PendingErrorCheck[], filename: string) => { + filename = ts.normalizePath(filename); + var project = this.projectService.getProjectForFile(filename); + if (project) { + accum.push({ filename: filename, project: project }); + } + return accum; + }, []); + if (checkList.length > 0) { + this.updateErrorCheck(checkList, this.changeSeq,(n) => n == this.changeSeq, ms) + } + } + + change(line: number, col: number, deleteLen: number, insertString: string, rawfile: string) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + if (pos >= 0) { + var end = pos; + if (deleteLen) { + end += deleteLen; + } + compilerService.host.editScript(file, pos, end, insertString); + this.changeSeq++; + } + } + } + + reload(rawfile: string, rawtmpfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var tmpfile = ts.normalizePath(rawtmpfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + this.changeSeq++; + // make sure no changes happen before this one is finished + project.compilerService.host.reloadScript(file, tmpfile,() => { + this.output(undefined, CommandNames.Reload, reqSeq); + }); + } + } + + saveToTmp(rawfile: string, rawtmpfile: string) { + var file = ts.normalizePath(rawfile); + var tmpfile = ts.normalizePath(rawtmpfile); + + var project = this.projectService.getProjectForFile(file); + if (project) { + project.compilerService.host.saveTo(file, tmpfile); + } + } + + closeClientFile(rawfile: string) { + var file = ts.normalizePath(rawfile); + this.projectService.closeClientFile(file); + } + + decorateNavBarItem(navBarItem: ts.NavigationBarItem, compilerService: CompilerService, file: string) { + if (navBarItem.spans.length == 1) { + var span = navBarItem.spans[0]; + var offset = span.start; + var textForSpan = compilerService.host.getScriptSnapshot(file).getText(offset, offset + span.length); + var adj = textForSpan.indexOf(navBarItem.text); + if (adj > 0) { + offset += adj; + } + var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, + offset + (navBarItem.text.length / 2)); + if (quickInfo) { + var displayString = ts.displayPartsToString(quickInfo.displayParts); + var docString = ts.displayPartsToString(quickInfo.documentation); + navBarItem.displayString = displayString; + navBarItem.docString = docString; + } + } + if (navBarItem.childItems.length > 0) { + navBarItem.childItems = + navBarItem.childItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); + } + return navBarItem; + } + + navbar(rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var navBarItems = compilerService.languageService.getNavigationBarItems(file); + var bakedNavBarItems = navBarItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); + this.sendLineToClient(JSON.stringify(bakedNavBarItems, null, " ")); + } + } + + navto(searchTerm: string, rawfile: string, cmd: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var navItems: ts.NavigateToItem[]; + var cancellationToken = compilerService.host.getCancellationToken(); + if (this.pendingOperation) { + cancellationToken.cancel(); + cancellationToken.reset(); + } + try { + this.pendingOperation = true; + navItems = sortNavItems(compilerService.languageService.getNavigateToItems(searchTerm)); + } + catch (err) { + this.logError(err, cmd); + navItems = undefined; + } + this.pendingOperation = false; + if (navItems) { + var bakedNavItems: ServerProtocol.NavtoItem[] = navItems.map((navItem) => { + var start = compilerService.host.positionToLineCol(navItem.fileName, + navItem.textSpan.start); + var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); + this.abbreviate(start); + var bakedItem: ServerProtocol.NavtoItem = { + name: navItem.name, + kind: navItem.kind, + file: this.encodeFilename(navItem.fileName), + start: start, + end: end, + }; + if (navItem.kindModifiers && (navItem.kindModifiers != "")) { + bakedItem.kindModifiers = navItem.kindModifiers; + } + if (navItem.matchKind != 'none') { + bakedItem.matchKind = navItem.matchKind; + } + if (navItem.containerName && (navItem.containerName.length > 0)) { + bakedItem.containerName = navItem.containerName; + } + if (navItem.containerKind && (navItem.containerKind.length > 0)) { + bakedItem.containerKind = navItem.containerKind; + } + this.abbreviate(bakedItem); + return bakedItem; + }); + + this.output(bakedNavItems, CommandNames.Navto, reqSeq); + } + else { + this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); + } + } + } + + executeJSONcmd(cmd: string) { + try { + var req = JSON.parse(cmd); + switch (req.command) { + case CommandNames.Definition: { + var defArgs = req.arguments; + this.goToDefinition(defArgs.line, defArgs.col, defArgs.file, req.seq); + break; + } + case CommandNames.References: { + var refArgs = req.arguments; + this.findReferences(refArgs.line, refArgs.col, refArgs.file, req.seq); + break; + } + case CommandNames.Rename: { + var renameArgs = req.arguments; + this.rename(renameArgs.line, renameArgs.col, renameArgs.file, req.seq); + break; + } + case CommandNames.Type: { + var typeArgs = req.arguments; + this.goToType(typeArgs.line, typeArgs.col, typeArgs.file, req.seq); + break; + } + case CommandNames.Open: { + var openArgs = req.arguments; + this.openClientFile(openArgs.file); + break; + } + case CommandNames.Quickinfo: { + var quickinfoArgs = req.arguments; + this.quickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file, req.seq); + break; + } + case CommandNames.Format: { + var formatArgs = req.arguments; + this.format(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file, + cmd, req.seq); + break; + } + case CommandNames.Formatonkey: { + var formatOnKeyArgs = req.arguments; + this.formatOnKey(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file, + cmd, req.seq); + break; + } + case CommandNames.Completions: { + var completionsArgs = req.arguments; + this.completions(req.arguments.line, req.arguments.col, completionsArgs.prefix, req.arguments.file, + cmd, req.seq); + break; + } + case CommandNames.Geterr: { + var geterrArgs = req.arguments; + this.geterr(geterrArgs.delay, geterrArgs.files); + break; + } + case CommandNames.Change: { + var changeArgs = req.arguments; + this.change(changeArgs.line, changeArgs.col, changeArgs.deleteLen, changeArgs.insertString, + changeArgs.file); + break; + } + case CommandNames.Reload: { + var reloadArgs = req.arguments; + this.reload(reloadArgs.file, reloadArgs.tmpfile, req.seq); + break; + } + case CommandNames.Saveto: { + var savetoArgs = req.arguments; + this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); + break; + } + case CommandNames.Close: { + var closeArgs = req.arguments; + this.closeClientFile(closeArgs.file); + break; + } + case CommandNames.Navto: { + var navtoArgs = req.arguments; + this.navto(navtoArgs.searchTerm, navtoArgs.file, cmd, req.seq); + break; + } + default: { + this.projectService.log("Unrecognized JSON command: " + cmd); + break; + } + } + } catch (err) { + this.logError(err, cmd); + } + } + + sendAbbrev(reqSeq = 0) { + if (!this.fetchedAbbrev) { + this.output(this.abbrevTable, CommandNames.Abbrev, reqSeq); + } + this.fetchedAbbrev = true; + } + + executeCmd(cmd: string) { + var line: number, col: number, file: string; + var m: string[]; + + try { + if (m = cmd.match(/^dbg start$/)) { + this.debugSession = new JsDebugSession(this.host.getDebuggerClient()); + } + else if (m = cmd.match(/^dbg cont$/)) { + if (this.debugSession) { + this.debugSession.cont((err, body, res) => { + }); + } + } + else if (m = cmd.match(/^dbg src$/)) { + if (this.debugSession) { + this.debugSession.listSrc(); + } + } + else if (m = cmd.match(/^dbg brk (\d+) (.*)$/)) { + line = parseInt(m[1]); + file = ts.normalizePath(m[2]); + if (this.debugSession) { + this.debugSession.setBreakpointOnLine(line, file); + } + } + else if (m = cmd.match(/^dbg eval (.*)$/)) { + var code = m[1]; + if (this.debugSession) { + this.debugSession.evaluate(code); + } + } + else if (m = cmd.match(/^definition (\d+) (\d+) (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + file = m[3]; + this.goToDefinition(line, col, file); + } + else if (m = cmd.match(/^rename (\d+) (\d+) (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + file = m[3]; + this.rename(line, col, file); + } + else if (m = cmd.match(/^type (\d+) (\d+) (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + file = m[3]; + this.goToType(line, col, file); + } + else if (m = cmd.match(/^open (.*)$/)) { + file = m[1]; + this.openClientFile(file); + } + else if (m = cmd.match(/^references (\d+) (\d+) (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + file = m[3]; + this.findReferences(line, col, file); + } + else if (m = cmd.match(/^quickinfo (\d+) (\d+) (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + file = m[3]; + this.quickInfo(line, col, file); + } + else if (m = cmd.match(/^format (\d+) (\d+) (\d+) (\d+) (.*)$/)) { + // format line col endLine endCol file + line = parseInt(m[1]); + col = parseInt(m[2]); + var endLine = parseInt(m[3]); + var endCol = parseInt(m[4]); + file = m[5]; + this.format(line, col, endLine, endCol, file, cmd); + } + else if (m = cmd.match(/^formatonkey (\d+) (\d+) (\{\".*\"\})\s* (.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + var key = JSON.parse(m[3].substring(1, m[3].length - 1)); + file = m[4]; + this.formatOnKey(line, col, key, file, cmd); + } + else if (m = cmd.match(/^completions (\d+) (\d+) (\{.*\})?\s*(.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + var prefix = ""; + file = m[4]; + if (m[3]) { + prefix = m[3].substring(1, m[3].length - 1); + } + this.completions(line, col, prefix, file, cmd); + } + else if (m = cmd.match(/^geterr (\d+) (.*)$/)) { + var ms = parseInt(m[1]); + var rawFiles = m[2]; + this.geterr(ms, rawFiles.split(';')); + } + else if (m = cmd.match(/^change (\d+) (\d+) (\d+) (\d+) (\{\".*\"\})?\s*(.*)$/)) { + line = parseInt(m[1]); + col = parseInt(m[2]); + var deleteLen = parseInt(m[3]); + var insertLen = parseInt(m[4]); + var insertString: string; + if (insertLen) { + insertString = JSON.parse(m[5].substring(1, m[5].length - 1)); + } + file = m[6]; + this.change(line, col, deleteLen, insertString, file); + } + else if (m = cmd.match(/^reload (.*) from (.*)$/)) { + this.reload(m[1], m[2]); + } + // TODO: change this to saveto + else if (m = cmd.match(/^save (.*) to (.*)$/)) { + this.saveToTmp(m[1], m[2]); + } + else if (m = cmd.match(/^close (.*)$/)) { + this.closeClientFile(m[1]); + } + else if (m = cmd.match(/^navto (\{.*\}) (.*)$/)) { + var searchTerm = m[1]; + searchTerm = searchTerm.substring(1, searchTerm.length - 1); + this.navto(searchTerm, m[2], cmd); + } + else if (m = cmd.match(/^navbar (.*)$/)) { + this.navbar(m[1]); + } + else if (m = cmd.match(/^abbrev/)) { + this.sendAbbrev(); + } + else if (m = cmd.match(/^pretty/)) { + this.prettyJSON = true; + } + else if (m = cmd.match(/^printproj/)) { + this.projectService.printProjects(); + } + else if (m = cmd.match(/^fileproj (.*)$/)) { + file = ts.normalizePath(m[1]); + this.projectService.printProjectsForFile(file); + } + else { + this.output(undefined, CommandNames.Unknown, 0, "Unrecognized command " + cmd); + } + } catch (err) { + this.logError(err, cmd); + } + } + } +} diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts new file mode 100644 index 0000000000000..75a41cb81ce70 --- /dev/null +++ b/src/server/protodef.d.ts @@ -0,0 +1,591 @@ +/** Declaration module describing the TypeScript Server protocol */ +declare module ServerProtocol { + /** A TypeScript Server message */ + export interface Message { + /** Sequence number of the message */ + seq: number; + /** One of "request", "response", or "event" */ + type: string; + } + + /** Client-initiated request message */ + export interface Request extends Message { + /** The command to execute */ + command: string; + /** Object containing arguments for the command */ + arguments?: any; + } + + /** Server-initiated event message */ + export interface Event extends Message { + /** Name of event */ + event: string; + /** Event-specific information */ + body?: any; + } + + /** Response by server to client request message */ + export interface Response extends Message { + /** Sequence number of the request message */ + request_seq: number; + /** Outcome of the request */ + success: boolean; + /** The command requested */ + command: string; + /** Contains error message if success == false. */ + message?: string; + /** Contains message body if success == true. */ + body?: any; + } + + /** Arguments for FileRequest messages */ + export interface FileRequestArgs { + /** The file for the request (absolute pathname required) */ + file: string; + } + + /** + Request whose sole parameter is a file name + */ + export interface FileRequest extends Request { + arguments: FileRequestArgs; + } + + /** + Instances of this interface specify a code location: + (file, line, col), where line and column are 1-based. + */ + export interface CodeLocationRequestArgs extends FileRequestArgs { + /** The line number for the request (1-based) */ + line: number; + /** The column for the request (1-based) */ + col: number; + } + + /** + A request whose arguments specify a code location (file, line, col) + */ + export interface CodeLocationRequest extends FileRequest { + arguments: CodeLocationRequestArgs; + } + + /** + Go to definition request; value of command field is + "definition". Return response giving the code locations that + define the symbol found in file at location line, col. + */ + export interface DefinitionRequest extends CodeLocationRequest { + } + + /** + Object containing line and column (one-based) of code location + */ + export interface LineCol { + line: number; + col: number; + } + + /** + Object found in response messages defining a span of text in + source code. + */ + export interface CodeSpan { + /** File containing the definition */ + file: string; + /** First character of the definition */ + start: LineCol; + /** One character past last character of the definition */ + end: LineCol; + } + + /** + Definition response message. Gives text range for definition. + */ + export interface DefinitionResponse extends Response { + body?: CodeSpan[]; + } + + /** + Find references request; value of command field is + "references". Return response giving the code locations that + reference the symbol found in file at location line, col. + */ + export interface ReferencesRequest extends CodeLocationRequest { + } + + export interface ReferencesResponseItem extends CodeSpan { + /** Text of line containing the reference. Including this + with the response avoids latency of editor loading files + to show text of reference line (the server already has + loaded the referencing files). + */ + lineText: string; + } + + /** The body of a "references" response message. */ + export interface ReferencesResponseBody { + /** The code locations referencing the symbol */ + refs: ReferencesResponseItem[]; + /** The name of the symbol */ + symbolName: string; + /** + The start column of the symbol (on the line provided by the references request) + */ + symbolStartCol: number; + /** The full display name of the symbol */ + symbolDisplayString: string; + } + + /** Response to "references" request. */ + export interface ReferencesResponse extends Response { + body?: ReferencesResponseBody; + } + + /** + Rename request; value of command field is "rename". Return + response giving the code locations that reference the symbol + found in file at location line, col. Also return full display + name of the symbol so that client can print it unambiguously. + */ + export interface RenameRequest extends CodeLocationRequest { + } + + /** Information about the item to be renamed. */ + export interface RenameInfo { + /** True if item can be renamed */ + canRename: boolean; + /** Error message if item can not be renamed */ + localizedErrorMessage: string; + /** Display name of the item to be renamed */ + displayName: string; + /** Full display name of item to be renamed */ + fullDisplayName: string; + /** The items's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers: string; + } + + /** + Rename response message. + */ + export interface RenameResponse extends Response { + body?: { + /** Information about the item to be renamed */ + info: RenameInfo; + /** + An array of code locations that refer to the + item to be renamed. + */ + locs: CodeSpan[]; + } + } + + /** + Type request; value of command field is "type". Return response + giving the code locations that define the type of the symbol + found in file at location line, col. + */ + export interface TypeRequest extends CodeLocationRequest { + } + + export interface TypeResponse extends Response { + body?: CodeSpan[]; + } + + /** + Open request; value of command field is "open". Notify the + server that the client has file open. The server will not + monitor the filesystem for changes in this file and will assume + that the client is updating the server (using the change and/or + reload messages) when the file changes. Server does not currently + send a response to an open request. + */ + export interface OpenRequest extends FileRequest { + } + + /** + Close request; value of command field is "close". Notify the + server that the client has closed a previously open file. If + file is still referenced by open files, the server will resume + monitoring the filesystem for changes to file. Server does not + currently send a response to a close request. + */ + export interface CloseRequest extends FileRequest { + } + + /** + Quickinfo request; value of command field is + "quickinfo". Return response giving a quick type and + documentation string for the symbol found in file at location + line, col. + */ + export interface QuickInfoRequest extends CodeLocationRequest { + } + + /** Body of QuickInfoResponse */ + export interface QuickInfoResponseBody { + /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers: string; + /** Starting code location of symbol */ + start: LineCol; + /** One past last character of symbol */ + end: LineCol; + /** Type and kind of symbol */ + displayString: string; + /** Documentation associated with symbol */ + documentation: string; + } + + /** Quickinfo response message */ + export interface QuickInfoResponse extends Response { + body?: QuickInfoResponseBody; + } + + /** Arguments for format messages */ + export interface FormatRequestArgs extends CodeLocationRequestArgs { + /** Last line of range for which to format text in file */ + endLine: number; + /** Last column of range for which to format text in file */ + endCol: number; + } + + /** + Format request; value of command field is "format". Return + response giving zero or more edit instructions. The edit + instructions will be sorted in file order. Applying the edit + instructions in reverse to file will result in correctly + reformatted text. + */ + export interface FormatRequest extends CodeLocationRequest { + arguments: FormatRequestArgs; + } + + /** + Object found in response messages defining an editing + instruction for a span of text in source code. The effect of + this instruction is to replace the text starting at start and + ending one character before end with newText. For an insertion, + the text span is empty. For a deletion, newText is empty. + */ + export interface CodeEdit { + /** First character of the text span to edit. */ + start: LineCol; + /** One character past last character of the text span to edit */ + end: LineCol; + /** + Replace the span defined above with this string (may be + the empty string) + */ + newText: string; + } + + /** Format and format on key response message */ + export interface FormatResponse extends Response { + body?: CodeEdit[]; + } + + /** Arguments for format on key messages */ + export interface FormatOnKeyRequestArgs extends CodeLocationRequestArgs { + /** Key pressed (';', '\n', or '}') */ + key: string; + } + + /** + Format on key request; value of command field is + "formatonkey". Given file location and key typed (as string), + return response giving zero or more edit instructions. The + edit instructions will be sorted in file order. Applying the + edit instructions in reverse to file will result in correctly + reformatted text. + */ + export interface FormatOnKeyRequest extends CodeLocationRequest { + arguments: FormatOnKeyRequestArgs; + } + + /** Arguments for completions messages */ + export interface CompletionsRequestArgs extends CodeLocationRequestArgs { + /** Optional prefix to apply to possible completions. */ + prefix?: string; + } + + /** + Completions request; value of command field is "completions". + Given a file location (file, line, col) and a prefix (which may + be the empty string), return the possible completions that + begin with prefix. + */ + export interface CompletionsRequest extends CodeLocationRequest { + arguments: CompletionsRequestArgs; + } + + /** + Part of a symbol description. + */ + export interface SymbolDisplayPart { + /** Text of an item describing the symbol */ + text: string; + /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + } + + /** An item found in a completion response */ + export interface CompletionItem { + /** The symbol's name */ + name: string; + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + /** Display parts of the symbol (similar to quick info) */ + displayParts?: SymbolDisplayPart[]; + /** Documentation strings for the symbol */ + documentation?: SymbolDisplayPart[]; + } + + export interface CompletionsResponse extends Response { + body?: CompletionItem[]; + } + + /** Arguments for geterr messages. */ + export interface GeterrRequestArgs { + /** + List of file names for which to compute compiler errors. + The files will be checked in list order. + */ + files: string[]; + /** + Delay in milliseconds to wait before starting to compute + errors for the files in the file list + */ + delay: number; + } + + /** + Geterr request; value of command field is "geterr". Wait for + delay milliseconds and then, if during the wait no change or + reload messages have arrived for the first file in the files + list, get the syntactic errors for the file, field requests, + and then get the semantic errors for the file. Repeat with a + smaller delay for each subsequent file on the files list. Best + practice for an editor is to send a file list containing each + file that is currently visible, in most-recently-used order. + */ + export interface GeterrRequest extends Request { + arguments: GeterrRequestArgs; + } + + /** Item of diagnostic information found in a DiagEvent message */ + export interface Diagnostic { + /** Starting code location at which text appies */ + start: LineCol; + /** Length of code location at which text applies */ + len: number; + /** Text of diagnostic message */ + text: string; + } + + /** Event message for "syntaxDiag" and "semanticDiag" event types. + These events provide syntactic and semantic errors for a file. + */ + export interface DiagEvent extends Event { + body?: { + /** The file for which diagnostic information is reported */ + file: string; + /** An array of diagnostic information items */ + diagnostics: Diagnostic[]; + }; + } + + /** Arguments for reload request. */ + export interface ReloadRequestArgs extends FileRequestArgs { + /** + Name of temporary file from which to reload file + contents. May be same as file. + */ + tmpfile: string; + } + + /** + Reload request message; value of command field is "reload". + Reload contents of file with name given by the 'file' argument + from temporary file with name given by the 'tmpfile' argument. + The two names can be identical. + */ + export interface ReloadRequest extends FileRequest { + arguments: ReloadRequestArgs; + } + + /** + Response to "reload" request. This is just an acknowledgement, so + no body field is required. + */ + export interface ReloadResponse extends Response { + } + + /** Arguments for saveto request. */ + export interface SavetoRequestArgs extends FileRequestArgs { + /** + Name of temporary file into which to save server's view of + file contents. + */ + tmpfile: string; + } + + /** + Saveto request message; value of command field is "saveto". + For debugging purposes, save to a temporaryfile (named by + argument 'tmpfile') the contents of file named by argument + 'file'. The server does not currently send a response to a + "saveto" request. + */ + export interface SavetoRequest extends FileRequest { + arguments: SavetoRequestArgs; + } + + /** Arguments for navto request message */ + export interface NavtoRequestArgs extends FileRequestArgs { + /** + Search term to navigate to from current location; term can + be '.*' or an identifier prefix. + */ + searchTerm: string; + } + + /** + Navto request message; value of command field is "navto". + Return list of objects giving code locations and symbols that + match the search term given in argument 'searchTerm'. The + context for the search is given by the named file. + */ + export interface NavtoRequest extends FileRequest { + arguments: NavtoRequestArgs; + } + + /** An item found in a navto response. */ + export interface NavtoItem { + /** The symbol's name */ + name: string; + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + /** exact, substring, or prefix */ + matchKind?: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + /** + The file in which the symbol is found; the value of this + field will always be a string unless the client opts-in to + file encoding by sending the "abbrev" request. + */ + file: EncodedFile; + /** The location within file at which the symbol is found*/ + start: LineCol; + /** One past the last character of the symbol */ + end: LineCol; + /** + Name of symbol's container symbol (if any); for example, + the class name if symbol is a class member + */ + containerName?: string; + /** Kind of symbol's container symbol (if any) */ + containerKind?: string; + } + + /** + Navto response message. Body is an array of navto items. Each + item gives a symbol that matched the search term. + */ + export interface NavtoResponse extends Response { + body?: NavtoItem[]; + } + + /** Arguments for change request message. */ + export interface ChangeRequestArgs extends CodeLocationRequestArgs { + /** + Length of span deleted at location (file, line, col); nothing deleted + if this field is zero or undefined. + */ + deleteLen?: number; + /** Optional string to insert at location (file, line col). */ + insertString?: string; + } + + /** + Change request message; value of command field is "change". + Update the server's view of the file named by argument 'file'. + Server does not currently send a response to a change request. + */ + export interface ChangeRequest extends CodeLocationRequest { + arguments: ChangeRequestArgs; + } + + /* + The following messages describe an OPTIONAL compression scheme + that clients can choose to use. If a client does not opt-in to + this scheme by sending an "abbrev" request, the server will not + compress messages. + */ + + /** + Abbrev request message; value of command field is "abbrev". + Server returns an array of mappings from common message field + names and common message field string values to shortened + versions of these strings. Once a client opts-in by requesting + the abbreviations, the server may send responses whose field + names are shortened. The server may also send file names as + instances of AssignFileId, or as file ids, if the corresponding + id assignment had been previously returned. Currently, only + responses to the "navto" request can be encoded. + */ + export interface AbbrevRequest extends Request { + } + + /** + If an object of this type is returned in place of a string as + the value of a file field in a response message, add the + mapping id => file to the client's cache of file id mappings, + and interpret the value as if it was the string in the 'file' + field. + */ + export interface IdFile { + /** Id to assign to file */ + id: number; + /** File name that will correspond to id */ + file: string; + } + + /** + The type of an encoded file name. If of type number, the value + is a file id. If of type IdFile, the value is interpreted as + 'file' and in addition the mapping 'id' to 'file' is + established. If of type string, the value is simply the file + name. + */ + export type EncodedFile = number | IdFile | string; + + /** + Response to abbrev opt-in request message. This is a map of + string field names and common string field values to shortened + strings. Once the server sends this response, it will assume + that it can use the shortened field names and field values. In + addition, the server will assume it can assign ids to file + names by returning an AssignFileId instance in place of a file + name. Once an AssignFileId instance is returned, the server + may send the file id (a number) in place of the file name. + */ + export interface AbbrevResponse extends Response { + body?: { + /** Map from full string (either field name or string + field value) to shortened string */ + [fullString: string]: string; + } + } +} + + + + + + diff --git a/src/server/server.ts b/src/server/server.ts new file mode 100644 index 0000000000000..ec0ee293ce3e1 --- /dev/null +++ b/src/server/server.ts @@ -0,0 +1,134 @@ +/// +/// + +module ts.server { + var nodeproto: typeof NodeJS._debugger = require('_debugger'); + var readline: NodeJS.ReadLine = require('readline'); + var path: NodeJS.Path = require('path'); + var fs: typeof NodeJS.fs = require('fs'); + + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, + }); + + class Logger implements ts.server.Logger { + fd = -1; + seq = 0; + inGroup = false; + firstInGroup = true; + + constructor(public logFilename: string) { + } + + static padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); + } + + close() { + if (this.fd >= 0) { + fs.close(this.fd); + } + } + + perftrc(s: string) { + this.msg(s, "Perf"); + } + + info(s: string) { + this.msg(s, "Info"); + } + + startGroup() { + this.inGroup = true; + this.firstInGroup = true; + } + + endGroup() { + this.inGroup = false; + this.seq++; + this.firstInGroup = true; + } + + msg(s: string, type = "Err") { + if (this.fd < 0) { + this.fd = fs.openSync(this.logFilename, "w"); + } + if (this.fd >= 0) { + s = s + "\n"; + var prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); + if (this.firstInGroup) { + s = prefix + s; + this.firstInGroup = false; + } + if (!this.inGroup) { + this.seq++; + this.firstInGroup = true; + } + var buf = new Buffer(s); + fs.writeSync(this.fd, buf, 0, buf.length, null); + } + } + } + + class IOSession extends Session { + protocol: NodeJS._debugger.Protocol; + + constructor(host: ServerHost, logger: ts.server.Logger, useProtocol: boolean, prettyJSON: boolean) { + super(host, logger, useProtocol, prettyJSON); + if (useProtocol) { + this.initProtocol(); + } + } + + initProtocol() { + this.protocol = new nodeproto.Protocol(); + // note: onResponse was named by nodejs authors; we are re-purposing the Protocol + // class in this case so that it supports a server instead of a client + this.protocol.onResponse = (pkt) => { + this.handleRequest(pkt); + }; + } + + handleRequest(req: NodeJS._debugger.Packet) { + this.projectService.log("Got JSON msg:\n" + req.raw); + } + + listen() { + rl.on('line',(input: string) => { + var cmd = input.trim(); + if (cmd.indexOf("{") == 0) { + // assumption is JSON on single line + // plan is to also carry this protocol + // over tcp, in which case JSON would + // have a Content-Length header + this.executeJSONcmd(cmd); + } + else { + this.executeCmd(cmd); + } + }); + + rl.on('close',() => { + this.projectService.closeLog(); + this.projectService.log("Exiting..."); + process.exit(0); + }); + } + } + + // This places log file in the directory containing editorServices.js + // TODO: check that this location is writable + var logger = new Logger(__dirname + "/.log" + process.pid.toString()); + + var host: ServerHost = ts.sys; + + // Wire the debugging interface + if (!host.getDebuggerClient) { + host.getDebuggerClient = () => new nodeproto.Client(); + } + + // Start listening + new IOSession(host, logger, /* useProtocol */ true, /* prettyJSON */ false).listen(); +} \ No newline at end of file From 7b28f20affdc7e59e5990299c857e7a7aa33b471 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 19:43:10 -0800 Subject: [PATCH 05/45] Wire the build for the server code --- Jakefile | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Jakefile b/Jakefile index b9406f1a43322..acd81b0ccd5ec 100644 --- a/Jakefile +++ b/Jakefile @@ -8,6 +8,7 @@ var child_process = require("child_process"); // Variables var compilerDirectory = "src/compiler/"; var servicesDirectory = "src/services/"; +var serverDirectory = "src/server/"; var harnessDirectory = "src/harness/"; var libraryDirectory = "src/lib/"; var scriptsDirectory = "scripts/"; @@ -90,6 +91,15 @@ var servicesSources = [ return path.join(servicesDirectory, f); })); +var serverSources = [ + "node.d.ts", + "editorServices.ts", + "protocol.ts", + "server.ts" +].map(function (f) { + return path.join(serverDirectory, f); +}); + var definitionsRoots = [ "compiler/types.d.ts", "compiler/scanner.d.ts", @@ -130,6 +140,12 @@ var harnessSources = [ "services/preProcessFile.ts" ].map(function (f) { return path.join(unittestsDirectory, f); +})).concat([ + "protocol.ts", + "client.ts", + "editorServices.ts", +].map(function (f) { + return path.join(serverDirectory, f); })); var librarySourceMap = [ @@ -382,9 +398,12 @@ compileFile(nodeDefinitionsFile, servicesSources,[builtLocalDirectory, copyright jake.rmRf(tempDirPath, {silent: true}); }); +var serverFile = path.join(builtLocalDirectory, "typescriptServer.js"); +compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true); + // Local target to build the compiler and services desc("Builds the full compiler and services"); -task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile]); +task("local", ["generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile]); // Local target to build only tsc.js desc("Builds only the compiler"); From abec4f97988f4bf02bda0c422566bedd3a2f4dea Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 19:43:30 -0800 Subject: [PATCH 06/45] Wire tests to use the new server --- src/harness/fourslash.ts | 2 + src/harness/fourslashRunner.ts | 7 ++- src/harness/harness.ts | 7 +-- src/harness/harnessLanguageService.ts | 68 ++++++++++++++++++--------- src/harness/runner.ts | 4 ++ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 603a78b5f75b9..9c61c3b1e274d 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -282,6 +282,8 @@ module FourSlash { return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions); case FourSlashTestType.Shims: return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions); + case FourSlashTestType.Server: + return new Harness.LanguageService.ServerLanugageServiceAdapter(cancellationToken, compilationOptions); default: throw new Error("Unknown FourSlash test type: "); } diff --git a/src/harness/fourslashRunner.ts b/src/harness/fourslashRunner.ts index fe3b6c7a91f8d..1ab6e31cdbbdb 100644 --- a/src/harness/fourslashRunner.ts +++ b/src/harness/fourslashRunner.ts @@ -4,7 +4,8 @@ const enum FourSlashTestType { Native, - Shims + Shims, + Server } class FourSlashRunner extends RunnerBase { @@ -22,6 +23,10 @@ class FourSlashRunner extends RunnerBase { this.basePath = 'tests/cases/fourslash/shims'; this.testSuiteName = 'fourslash-shims'; break; + case FourSlashTestType.Server: + this.basePath = 'tests/cases/fourslash/server'; + this.testSuiteName = 'fourslash-server'; + break; } } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 534bf85d119ea..cbc4064119a65 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -16,14 +16,15 @@ /// /// +/// +/// +/// /// /// /// /// -declare var require: any; -declare var process: any; -var Buffer = require('buffer').Buffer; +var Buffer: BufferConstructor = require('buffer').Buffer; // this will work in the browser via browserify var _chai: typeof chai = require('chai'); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 2f95f5072d067..eb0daf74dcf0f 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -1,5 +1,6 @@ /// /// +/// /// module Harness.LanguageService { @@ -23,18 +24,18 @@ module Harness.LanguageService { this.version++; } - public editContent(minChar: number, limChar: number, newText: string): void { + public editContent(start: number, end: number, newText: string): void { // Apply edits - var prefix = this.content.substring(0, minChar); + var prefix = this.content.substring(0, start); var middle = newText; - var suffix = this.content.substring(limChar); + var suffix = this.content.substring(end); this.setContent(prefix + middle + suffix); // Store edit range + new length of script this.editRanges.push({ length: this.content.length, textChangeRange: ts.createTextChangeRange( - ts.createTextSpanFromBounds(minChar, limChar), newText.length) + ts.createTextSpanFromBounds(start, end), newText.length) }); // Update version # @@ -127,9 +128,7 @@ module Harness.LanguageService { protected settings = ts.getDefaultCompilerOptions()) { } - public getNewLine(): string { - return "\r\n"; - } + public getNewLine(): string { return "\r\n"; } public getFilenames(): string[] { var fileNames: string[] = []; @@ -145,20 +144,10 @@ module Harness.LanguageService { this.fileNameToScript[fileName] = new ScriptInfo(fileName, content); } - public updateScript(fileName: string, content: string) { - var script = this.getScriptInfo(fileName); - if (script !== null) { - script.updateContent(content); - return; - } - - this.addScript(fileName, content); - } - - public editScript(fileName: string, minChar: number, limChar: number, newText: string) { + public editScript(fileName: string, start: number, end: number, newText: string) { var script = this.getScriptInfo(fileName); if (script !== null) { - script.editContent(minChar, limChar, newText); + script.editContent(start, end, newText); return; } @@ -236,8 +225,7 @@ module Harness.LanguageService { getFilenames(): string[] { return this.nativeHost.getFilenames(); } getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); } addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); } - updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); } - editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); } + editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); } lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); } positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); } @@ -442,5 +430,43 @@ module Harness.LanguageService { return convertResult; } } + + // Server adapter + class ServerLanguageServiceHost extends NativeLanguageServiceHost { + private client: ts.server.SessionClient; + constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) { + super(cancellationToken, settings); + } + + setClient(client: ts.server.SessionClient) { + this.client = client; + } + + addScript(fileName: string, content: string): void { + super.addScript(fileName, content); + this.client.openFile(fileName); + } + + editScript(fileName: string, start: number, end: number, newText: string) { + super.editScript(fileName, start, end, newText); + this.client.changeFile(fileName, start, end, newText); + } + } + + export class ServerLanugageServiceAdapter implements LanguageServiceAdapter { + private host: ServerLanguageServiceHost; + private client: ts.server.SessionClient; + constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + debugger; + + this.host = new ServerLanguageServiceHost(cancellationToken, options); + this.client = new ts.server.SessionClient(this.host); + this.host.setClient(this.client); + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return this.client; } + getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } + } } \ No newline at end of file diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 942ae56bf6fe8..e1e5429b00723 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -66,6 +66,9 @@ if (testConfigFile !== '') { case 'fourslash-shims': runners.push(new FourSlashRunner(FourSlashTestType.Shims)); break; + case 'fourslash-server': + runners.push(new FourSlashRunner(FourSlashTestType.Server)); + break; case 'fourslash-generated': runners.push(new GeneratedFourslashRunner(FourSlashTestType.Native)); break; @@ -95,6 +98,7 @@ if (runners.length === 0) { // language services runners.push(new FourSlashRunner(FourSlashTestType.Native)); runners.push(new FourSlashRunner(FourSlashTestType.Shims)); + runners.push(new FourSlashRunner(FourSlashTestType.Server)); //runners.push(new GeneratedFourslashRunner()); } From 54f3250cdd6eeed608dcdcec4ed9e9cd792f7140 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 19:48:32 -0800 Subject: [PATCH 07/45] Add fomratting tests --- tests/cases/fourslash/server/format.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/cases/fourslash/server/format.ts diff --git a/tests/cases/fourslash/server/format.ts b/tests/cases/fourslash/server/format.ts new file mode 100644 index 0000000000000..75d06eef0c2e4 --- /dev/null +++ b/tests/cases/fourslash/server/format.ts @@ -0,0 +1,8 @@ +/// + +/////**/module Default{var x= ( { } ) ;} + + +format.document(); +goTo.marker(); +verify.currentLineContentIs('module Default { var x = ({}); }'); \ No newline at end of file From 096364408959e57cb553de5bcaca56b6fa520f0e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 11 Feb 2015 19:49:36 -0800 Subject: [PATCH 08/45] Add type annotations --- src/server/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 1fb93329e03d5..cf169c6e4cfa5 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -35,8 +35,8 @@ module ts.server { getCurrentDirectory: () => host.getCurrentDirectory(), readDirectory: (path: string, extension?: string) => [], - getModififedTime: (fileName) => new Date(), - stat: (path, callback) => { throw new Error("Not implemented Yet."); }, + getModififedTime: (fileName: string) => new Date(), + stat: (path: string, callback?: (err: any, stats: any) => any) => { throw new Error("Not implemented Yet."); }, }, { close: () => { }, info: (m) => this.host.log(m), From ee3ee05cc430e64b4834266423025bccb82eb084 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 08:20:41 -0800 Subject: [PATCH 09/45] wire package.json command for the server --- bin/tsserver | 2 ++ package.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 bin/tsserver diff --git a/bin/tsserver b/bin/tsserver new file mode 100644 index 0000000000000..2327c344c44c3 --- /dev/null +++ b/bin/tsserver @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./typescriptServer.js') diff --git a/package.json b/package.json index 94b5435be1965..9261174b68f3b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "preferGlobal": true, "main": "./bin/typescript.js", "bin": { - "tsc": "./bin/tsc" + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" }, "engines": { "node": ">=0.8.0" From 07d37fa7fb8d773160e78e1b5fe4268919217b61 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 10:31:41 -0800 Subject: [PATCH 10/45] Simplify host logic in client --- src/server/client.ts | 147 ++++++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index cf169c6e4cfa5..6456ab9aa0864 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -2,57 +2,119 @@ module ts.server { - export interface SessionClientHost extends LanguageServiceHost { + export interface SessionClientHost extends LanguageServiceHost { lineColToPosition(fileName: string, line: number, col: number): number; positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter; } - export class SessionClient implements LanguageService { - private session: Session; - private sequence: number; - private lastReply: string; + class SessionClientHostProxy implements ServerHost, Logger { + args: string[] = []; + newLine: string; + useCaseSensitiveFileNames: boolean = false; + lastReply: string; constructor(private host: SessionClientHost) { - this.sequence = 0; + this.newLine = this.host.getNewLine(); + } + + write(message: string): void { + this.lastReply = message; + } + + readFile(fileName: string): string { + var snapshot = this.host.getScriptSnapshot(fileName); + return snapshot && snapshot.getText(0, snapshot.getLength()); + } + + writeFile(name: string, text:string, writeByteOrderMark: boolean): void { + } + + resolvePath(path: string): string { + return path; + } + + fileExists(path: string): boolean { + return !!this.host.getScriptSnapshot(path); + } + + directoryExists(path: string): boolean { + return false; + } + + getExecutingFilePath(): string { + return ""; + } + + exit(exitCode: number): void { + } - this.session = new Session({ - args: [], - newLine: host.getNewLine(), - useCaseSensitiveFileNames: true, - write: (s) => this.lastReply = s, - readFile: (fileName): string => { - var snapshot = host.getScriptSnapshot(fileName); - return snapshot && snapshot.getText(0, snapshot.getLength()); - }, - writeFile: (name, text, writeByteOrderMark) => { - }, - resolvePath: (path) => path, - fileExists: (path) => !!host.getScriptSnapshot(path), - directoryExists: (path) => false, - getExecutingFilePath: () => "", - exit: (exitCode) => { }, - createDirectory: (directoryName: string) => { }, - - getCurrentDirectory: () => host.getCurrentDirectory(), - readDirectory: (path: string, extension?: string) => [], - getModififedTime: (fileName: string) => new Date(), - stat: (path: string, callback?: (err: any, stats: any) => any) => { throw new Error("Not implemented Yet."); }, - }, { - close: () => { }, - info: (m) => this.host.log(m), - msg: (m) => this.host.log(m), - endGroup: () => { }, - perftrc: (m) => this.host.log(m), - startGroup: () => { } - }, /* useProtocol */ true, /*prettyJSON*/ true); + createDirectory(directoryName: string): void { + throw new Error("Not Implemented Yet."); + } + + getCurrentDirectory(): string { + return this.host.getCurrentDirectory(); + } + + readDirectory(path: string, extension?: string): string[] { + throw new Error("Not implemented Yet."); + } + + getModififedTime(fileName: string): Date { + return new Date(); + } + + stat(path: string, callback?: (err: any, stats: any) => any) { + throw new Error("Not implemented Yet."); } - private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { - return this.host.lineColToPosition(fileName, lineCol.line, lineCol.col); + lineColToPosition(fileName: string, line: number, col: number): number { + return this.host.lineColToPosition(fileName, line, col); } - private getFileLength(fileName: string): number { + positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { + return this.host.positionToZeroBasedLineCol(fileName, position); + } + + getFileLength(fileName: string): number { return this.host.getScriptSnapshot(fileName).getLength(); + } + + close(): void { + } + + info(message: string): void { + return this.host.log(message); + } + + msg(message: string) { + return this.host.log(message); + } + + endGroup(): void { + } + + perftrc(message: string): void { + return this.host.log(message); + } + + startGroup(): void { + } + } + + export class SessionClient implements LanguageService { + private session: Session; + private sequence: number = 0; + private host: SessionClientHostProxy; + + constructor(host: SessionClientHost) { + this.sequence = 0; + this.host = new SessionClientHostProxy(host); + this.session = new Session(this.host, this.host, /* useProtocol */ true, /*prettyJSON*/ true); + } + + private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { + return this.host.lineColToPosition(fileName, lineCol.line, lineCol.col); } private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { @@ -103,7 +165,7 @@ module ts.server { // Read the content length var contentLengthPrefix = "Content-Length: "; - var lines = this.lastReply.split("\r\n"); + var lines = this.host.lastReply.split("\r\n"); Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); var contentLengthText = lines[0]; @@ -120,7 +182,7 @@ module ts.server { var response: T = JSON.parse(responseBody); } catch (e) { - throw new Error("Malformed response: Failed to parse server response: " + this.lastReply + ". \r\n Error detailes: " + e.message); + throw new Error("Malformed response: Failed to parse server response: " + this.host.lastReply + ". \r\n Error detailes: " + e.message); } // verify the sequence numbers @@ -239,7 +301,7 @@ module ts.server { } getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return this.getFormattingEditsForRange(fileName, 0, this.getFileLength(fileName), options); + return this.getFormattingEditsForRange(fileName, 0, this.host.getFileLength(fileName), options); } getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { @@ -372,7 +434,6 @@ module ts.server { throw new Error("Not Implemented Yet."); } - getProgram(): Program { throw new Error("SourceFile objects are not serializable through the server protocol."); } From 6e94f39397350bb8215b07ad1afba1fa397b5b85 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 10:34:57 -0800 Subject: [PATCH 11/45] Remove redundant type definitions --- src/server/protocol.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index abbce0b358881..bd05cfbd533d6 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -9,11 +9,6 @@ module ts { displayString?: string; docString?: string; } - - export interface System { - getModififedTime? (fileName: string): Date; - stat? (path: string, callback?: (err: any, stats: any) => any): void; - } } module ts.server { From 4a44b7437a4c4c9f328b9396efb0703753dcc0af Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 11:53:13 -0800 Subject: [PATCH 12/45] Supportt abbreviation --- src/harness/harnessLanguageService.ts | 2 +- src/server/client.ts | 87 ++++++++++++++++++++++++--- src/server/protocol.ts | 4 ++ 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index eb0daf74dcf0f..2c0d2549c8e70 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -460,7 +460,7 @@ module Harness.LanguageService { debugger; this.host = new ServerLanguageServiceHost(cancellationToken, options); - this.client = new ts.server.SessionClient(this.host); + this.client = new ts.server.SessionClient(this.host, /*abbreviate*/ true); this.host.setClient(this.client); } getHost() { return this.host; } diff --git a/src/server/client.ts b/src/server/client.ts index 6456ab9aa0864..908562e589b2f 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -106,11 +106,53 @@ module ts.server { private session: Session; private sequence: number = 0; private host: SessionClientHostProxy; + private expantionTable: ts.Map; - constructor(host: SessionClientHost) { + constructor(host: SessionClientHost, abbreviate: boolean) { this.sequence = 0; this.host = new SessionClientHostProxy(host); - this.session = new Session(this.host, this.host, /* useProtocol */ true, /*prettyJSON*/ true); + this.session = new Session(this.host, this.host, /* useProtocol */ true, /*prettyJSON*/ false); + + // Setup the abbreviation table + if (abbreviate) { + this.setupExpantionTable() + } + } + + private setupExpantionTable(): void { + var request = this.processRequest("abbrev"); + var response = this.processResponse(request); + var abbriviationTable = response.body; + + Debug.assert(!!abbriviationTable, "Could not setup abbreviation. Abbreviation table was empty."); + + var expantionTable: ts.Map = {}; + for (var p in abbriviationTable) { + if (abbriviationTable.hasOwnProperty(p)) { + expantionTable[abbriviationTable[p]] = p; + } + } + this.expantionTable = expantionTable; + } + + private expand(obj: T): T { + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + if (typeof (obj)[p] === "object") { + // Expand the property value + (obj)[p] = this.expand((obj)[p]); + } + + // Substitute the name if applicaple + var substitution = ts.lookUp(this.expantionTable, p); + if (substitution) { + (obj)[substitution] = (obj)[p]; + (obj)[p] = undefined; + } + } + } + + return obj; } private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { @@ -135,6 +177,23 @@ module ts.server { }; } + private getFileNameFromEncodedFile(fileId: ServerProtocol.EncodedFile, fileMapping: ts.Map): string { + var fileName: string; + if (typeof fileId === "object") { + fileName = (fileId).file; + fileMapping[(fileId).id] = fileName; + } + else if (typeof fileId === "number") { + fileName = ts.lookUp(fileMapping, fileId.toString()); + Debug.assert(!!fileName, "Did not find filename in previous fileID mappings."); + } + else { + Debug.fail("Got unexpedted fileId type."); + } + return fileName; + } + + private processRequest(command: "abbrev"): ServerProtocol.AbbrevRequest; private processRequest(command: "open", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.OpenRequest; private processRequest(command: "close", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.CloseRequest; private processRequest(command: "change", arguments: ServerProtocol.ChangeRequestArgs): ServerProtocol.ChangeRequest; @@ -146,7 +205,7 @@ module ts.server { private processRequest(command: "completions", arguments: ServerProtocol.CompletionsRequestArgs): ServerProtocol.CompletionsRequest; private processRequest(command: "navto", arguments: ServerProtocol.NavtoRequestArgs): ServerProtocol.NavtoRequest; private processRequest(command: "saveto", arguments: ServerProtocol.SavetoRequestArgs): ServerProtocol.SavetoRequest; - private processRequest(command: string, arguments: any): ServerProtocol.Request; + private processRequest(command: string, arguments?: any): ServerProtocol.Request; private processRequest(command: string, arguments: any): ServerProtocol.Request { var request: ServerProtocol.Request = { seq: this.sequence++, @@ -163,9 +222,13 @@ module ts.server { private processResponse(request: ServerProtocol.Request): T { debugger; + var lastMessage = this.host.lastReply; + this.host.lastReply = undefined; + Debug.assert(!!lastMessage, "Did not recieve any responses."); + // Read the content length var contentLengthPrefix = "Content-Length: "; - var lines = this.host.lastReply.split("\r\n"); + var lines = lastMessage.split("\r\n"); Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); var contentLengthText = lines[0]; @@ -182,7 +245,7 @@ module ts.server { var response: T = JSON.parse(responseBody); } catch (e) { - throw new Error("Malformed response: Failed to parse server response: " + this.host.lastReply + ". \r\n Error detailes: " + e.message); + throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error detailes: " + e.message); } // verify the sequence numbers @@ -195,9 +258,13 @@ module ts.server { Debug.assert(!!response.body, "Malformed response: Unexpected empty response body."); + if (this.expantionTable) { + // Expand the response if abbreviated + return this.expand(response); + } return response; } - + openFile(fileName: string): void { this.processRequest("open", { file: fileName @@ -265,11 +332,15 @@ module ts.server { getNavigateToItems(seatchTerm: string): NavigateToItem[] { var request = this.processRequest("navto", { seatchTerm }); - + var response = this.processResponse(request); + var fileMapping: ts.Map = {}; + return response.body.map(entry => { + var fileName = this.getFileNameFromEncodedFile(entry.file, fileMapping); var start = this.lineColToPosition(entry.file.toString(), entry.start); var end = this.lineColToPosition(entry.file.toString(), entry.end); + return { name: entry.name, containerName: entry.containerName, @@ -277,7 +348,7 @@ module ts.server { kind: entry.kind, kindModifiers: entry.kindModifiers, matchKind: entry.matchKind, - fileName: entry.file.toString(), + fileName: fileName, textSpan: ts.createTextSpanFromBounds(start, end) }; }); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index bd05cfbd533d6..7c27eec02dd5c 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1136,6 +1136,10 @@ module ts.server { this.navto(navtoArgs.searchTerm, navtoArgs.file, cmd, req.seq); break; } + case CommandNames.Abbrev: { + this.sendAbbrev(); + break; + } default: { this.projectService.log("Unrecognized JSON command: " + cmd); break; From c987ab93f0fb0c46daa183bc2eadb841683aedbb Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 12:52:26 -0800 Subject: [PATCH 13/45] Wire navto tests --- src/server/client.ts | 29 +++++++++++++++++---------- src/server/protocol.ts | 3 +++ tests/cases/fourslash/server/navto.ts | 28 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/server/navto.ts diff --git a/src/server/client.ts b/src/server/client.ts index 908562e589b2f..f637a9f12692c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -80,6 +80,10 @@ module ts.server { return this.host.getScriptSnapshot(fileName).getLength(); } + getFileNames(): string[] { + return this.host.getScriptFileNames(); + } + close(): void { } @@ -107,6 +111,7 @@ module ts.server { private sequence: number = 0; private host: SessionClientHostProxy; private expantionTable: ts.Map; + private fileMapping: ts.Map = {}; constructor(host: SessionClientHost, abbreviate: boolean) { this.sequence = 0; @@ -177,14 +182,14 @@ module ts.server { }; } - private getFileNameFromEncodedFile(fileId: ServerProtocol.EncodedFile, fileMapping: ts.Map): string { + private getFileNameFromEncodedFile(fileId: ServerProtocol.EncodedFile): string { var fileName: string; if (typeof fileId === "object") { fileName = (fileId).file; - fileMapping[(fileId).id] = fileName; + this.fileMapping[(fileId).id] = fileName; } else if (typeof fileId === "number") { - fileName = ts.lookUp(fileMapping, fileId.toString()); + fileName = ts.lookUp(this.fileMapping, fileId.toString()); Debug.assert(!!fileName, "Did not find filename in previous fileID mappings."); } else { @@ -330,21 +335,23 @@ module ts.server { }; } - getNavigateToItems(seatchTerm: string): NavigateToItem[] { - var request = this.processRequest("navto", { seatchTerm }); + getNavigateToItems(searchTerm: string): NavigateToItem[] { + var request = this.processRequest("navto", { + searchTerm, + file: this.host.getFileNames()[0] + }); var response = this.processResponse(request); - var fileMapping: ts.Map = {}; return response.body.map(entry => { - var fileName = this.getFileNameFromEncodedFile(entry.file, fileMapping); - var start = this.lineColToPosition(entry.file.toString(), entry.start); - var end = this.lineColToPosition(entry.file.toString(), entry.end); + var fileName = this.getFileNameFromEncodedFile(entry.file); + var start = this.lineColToPosition(fileName, entry.start); + var end = this.lineColToPosition(fileName, entry.end); return { name: entry.name, - containerName: entry.containerName, - containerKind: entry.containerKind, + containerName: entry.containerName || "", + containerKind: entry.containerKind || "", kind: entry.kind, kindModifiers: entry.kindModifiers, matchKind: entry.matchKind, diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 7c27eec02dd5c..b4ffddd25edbf 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1051,6 +1051,9 @@ module ts.server { this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); } } + else { + this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); + } } executeJSONcmd(cmd: string) { diff --git a/tests/cases/fourslash/server/navto.ts b/tests/cases/fourslash/server/navto.ts new file mode 100644 index 0000000000000..8dc42a2365e36 --- /dev/null +++ b/tests/cases/fourslash/server/navto.ts @@ -0,0 +1,28 @@ +/// + +/////// Module +////{| "itemName": "Shapes", "kind": "module", "parentName": "", "matchKind": "substring" |}module Shapes { +//// +//// // Class +//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes", "matchKind": "substring" |}export class Point { +//// // Instance member +//// {| "itemName": "originPointAttheHorizon", "kind": "property", "parentName": "Point", "matchKind": "substring"|}private originPointAttheHorizon = 0.0; +//// +//// // Getter +//// {| "itemName": "distanceFromOrigin", "kind": "getter", "parentName": "Point", "matchKind": "substring" |}get distanceFromOrigin(): number { return 0; } +//// +//// } +////} +//// +////// Local variables +////{| "itemName": "myPointThatIJustInitiated", "kind": "var", "parentName": "", "matchKind": "substring"|}var myPointThatIJustInitiated = new Shapes.Point(); + +//// Testing for substring matching of navigationItems +//var searchValue = "FromOrigin horizon INITIATED Shape Point"; + +test.markers().forEach((marker) => { + if (marker.data) { + var name = marker.data.itemName; + verify.navigationItemsListContains(name, marker.data.kind, name.substr(1), marker.data.matchKind, marker.fileName, marker.data.parentName); + } +}); \ No newline at end of file From 3e86e557d5d5f7db630bdae52de6adfa6dd2dcbd Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 13:10:55 -0800 Subject: [PATCH 14/45] Use commmandNames module --- src/server/client.ts | 81 +++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index f637a9f12692c..60fbd31243ecb 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -125,7 +125,7 @@ module ts.server { } private setupExpantionTable(): void { - var request = this.processRequest("abbrev"); + var request = this.processRequest(CommandNames.Abbrev); var response = this.processResponse(request); var abbriviationTable = response.body; @@ -198,20 +198,7 @@ module ts.server { return fileName; } - private processRequest(command: "abbrev"): ServerProtocol.AbbrevRequest; - private processRequest(command: "open", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.OpenRequest; - private processRequest(command: "close", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.CloseRequest; - private processRequest(command: "change", arguments: ServerProtocol.ChangeRequestArgs): ServerProtocol.ChangeRequest; - private processRequest(command: "quickinfo", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.QuickInfoRequest; - private processRequest(command: "format", arguments: ServerProtocol.FormatRequestArgs): ServerProtocol.FormatRequest; - private processRequest(command: "formatonkey", arguments: ServerProtocol.FormatOnKeyRequestArgs): ServerProtocol.FormatRequest; - private processRequest(command: "definition", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.DefinitionRequest; - private processRequest(command: "references", arguments: ServerProtocol.FileRequestArgs): ServerProtocol.ReferencesRequest; - private processRequest(command: "completions", arguments: ServerProtocol.CompletionsRequestArgs): ServerProtocol.CompletionsRequest; - private processRequest(command: "navto", arguments: ServerProtocol.NavtoRequestArgs): ServerProtocol.NavtoRequest; - private processRequest(command: "saveto", arguments: ServerProtocol.SavetoRequestArgs): ServerProtocol.SavetoRequest; - private processRequest(command: string, arguments?: any): ServerProtocol.Request; - private processRequest(command: string, arguments: any): ServerProtocol.Request { + private processRequest(command: string, arguments?: any): T { var request: ServerProtocol.Request = { seq: this.sequence++, type: "request", @@ -221,7 +208,7 @@ module ts.server { this.session.executeJSONcmd(JSON.stringify(request)); - return request; + return request; } private processResponse(request: ServerProtocol.Request): T { @@ -271,37 +258,37 @@ module ts.server { } openFile(fileName: string): void { - this.processRequest("open", { - file: fileName - }); + var args: ServerProtocol.FileRequestArgs = { file: fileName }; + this.processRequest(CommandNames.Open, args); } closeFile(fileName: string): void { - this.processRequest("close", { - file: fileName - }); + var args: ServerProtocol.FileRequestArgs = { file: fileName }; + this.processRequest(CommandNames.Close, args); } changeFile(fileName: string, start: number, end: number, newText: string): void { var lineCol = this.positionToOneBasedLineCol(fileName, start); - - this.processRequest("change", { + var args: ServerProtocol.ChangeRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, insertLen: end - start, deleteLen: end - start, insertString: newText - }); + }; + + this.processRequest(CommandNames.Change, args); } getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { - var request = this.processRequest("quickinfo", { + var args: ServerProtocol.CodeLocationRequestArgs = { file: fileName, line: 0, col: 1 - }); + }; + var request = this.processRequest(CommandNames.Quickinfo, args); var response = this.processResponse(request); var start = this.lineColToPosition(fileName, response.body.start); @@ -320,12 +307,13 @@ module ts.server { getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var request = this.processRequest("completions", { + var args: ServerProtocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, - }); + }; + var request = this.processRequest(CommandNames.Completions, args); var response = this.processResponse(request); return { @@ -336,11 +324,12 @@ module ts.server { } getNavigateToItems(searchTerm: string): NavigateToItem[] { - var request = this.processRequest("navto", { - searchTerm, + var args: ServerProtocol.NavtoRequestArgs = { + searchTerm, file: this.host.getFileNames()[0] - }); - + }; + + var request = this.processRequest(CommandNames.Navto, args); var response = this.processResponse(request); return response.body.map(entry => { @@ -364,15 +353,16 @@ module ts.server { getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { var startLineCol = this.positionToOneBasedLineCol(fileName, start); var endLineCol = this.positionToOneBasedLineCol(fileName, end); - // TODO: handle FormatCodeOptions - var request = this.processRequest("format", { + var args: ServerProtocol.FormatRequestArgs = { file: fileName, line: startLineCol.line, col: startLineCol.col, endLine: endLineCol.line, endCol: endLineCol.col, - }); + }; + // TODO: handle FormatCodeOptions + var request = this.processRequest(CommandNames.Format, args); var response = this.processResponse(request); return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); @@ -384,27 +374,31 @@ module ts.server { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - // TODO: handle FormatCodeOptions - var request = this.processRequest("formatonkey", { + var args: ServerProtocol.FormatOnKeyRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, key: key - }); + }; + // TODO: handle FormatCodeOptions + var request = this.processRequest(CommandNames.Formatonkey, args); var response = this.processResponse(request); + return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); } getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var request = this.processRequest("definition", { + var args: ServerProtocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, - }); + }; + var request = this.processRequest(CommandNames.Definition, args); var response = this.processResponse(request); + return response.body.map(entry => { var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); @@ -421,12 +415,13 @@ module ts.server { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var request = this.processRequest("references", { + var args: ServerProtocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, - }); + }; + var request = this.processRequest(CommandNames.References, args); var response = this.processResponse(request); return response.body.refs.map(entry => { From c0b12540726bb5955c80a989fb3d47d82f65723f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 13:35:11 -0800 Subject: [PATCH 15/45] Support brace matching --- src/server/client.ts | 19 +++++++++++- src/server/protocol.ts | 40 ++++++++++++++++++++++++- src/server/protodef.d.ts | 28 +++++++++++++++-- tests/cases/fourslash/server/brace.ts | 43 +++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/server/brace.ts diff --git a/src/server/client.ts b/src/server/client.ts index 60fbd31243ecb..07b5d81d8c03b 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -488,7 +488,24 @@ module ts.server { } getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { - throw new Error("Not Implemented Yet."); + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var args: ServerProtocol.CodeLocationRequestArgs = { + file: fileName, + line: lineCol.line, + col: lineCol.col, + }; + + var request = this.processRequest(CommandNames.Brace, args); + var response = this.processResponse(request); + + return response.body.map(entry => { + var start = this.lineColToPosition(fileName, entry.start); + var end = this.lineColToPosition(fileName, entry.end); + return { + start: start, + length: end - start, + }; + }); } getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b4ffddd25edbf..6187d8e6140ec 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -333,7 +333,7 @@ module ts.server { return true; } - module CommandNames { + export module CommandNames { export var Abbrev = "abbrev"; export var Change = "change"; export var Close = "close"; @@ -350,6 +350,7 @@ module ts.server { export var Rename = "rename"; export var Saveto = "saveto"; export var Type = "type"; + export var Brace = "brace"; export var Unknown = "unknown"; } @@ -1056,6 +1057,38 @@ module ts.server { } } + getMatchingBrace(line: number, col: number, rawfile: string, reqSeq = 0) { + var file = ts.normalizePath(rawfile); + var project = this.projectService.getProjectForFile(file); + if (project) { + var compilerService = project.compilerService; + var pos = compilerService.host.lineColToPosition(file, line, col); + var spans: ts.TextSpan[]; + try { + spans = compilerService.languageService.getBraceMatchingAtPosition(file, pos); + } + catch (err) { + this.logError(err, CommandNames.Brace); + spans = undefined; + } + if (spans) { + var bakedSpans: ServerProtocol.TextSpan[] = spans.map(span => ({ + start: span && + compilerService.host.positionToLineCol(file, span.start), + end: span && + compilerService.host.positionToLineCol(file, span.start + span.length) + })); + this.output(bakedSpans, CommandNames.Brace, reqSeq); + } + else { + this.output(undefined, CommandNames.Brace, reqSeq, "no matching braces"); + } + } + else { + this.output(undefined, CommandNames.Brace, reqSeq, "no matching braces"); + } + } + executeJSONcmd(cmd: string) { try { var req = JSON.parse(cmd); @@ -1143,6 +1176,11 @@ module ts.server { this.sendAbbrev(); break; } + case CommandNames.Brace: { + var defArgs = req.arguments; + this.getMatchingBrace(defArgs.line, defArgs.col, defArgs.file, req.seq); + break; + } default: { this.projectService.log("Unrecognized JSON command: " + cmd); break; diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index 75a41cb81ce70..73c394041545c 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -89,15 +89,22 @@ declare module ServerProtocol { Object found in response messages defining a span of text in source code. */ - export interface CodeSpan { - /** File containing the definition */ - file: string; + export interface TextSpan { /** First character of the definition */ start: LineCol; /** One character past last character of the definition */ end: LineCol; } + /** + Object found in response messages defining a span of text in + a specific source file. + */ + export interface CodeSpan extends TextSpan { + /** File containing the definition */ + file: string; + } + /** Definition response message. Gives text range for definition. */ @@ -582,6 +589,21 @@ declare module ServerProtocol { [fullString: string]: string; } } + + + /** Response to "brace" request. */ + export interface BraceResponse extends Response { + body?: TextSpan[]; + } + + /** + Brace matching request; value of command field is "brace". + Return response giving the code locations of matching braces + found in file at location line, col. + */ + export interface BraceRequest extends CodeLocationRequest { + } + } diff --git a/tests/cases/fourslash/server/brace.ts b/tests/cases/fourslash/server/brace.ts new file mode 100644 index 0000000000000..fc8a71197db11 --- /dev/null +++ b/tests/cases/fourslash/server/brace.ts @@ -0,0 +1,43 @@ +/// + +//////curly braces +////module Foo [|{ +//// class Bar [|{ +//// private f() [|{ +//// }|] +//// +//// private f2() [|{ +//// if (true) [|{ }|] [|{ }|]; +//// }|] +//// }|] +////}|] +//// +//////parenthesis +////class FooBar { +//// private f[|()|] { +//// return [|([|(1 + 1)|])|]; +//// } +//// +//// private f2[|()|] { +//// if [|(true)|] { } +//// } +////} +//// +//////square brackets +////class Baz { +//// private f() { +//// var a: any[|[]|] = [|[[|[1, 2]|], [|[3, 4]|], 5]|]; +//// } +////} +//// +////// angular brackets +////class TemplateTest [||] { +//// public foo(a, b) { +//// return [||] a; +//// } +////} + +test.ranges().forEach((range) => { + verify.matchingBracePositionInCurrentFile(range.start, range.end - 1); + verify.matchingBracePositionInCurrentFile(range.end - 1, range.start); +}); \ No newline at end of file From 93aa3f161dd7ff23240124d2b8d2f917cf5d6783 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 13:35:24 -0800 Subject: [PATCH 16/45] Add test for format on key --- tests/cases/fourslash/server/formatonkey.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/cases/fourslash/server/formatonkey.ts diff --git a/tests/cases/fourslash/server/formatonkey.ts b/tests/cases/fourslash/server/formatonkey.ts new file mode 100644 index 0000000000000..64cd74ca8580b --- /dev/null +++ b/tests/cases/fourslash/server/formatonkey.ts @@ -0,0 +1,12 @@ +/// + +////switch (1) { +//// case 1: +//// { +//// /*1*/ +//// break; +////} + +goTo.marker("1"); +edit.insert("}"); +verify.currentLineContentIs(" }"); From 99373dbd89acf57f3c3a74f31421e82f092673c4 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 12 Feb 2015 16:31:08 -0800 Subject: [PATCH 17/45] Add test for goto def --- src/harness/fourslash.ts | 3 +++ src/harness/harnessLanguageService.ts | 7 +++++-- src/server/client.ts | 2 +- tests/cases/fourslash/definition.ts | 12 ++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/definition.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 9c61c3b1e274d..bc7a8718a76a4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -420,6 +420,9 @@ module FourSlash { this.activeFile = fileToOpen; var fileName = fileToOpen.fileName.replace(Harness.IO.directoryName(fileToOpen.fileName), '').substr(1); this.scenarioActions.push(''); + + // Let the host know that this file is now open + this.languageServiceAdapterHost.openFile(fileToOpen.fileName); } public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 2c0d2549c8e70..39f59948cbc82 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -154,6 +154,9 @@ module Harness.LanguageService { throw new Error("No script with name '" + fileName + "'"); } + public openFile(fileName: string): void { + } + /** * @param line 1 based index * @param col 1 based index @@ -442,8 +445,8 @@ module Harness.LanguageService { this.client = client; } - addScript(fileName: string, content: string): void { - super.addScript(fileName, content); + openFile(fileName: string): void { + super.openFile(fileName); this.client.openFile(fileName); } diff --git a/src/server/client.ts b/src/server/client.ts index 07b5d81d8c03b..a93177adb3e12 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -65,7 +65,7 @@ module ts.server { } stat(path: string, callback?: (err: any, stats: any) => any) { - throw new Error("Not implemented Yet."); + return 0; } lineColToPosition(fileName: string, line: number, col: number): number { diff --git a/tests/cases/fourslash/definition.ts b/tests/cases/fourslash/definition.ts new file mode 100644 index 0000000000000..531e45628433d --- /dev/null +++ b/tests/cases/fourslash/definition.ts @@ -0,0 +1,12 @@ +/// + +// @Filename: b.ts +////import n = require('a/*1*/'); +////var x = new n.Foo(); + +// @Filename: a.ts +//// /*2*/export class Foo {} + +goTo.marker('1'); +goTo.definition(); +verify.caretAtMarker('2'); \ No newline at end of file From a0b557e1e2d3c56f8c2ece643b7d77b2410a699e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 15:12:06 -0800 Subject: [PATCH 18/45] Recover from git corruption --- src/harness/harnessLanguageService.ts | 149 ++- src/server/client.ts | 278 ++---- src/server/editorServices.ts | 3 +- src/server/protocol.ts | 1194 +++++++------------------ src/server/protodef.d.ts | 56 +- src/server/server.ts | 76 +- 6 files changed, 595 insertions(+), 1161 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 39f59948cbc82..2e50ca59b97ad 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -128,7 +128,9 @@ module Harness.LanguageService { protected settings = ts.getDefaultCompilerOptions()) { } - public getNewLine(): string { return "\r\n"; } + public getNewLine(): string { + return "\r\n"; + } public getFilenames(): string[] { var fileNames: string[] = []; @@ -435,17 +437,26 @@ module Harness.LanguageService { } // Server adapter - class ServerLanguageServiceHost extends NativeLanguageServiceHost { + class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { private client: ts.server.SessionClient; + constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) { super(cancellationToken, settings); } - setClient(client: ts.server.SessionClient) { + onMessage(message: string): void { + + } + + writeMessage(message: string): void { + + } + + setClient(client: ts.server.SessionClient) { this.client = client; } - openFile(fileName: string): void { + openFile(fileName: string): void { super.openFile(fileName); this.client.openFile(fileName); } @@ -456,15 +467,135 @@ module Harness.LanguageService { } } + class SessionServerHost implements ts.server.ServerHost, ts.server.Logger { + args: string[] = []; + newLine: string; + useCaseSensitiveFileNames: boolean = false; + + constructor(private host: NativeLanguageServiceHost) { + this.newLine = this.host.getNewLine(); + } + + onMessage(message: string): void { + + } + + writeMessage(message: string): void { + } + + write(message: string): void { + this.writeMessage(message); + } + + readFile(fileName: string): string { + var snapshot = this.host.getScriptSnapshot(fileName); + return snapshot && snapshot.getText(0, snapshot.getLength()); + } + + writeFile(name: string, text: string, writeByteOrderMark: boolean): void { + } + + resolvePath(path: string): string { + return path; + } + + fileExists(path: string): boolean { + return !!this.host.getScriptSnapshot(path); + } + + directoryExists(path: string): boolean { + return false; + } + + getExecutingFilePath(): string { + return ""; + } + + exit(exitCode: number): void { + } + + createDirectory(directoryName: string): void { + throw new Error("Not Implemented Yet."); + } + + getCurrentDirectory(): string { + return this.host.getCurrentDirectory(); + } + + readDirectory(path: string, extension?: string): string[] { + throw new Error("Not implemented Yet."); + } + + getModififedTime(fileName: string): Date { + return new Date(); + } + + stat(path: string, callback?: (err: any, stats: any) => any) { + return 0; + } + + lineColToPosition(fileName: string, line: number, col: number): number { + return this.host.lineColToPosition(fileName, line, col); + } + + positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { + return this.host.positionToZeroBasedLineCol(fileName, position); + } + + getFileLength(fileName: string): number { + return this.host.getScriptSnapshot(fileName).getLength(); + } + + getFileNames(): string[] { + return this.host.getScriptFileNames(); + } + + close(): void { + } + + info(message: string): void { + return this.host.log(message); + } + + msg(message: string) { + return this.host.log(message); + } + + endGroup(): void { + } + + perftrc(message: string): void { + return this.host.log(message); + } + + startGroup(): void { + } + } + export class ServerLanugageServiceAdapter implements LanguageServiceAdapter { - private host: ServerLanguageServiceHost; + private host: SessionClientHost; private client: ts.server.SessionClient; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { - debugger; + // This is the main host that tests use to direct tests + var clientHost = new SessionClientHost(cancellationToken, options); + var client = new ts.server.SessionClient(clientHost); + + // This host is just a proxy for the clientHost, it uses the client + // host to answer server queries about files on disk + var serverHost = new SessionServerHost(clientHost); + var server = new ts.server.Session(serverHost, serverHost, /*useProtocol*/ true, /*prettyJSON*/ false); + + // Fake the connection between the client and the server + serverHost.writeMessage = client.onMessage.bind(client); + clientHost.writeMessage = server.onMessage.bind(server); - this.host = new ServerLanguageServiceHost(cancellationToken, options); - this.client = new ts.server.SessionClient(this.host, /*abbreviate*/ true); - this.host.setClient(this.client); + // Wire the client to the host to get notifications when a file is open + // or edited. + clientHost.setClient(client); + + // Set the properties + this.client = client; + this.host = clientHost; } getHost() { return this.host; } getLanguageService(): ts.LanguageService { return this.client; } diff --git a/src/server/client.ts b/src/server/client.ts index a93177adb3e12..1aa7547f5411f 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -1,177 +1,49 @@ -/// - -module ts.server { - - export interface SessionClientHost extends LanguageServiceHost { - lineColToPosition(fileName: string, line: number, col: number): number; - positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter; - } - - class SessionClientHostProxy implements ServerHost, Logger { - args: string[] = []; - newLine: string; - useCaseSensitiveFileNames: boolean = false; - lastReply: string; - - constructor(private host: SessionClientHost) { - this.newLine = this.host.getNewLine(); - } +/// - write(message: string): void { - this.lastReply = message; - } - - readFile(fileName: string): string { - var snapshot = this.host.getScriptSnapshot(fileName); - return snapshot && snapshot.getText(0, snapshot.getLength()); - } - - writeFile(name: string, text:string, writeByteOrderMark: boolean): void { - } +module ts.server { - resolvePath(path: string): string { - return path; - } + export interface SessionClientHost extends LanguageServiceHost { + writeMessage(message: string): void; + } - fileExists(path: string): boolean { - return !!this.host.getScriptSnapshot(path); + export class SessionClient implements LanguageService { + private sequence: number = 0; + private fileMapping: ts.Map = {}; + private lineMaps: ts.Map = {}; + private messages: string[] = []; + + constructor(private host: SessionClientHost) { } - directoryExists(path: string): boolean { - return false; + public onMessage(message: string): void { + this.messages.push(message); } - getExecutingFilePath(): string { - return ""; + private writeMessage(message: string): void { + this.host.writeMessage(message); } - exit(exitCode: number): void { - } - - createDirectory(directoryName: string): void { - throw new Error("Not Implemented Yet."); + private getLineMap(fileName: string): number[] { + var lineMap = ts.lookUp(this.lineMaps, fileName); + if (!lineMap) { + var scriptSnapshot = this.host.getScriptSnapshot(fileName); + lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); + } + return lineMap; } - getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); - } - - readDirectory(path: string, extension?: string): string[] { - throw new Error("Not implemented Yet."); - } - - getModififedTime(fileName: string): Date { - return new Date(); - } - - stat(path: string, callback?: (err: any, stats: any) => any) { - return 0; - } - - lineColToPosition(fileName: string, line: number, col: number): number { - return this.host.lineColToPosition(fileName, line, col); - } - - positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { - return this.host.positionToZeroBasedLineCol(fileName, position); - } - - getFileLength(fileName: string): number { - return this.host.getScriptSnapshot(fileName).getLength(); - } - - getFileNames(): string[] { - return this.host.getScriptFileNames(); - } - - close(): void { - } - - info(message: string): void { - return this.host.log(message); - } - - msg(message: string) { - return this.host.log(message); - } - - endGroup(): void { - } - - perftrc(message: string): void { - return this.host.log(message); - } - - startGroup(): void { - } - } - - export class SessionClient implements LanguageService { - private session: Session; - private sequence: number = 0; - private host: SessionClientHostProxy; - private expantionTable: ts.Map; - private fileMapping: ts.Map = {}; - - constructor(host: SessionClientHost, abbreviate: boolean) { - this.sequence = 0; - this.host = new SessionClientHostProxy(host); - this.session = new Session(this.host, this.host, /* useProtocol */ true, /*prettyJSON*/ false); - - // Setup the abbreviation table - if (abbreviate) { - this.setupExpantionTable() - } - } - - private setupExpantionTable(): void { - var request = this.processRequest(CommandNames.Abbrev); - var response = this.processResponse(request); - var abbriviationTable = response.body; - - Debug.assert(!!abbriviationTable, "Could not setup abbreviation. Abbreviation table was empty."); - - var expantionTable: ts.Map = {}; - for (var p in abbriviationTable) { - if (abbriviationTable.hasOwnProperty(p)) { - expantionTable[abbriviationTable[p]] = p; - } - } - this.expantionTable = expantionTable; - } - - private expand(obj: T): T { - for (var p in obj) { - if (obj.hasOwnProperty(p)) { - if (typeof (obj)[p] === "object") { - // Expand the property value - (obj)[p] = this.expand((obj)[p]); - } - - // Substitute the name if applicaple - var substitution = ts.lookUp(this.expantionTable, p); - if (substitution) { - (obj)[substitution] = (obj)[p]; - (obj)[p] = undefined; - } - } - } - - return obj; - } - private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { - return this.host.lineColToPosition(fileName, lineCol.line, lineCol.col); + return ts.computePositionFromLineAndCharacter(this.getLineMap(fileName), lineCol.line, lineCol.col); } - private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { - var lineCol = this.host.positionToZeroBasedLineCol(fileName, position); + private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { + var lineCol = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); return { - line: lineCol.line + 1, - col: lineCol.character + 1 + line: lineCol.line, + col: lineCol.character }; - } - + } + private convertCodeEditsToTextChange(fileName: string, codeEdit: ServerProtocol.CodeEdit): ts.TextChange { var start = this.lineColToPosition(fileName, codeEdit.start); var end = this.lineColToPosition(fileName, codeEdit.end); @@ -180,8 +52,8 @@ module ts.server { span: ts.createTextSpanFromBounds(start, end), newText: codeEdit.newText }; - } - + } + private getFileNameFromEncodedFile(fileId: ServerProtocol.EncodedFile): string { var fileName: string; if (typeof fileId === "object") { @@ -196,9 +68,9 @@ module ts.server { Debug.fail("Got unexpedted fileId type."); } return fileName; - } - - private processRequest(command: string, arguments?: any): T { + } + + private processRequest(command: string, arguments?: any): T { var request: ServerProtocol.Request = { seq: this.sequence++, type: "request", @@ -206,34 +78,33 @@ module ts.server { arguments: arguments }; - this.session.executeJSONcmd(JSON.stringify(request)); - - return request; - } - - private processResponse(request: ServerProtocol.Request): T { - debugger; - - var lastMessage = this.host.lastReply; - this.host.lastReply = undefined; - Debug.assert(!!lastMessage, "Did not recieve any responses."); - - // Read the content length - var contentLengthPrefix = "Content-Length: "; - var lines = lastMessage.split("\r\n"); - Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); - - var contentLengthText = lines[0]; - Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); - var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); - - // Read the body - var responseBody = lines[2]; - - // Verify content length - Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); - - try { + this.writeMessage(JSON.stringify(request)); + + return request; + } + + private processResponse(request: ServerProtocol.Request): T { + debugger; + + var lastMessage = this.messages.shift(); + Debug.assert(!!lastMessage, "Did not recieve any responses."); + + // Read the content length + var contentLengthPrefix = "Content-Length: "; + var lines = lastMessage.split("\r\n"); + Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); + + var contentLengthText = lines[0]; + Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); + var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); + + // Read the body + var responseBody = lines[2]; + + // Verify content length + Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); + + try { var response: T = JSON.parse(responseBody); } catch (e) { @@ -250,13 +121,9 @@ module ts.server { Debug.assert(!!response.body, "Malformed response: Unexpected empty response body."); - if (this.expantionTable) { - // Expand the response if abbreviated - return this.expand(response); - } return response; - } - + } + openFile(fileName: string): void { var args: ServerProtocol.FileRequestArgs = { file: fileName }; this.processRequest(CommandNames.Open, args); @@ -268,6 +135,9 @@ module ts.server { } changeFile(fileName: string, start: number, end: number, newText: string): void { + // clear the line map after an edit + this.lineMaps[fileName] = undefined; + var lineCol = this.positionToOneBasedLineCol(fileName, start); var args: ServerProtocol.ChangeRequestArgs = { file: fileName, @@ -326,7 +196,7 @@ module ts.server { getNavigateToItems(searchTerm: string): NavigateToItem[] { var args: ServerProtocol.NavtoRequestArgs = { searchTerm, - file: this.host.getFileNames()[0] + file: this.host.getScriptFileNames()[0] }; var request = this.processRequest(CommandNames.Navto, args); @@ -357,7 +227,7 @@ module ts.server { file: fileName, line: startLineCol.line, col: startLineCol.col, - endLine: endLineCol.line, + endLine: endLineCol.line, endCol: endLineCol.col, }; @@ -369,7 +239,7 @@ module ts.server { } getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return this.getFormattingEditsForRange(fileName, 0, this.host.getFileLength(fileName), options); + return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName).getLength(), options); } getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { @@ -537,7 +407,7 @@ module ts.server { } dispose(): void { - throw new Error("dispose is not available through the server layer."); - } - } -} + throw new Error("dispose is not available through the server layer."); + } + } +} diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 933435f93a1e9..7007704a168b9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -425,7 +425,8 @@ module ts.server { if (projectOptions.compilerOptions) { this.compilerService.setCompilerOptions(projectOptions.compilerOptions); } - // TODO: format code options } + // TODO: format code options + } } export interface ProjectOpenResult { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6187d8e6140ec..16abbf029d78b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -33,39 +33,6 @@ module ts.server { return spaceCache[n]; } - function isTypeName(name: string, suffix?: string) { - for (var i = 0, len = typeNames.length; i < len; i++) { - if (typeNames[i] == name) { - return true; - } - else if (suffix && ((typeNames[i] + suffix) == name)) { - return true; - } - } - return false; - } - - function parseTypeName(displayParts: ts.SymbolDisplayPart[]) { - var len = displayParts.length; - for (var i = len - 1; i >= 0; i--) { - if (isTypeName(displayParts[i].kind, "Name")) { - return displayParts[i].text; - } - } - return undefined; - } - - function findExactMatchType(items: ts.NavigateToItem[]) { - for (var i = 0, len = items.length; i < len; i++) { - var navItem = items[i]; - if (navItem.matchKind == "exact") { - if (isTypeName(navItem.kind)) { - return navItem; - } - } - } - } - interface FileStart { file: string; start: ILineInfo; @@ -81,14 +48,6 @@ module ts.server { else return 1; } - function printObject(obj: any) { - for (var p in obj) { - if (obj.hasOwnProperty(p)) { - console.log(p + ": " + obj[p]); - } - } - } - function compareFileStart(a: FileStart, b: FileStart) { if (a.file < b.file) { return -1; @@ -128,178 +87,7 @@ module ts.server { } }) } - - //function SourceInfo(body: NodeJS._debugger.BreakResponse) { - // var result = body.exception ? 'exception in ' : 'break in '; - - // if (body.script) { - // if (body.script.name) { - // var name = body.script.name, - // dir = path.resolve() + '/'; - - // // Change path to relative, if possible - // if (name.indexOf(dir) === 0) { - // name = name.slice(dir.length); - // } - - // result += name; - // } else { - // result += '[unnamed]'; - // } - // } - // result += ':'; - // result += body.sourceLine + 1; - - // if (body.exception) result += '\n' + body.exception.text; - - // return result; - //} - - class JsDebugSession { - host = 'localhost'; - port = 5858; - - constructor(public client: NodeJS._debugger.Client) { - this.init(); - } - - cont(cb: NodeJS._debugger.RequestHandler) { - this.client.reqContinue(cb); - } - - listSrc() { - this.client.reqScripts((err: any) => { - if (err) { - console.log("rscr error: " + err); - } - else { - console.log("req scripts"); - for (var id in this.client.scripts) { - var script = this.client.scripts[id]; - if ((typeof script === "object") && script.name) { - console.log(id + ": " + script.name); - } - } - } - }); - } - - findScript(file: string) { - if (file) { - var script: NodeJS._debugger.ScriptDesc; - var scripts = this.client.scripts; - var keys: any[] = Object.keys(scripts); - var ambiguous = false; - for (var v = 0; v < keys.length; v++) { - var id = keys[v]; - if (scripts[id] && - scripts[id].name && - scripts[id].name.indexOf(file) !== -1) { - if (script) { - ambiguous = true; - } - script = scripts[id]; - } - } - return { script: script, ambiguous: ambiguous }; - } - } - - // TODO: condition - setBreakpointOnLine(line: number, file?: string) { - if (!file) { - file = this.client.currentScript; - } - var script: NodeJS._debugger.ScriptDesc; - var scriptResult = this.findScript(file); - if (scriptResult) { - if (scriptResult.ambiguous) { - // TODO: send back error - script = undefined; - } - else { - script = scriptResult.script; - } - } - // TODO: set breakpoint when script not loaded - if (script) { - var brkmsg: NodeJS._debugger.BreakpointMessageBody = { - type: 'scriptId', - target: script.id, - line: line - 1, - } - this.client.setBreakpoint(brkmsg,(err, bod) => { - // TODO: remember breakpoint - if (err) { - console.log("Error: set breakpoint: " + err); - } - }); - } - - } - - init() { - var connectionAttempts = 0; - this.client.on('break',(res: NodeJS._debugger.Event) => { - this.handleBreak(res.body); - }); - this.client.on('exception',(res: NodeJS._debugger.Event) => { - this.handleBreak(res.body); - }); - this.client.on('error',() => { - setTimeout(() => { - ++connectionAttempts; - this.client.connect(this.port, this.host); - }, 500); - }); - this.client.once('ready',() => { - }); - this.client.on('unhandledResponse',() => { - }); - this.client.connect(this.port, this.host); - } - - evaluate(code: string) { - var frame = this.client.currentFrame; - this.client.reqFrameEval(code, frame,(err, bod) => { - if (err) { - console.log("Error: evaluate: " + err); - return; - } - - console.log("Value: " + bod.toString()); - if (typeof bod === "object") { - printObject(bod); - } - - // Request object by handles (and it's sub-properties) - this.client.mirrorObject(bod, 3,(err, mirror) => { - if (mirror) { - if (typeof mirror === "object") { - printObject(mirror); - } - console.log(mirror.toString()); - } - else { - console.log("undefined"); - } - }); - - }); - } - - handleBreak(breakInfo: NodeJS._debugger.BreakResponse) { - this.client.currentSourceLine = breakInfo.sourceLine; - this.client.currentSourceLineText = breakInfo.sourceLineText; - this.client.currentSourceColumn = breakInfo.sourceColumn; - this.client.currentFrame = 0; - this.client.currentScript = breakInfo.script && breakInfo.script.name; - - //console.log(SourceInfo(breakInfo)); - // TODO: watchers - } - } - + interface FileRange { file?: string; start: ILineInfo; @@ -320,7 +108,7 @@ module ts.server { } interface PendingErrorCheck { - filename: string; + fileName: string; project: Project; } @@ -334,7 +122,6 @@ module ts.server { } export module CommandNames { - export var Abbrev = "abbrev"; export var Change = "change"; export var Close = "close"; export var Completions = "completions"; @@ -349,22 +136,22 @@ module ts.server { export var Reload = "reload"; export var Rename = "rename"; export var Saveto = "saveto"; - export var Type = "type"; export var Brace = "brace"; export var Unknown = "unknown"; } + module Errors { + export var NoProject = new Error("No Project."); + export var NoContent = new Error("No Content."); + } + export interface ServerHost extends ts.System { - getDebuggerClient? (): NodeJS._debugger.Client; } export class Session { projectService: ProjectService; - debugSession: JsDebugSession; pendingOperation = false; fileHash: ts.Map = {}; - abbrevTable: ts.Map; - fetchedAbbrev = false; nextFileId = 1; errorTimer: NodeJS.Timer; immediateId: any; @@ -372,7 +159,6 @@ module ts.server { constructor(private host: ServerHost, private logger: Logger, protected useProtocol: boolean, protected prettyJSON: boolean) { this.projectService = new ProjectService(host, logger); - this.initAbbrevTable(); } logError(err: Error, cmd: string) { @@ -387,8 +173,8 @@ module ts.server { this.projectService.log(msg); } - sendLineToClient(line: string) { - this.host.write(line + this.host.newLine); + sendLineToClient(line: string) { + this.host.write(line + this.host.newLine); } send(msg: NodeJS._debugger.Message) { @@ -430,51 +216,15 @@ module ts.server { this.send(res); } - initAbbrevTable() { - this.abbrevTable = { - name: "n", - kind: "k", - fileName: "f", - containerName: "cn", - containerKind: "ck", - kindModifiers: "km", - start: "s", - end: "e", - line: "l", - col: "c", - "interface": "i", - "function": "fn", - }; - } - - encodeFilename(filename: string): ServerProtocol.EncodedFile { - if (!this.fetchedAbbrev) { - return filename; + encodeFilename(fileName: string): ServerProtocol.EncodedFile { + var id = ts.lookUp(this.fileHash, fileName); + if (!id) { + id = this.nextFileId++; + this.fileHash[fileName] = id; + return { id: id, file: fileName }; } else { - var id = ts.lookUp(this.fileHash, filename); - if (!id) { - id = this.nextFileId++; - this.fileHash[filename] = id; - return { id: id, file: filename }; - } - else { - return id; - } - } - } - - abbreviate(obj: any) { - if (this.fetchedAbbrev && (!this.prettyJSON)) { - for (var p in obj) { - if (obj.hasOwnProperty(p)) { - var sub = ts.lookUp(this.abbrevTable, p); - if (sub) { - obj[sub] = obj[p]; - obj[p] = undefined; - } - } - } + return id; } } @@ -544,10 +294,10 @@ module ts.server { var checkOne = () => { if (matchSeq(seq)) { var checkSpec = checkList[index++]; - if (checkSpec.project.getSourceFileFromName(checkSpec.filename)) { - this.syntacticCheck(checkSpec.filename, checkSpec.project); + if (checkSpec.project.getSourceFileFromName(checkSpec.fileName)) { + this.syntacticCheck(checkSpec.fileName, checkSpec.project); this.immediateId = setImmediate(() => { - this.semanticCheck(checkSpec.filename, checkSpec.project); + this.semanticCheck(checkSpec.fileName, checkSpec.project); this.immediateId = undefined; if (checkList.length > index) { this.errorTimer = setTimeout(checkOne, followMs); @@ -564,181 +314,140 @@ module ts.server { } } - goToDefinition(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + goToDefinition(line: number, col: number, fileName: string): ServerProtocol.CodeSpan[] { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var locs = compilerService.languageService.getDefinitionAtPosition(file, pos); - if (locs) { - var info: ServerProtocol.CodeSpan[] = locs.map(def => ({ - file: def && def.fileName, - start: def && - compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), - end: def && - compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) - })); - this.output(info, CommandNames.Definition, reqSeq); - } - else { - this.output(undefined, CommandNames.Definition, reqSeq, "could not find def"); - } + if (!project) { + throw Errors.NoProject; } - else { - this.output(undefined, CommandNames.Definition, reqSeq, "no project for " + file); + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + + var definitions = compilerService.languageService.getDefinitionAtPosition(file, position); + if (!definitions) { + throw Errors.NoContent; } + + return definitions.map(def => ({ + file: def && def.fileName, + start: def && + compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), + end: def && + compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) + })); } - rename(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + rename(line: number, col: number, fileName: string): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var renameInfo = compilerService.languageService.getRenameInfo(file, pos); - if (renameInfo) { - if (renameInfo.canRename) { - var renameLocs = compilerService.languageService.findRenameLocations(file, pos, false, false); - if (renameLocs) { - var bakedRenameLocs = renameLocs.map(loc=> ({ - file: loc.fileName, - start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), - end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), - })).sort((a, b) => { - if (a.file < b.file) { - return -1; - } - else if (a.file > b.file) { - return 1; - } - else { - // reverse sort assuming no overlap - if (a.start.line < b.start.line) { - return 1; - } - else if (a.start.line > b.start.line) { - return -1; - } - else { - return b.start.col - a.start.col; - } - } - }).reduce((accum: FileRanges[], cur: FileRange) => { - var curFileAccum: FileRanges; - if (accum.length > 0) { - curFileAccum = accum[accum.length - 1]; - if (curFileAccum.file != cur.file) { - curFileAccum = undefined; - } - } - if (!curFileAccum) { - curFileAccum = { file: cur.file, locs: [] }; - accum.push(curFileAccum); - } - curFileAccum.locs.push({ start: cur.start, end: cur.end }); - return accum; - }, []); - this.output({ info: renameInfo, locs: bakedRenameLocs }, CommandNames.Rename, reqSeq); + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + var renameInfo = compilerService.languageService.getRenameInfo(file, position); + if (!renameInfo) { + throw Errors.NoContent; + } + + if (!renameInfo.canRename) { + return { + info: renameInfo, + locs: [] + }; + } + + var renameLocs = compilerService.languageService.findRenameLocations(file, position, false, false); + if (renameLocs) { + var bakedRenameLocs = renameLocs.map(loc=> ({ + file: loc.fileName, + start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), + end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), + })).sort((a, b) => { + if (a.file < b.file) { + return -1; + } + else if (a.file > b.file) { + return 1; + } + else { + // reverse sort assuming no overlap + if (a.start.line < b.start.line) { + return 1; + } + else if (a.start.line > b.start.line) { + return -1; } else { - this.output({ info: renameInfo, locs: [] }, CommandNames.Rename, reqSeq); + return b.start.col - a.start.col; } } - else { - this.output(undefined, CommandNames.Rename, reqSeq, renameInfo.localizedErrorMessage); + }).reduce((accum, cur) => { + var curFileAccum: FileRanges; + if (accum.length > 0) { + curFileAccum = accum[accum.length - 1]; + if (curFileAccum.file != cur.file) { + curFileAccum = undefined; + } } - } - else { - this.output(undefined, CommandNames.Rename, reqSeq, "no rename information at cursor"); - } + if (!curFileAccum) { + curFileAccum = { file: cur.file, locs: [] }; + accum.push(curFileAccum); + } + curFileAccum.locs.push({ start: cur.start, end: cur.end }); + return accum; + }, []); } + + return { info: renameInfo, locs: bakedRenameLocs }; } - findReferences(line: number, col: number, rawfile: string, reqSeq = 0) { + findReferences(line: number, col: number, fileName: string): ServerProtocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects - var file = ts.normalizePath(rawfile); - var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var refs = compilerService.languageService.getReferencesAtPosition(file, pos); - if (refs) { - var nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); - if (nameInfo) { - var displayString = ts.displayPartsToString(nameInfo.displayParts); - var nameSpan = nameInfo.textSpan; - var nameColStart = - compilerService.host.positionToLineCol(file, nameSpan.start).col; - var nameText = - compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - var bakedRefs: ServerProtocol.ReferencesResponseItem[] = refs.map((ref) => { - var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start); - var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); - var snap = compilerService.host.getScriptSnapshot(ref.fileName); - var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); - return { - file: ref.fileName, - start: start, - lineText: lineText, - end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), - }; - }).sort(compareFileStart); - var response: ServerProtocol.ReferencesResponseBody = { - refs: bakedRefs, - symbolName: nameText, - symbolStartCol: nameColStart, - symbolDisplayString: displayString - }; - this.output(response, CommandNames.References, reqSeq); - } - else { - this.output(undefined, CommandNames.References, reqSeq, "no references at this position"); - } - } - else { - this.output(undefined, CommandNames.References, reqSeq, "no references at this position"); - } - } - } - - // TODO: implement this as ls api that can return multiple def sites - goToType(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); - var typeLoc: any; - - if (quickInfo && (quickInfo.kind == "var") || (quickInfo.kind == "local var")) { - var typeName = parseTypeName(quickInfo.displayParts); - if (typeName) { - var navItems = compilerService.languageService.getNavigateToItems(typeName); - var navItem = findExactMatchType(navItems); - if (navItem) { - typeLoc = { - file: navItem.fileName, - start: compilerService.host.positionToLineCol(navItem.fileName, - navItem.textSpan.start), - end: compilerService.host.positionToLineCol(navItem.fileName, - ts.textSpanEnd(navItem.textSpan)), - }; - } - } - } - if (typeLoc) { - this.output([typeLoc], CommandNames.Type, reqSeq); - } - else { - this.output(undefined, CommandNames.Type, reqSeq, "no info at this location"); - } - } - else { - this.output(undefined, CommandNames.Type, reqSeq, "no project for " + file); - } + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + + var references = compilerService.languageService.getReferencesAtPosition(file, position); + if (!references) { + throw Errors.NoContent; + } + + var nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position); + if (!nameInfo) { + throw Errors.NoContent; + } + + var displayString = ts.displayPartsToString(nameInfo.displayParts); + var nameSpan = nameInfo.textSpan; + var nameColStart = compilerService.host.positionToLineCol(file, nameSpan.start).col; + var nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); + var bakedRefs: ServerProtocol.ReferencesResponseItem[] = references.map((ref) => { + var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start); + var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); + var snap = compilerService.host.getScriptSnapshot(ref.fileName); + var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); + return { + file: ref.fileName, + start: start, + lineText: lineText, + end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), + }; + }).sort(compareFileStart); + return { + refs: bakedRefs, + symbolName: nameText, + symbolStartCol: nameColStart, + symbolDisplayString: displayString + }; } openClientFile(rawfile: string) { @@ -746,173 +455,151 @@ module ts.server { this.projectService.openClientFile(file); } - quickInfo(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + quickInfo(line: number, col: number, fileName: string): ServerProtocol.QuickInfoResponseBody { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, pos); - if (quickInfo) { - var displayString = ts.displayPartsToString(quickInfo.displayParts); - var docString = ts.displayPartsToString(quickInfo.documentation); - var qi: ServerProtocol.QuickInfoResponseBody = { - kind: quickInfo.kind, - kindModifiers: quickInfo.kindModifiers, - start: compilerService.host.positionToLineCol(file, quickInfo.textSpan.start), - end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(quickInfo.textSpan)), - displayString: displayString, - documentation: docString, - }; - this.output(qi, CommandNames.Quickinfo, reqSeq); - } - else { - this.output(undefined, CommandNames.Quickinfo, reqSeq, "no info") - } - } + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, position); + if (!quickInfo) { + throw Errors.NoContent; + } + + var displayString = ts.displayPartsToString(quickInfo.displayParts); + var docString = ts.displayPartsToString(quickInfo.documentation); + return { + kind: quickInfo.kind, + kindModifiers: quickInfo.kindModifiers, + start: compilerService.host.positionToLineCol(file, quickInfo.textSpan.start), + end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(quickInfo.textSpan)), + displayString: displayString, + documentation: docString, + }; } - format(line: number, col: number, endLine: number, endCol: number, rawfile: string, cmd: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + format(line: number, col: number, endLine: number, endCol: number, fileName: string): ServerProtocol.CodeEdit[] { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var endPos = compilerService.host.lineColToPosition(file, endLine, endCol); - var edits: ts.TextChange[]; - // TODO: avoid duplicate code (with formatonkey) - try { - edits = compilerService.languageService.getFormattingEditsForRange(file, pos, endPos, - compilerService.formatCodeOptions); - } - catch (err) { - this.logError(err, cmd); - edits = undefined; - } - if (edits) { - var bakedEdits: ServerProtocol.CodeEdit[] = edits.map((edit) => { - return { - start: compilerService.host.positionToLineCol(file, - edit.span.start), - end: compilerService.host.positionToLineCol(file, - ts.textSpanEnd(edit.span)), - newText: edit.newText ? edit.newText : "" - }; - }); - this.output(bakedEdits, CommandNames.Format, reqSeq); - } - else { - this.output(undefined, CommandNames.Format, reqSeq, "no edits") - } - } + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var startPosition = compilerService.host.lineColToPosition(file, line, col); + var endPosition = compilerService.host.lineColToPosition(file, endLine, endCol); + + // TODO: avoid duplicate code (with formatonkey) + var edits = compilerService.languageService.getFormattingEditsForRange(file, startPosition, endPosition, compilerService.formatCodeOptions); + if (!edits) { + throw Errors.NoContent; + } + + return edits.map((edit) => { + return { + start: compilerService.host.positionToLineCol(file, edit.span.start), + end: compilerService.host.positionToLineCol(file, ts.textSpanEnd(edit.span)), + newText: edit.newText ? edit.newText : "" + }; + }); } - formatOnKey(line: number, col: number, key: string, rawfile: string, cmd: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + formatOnKey(line: number, col: number, key: string, fileName: string): ServerProtocol.CodeEdit[] { + var file = ts.normalizePath(fileName); + var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var edits: ts.TextChange[]; - try { - edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, pos, key, - compilerService.formatCodeOptions); - if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, pos))) { - // TODO: get these options from host - var editorOptions: ts.EditorOptions = { - IndentSize: 4, - TabSize: 4, - NewLineCharacter: "\n", - ConvertTabsToSpaces: true, - }; - var indentPosition = compilerService.languageService.getIndentationAtPosition(file, pos, editorOptions); - var spaces = generateSpaces(indentPosition); - if (indentPosition > 0) { - edits.push({ span: ts.createTextSpanFromBounds(pos, pos), newText: spaces }); - } - } - } - catch (err) { - this.logError(err, cmd); - edits = undefined; - } - if (edits) { - var bakedEdits: ServerProtocol.CodeEdit[] = edits.map((edit) => { - return { - start: compilerService.host.positionToLineCol(file, - edit.span.start), - end: compilerService.host.positionToLineCol(file, - ts.textSpanEnd(edit.span)), - newText: edit.newText ? edit.newText : "" - }; - }); - this.output(bakedEdits, CommandNames.Formatonkey, reqSeq); - } - else { - this.output(undefined, CommandNames.Formatonkey, reqSeq, "no edits") - } - } + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + var edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, position, key, + compilerService.formatCodeOptions); + if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, position))) { + // TODO: get these options from host + var editorOptions: ts.EditorOptions = { + IndentSize: 4, + TabSize: 4, + NewLineCharacter: "\n", + ConvertTabsToSpaces: true, + }; + var indentPosition = compilerService.languageService.getIndentationAtPosition(file, position, editorOptions); + var spaces = generateSpaces(indentPosition); + if (indentPosition > 0) { + edits.push({ span: ts.createTextSpanFromBounds(position, position), newText: spaces }); + } + } + + if (!edits) { + throw Errors.NoContent; + } + + return edits.map((edit) => { + return { + start: compilerService.host.positionToLineCol(file, + edit.span.start), + end: compilerService.host.positionToLineCol(file, + ts.textSpanEnd(edit.span)), + newText: edit.newText ? edit.newText : "" + }; + }); } - completions(line: number, col: number, prefix: string, rawfile: string, cmd: string, reqSeq = 0) { + completions(line: number, col: number, prefix: string, fileName: string): ServerProtocol.CompletionItem[] { if (!prefix) { prefix = ""; } - var file = ts.normalizePath(rawfile); + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - var completions: ts.CompletionInfo = undefined; - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - if (pos >= 0) { - try { - completions = compilerService.languageService.getCompletionsAtPosition(file, pos); + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + + var completions = compilerService.languageService.getCompletionsAtPosition(file, position); + if (!completions) { + throw Errors.NoContent; + } + + return completions.entries.reduce((accum: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { + if (entry.name.indexOf(prefix) == 0) { + var protoEntry = {}; + protoEntry.name = entry.name; + protoEntry.kind = entry.kind; + if (entry.kindModifiers && (entry.kindModifiers.length > 0)) { + protoEntry.kindModifiers = entry.kindModifiers; } - catch (err) { - this.logError(err, cmd); - completions = undefined; + var details = compilerService.languageService.getCompletionEntryDetails(file, position, entry.name); + if (details && (details.documentation) && (details.documentation.length > 0)) { + protoEntry.documentation = details.documentation; } - if (completions) { - var compressedEntries: ServerProtocol.CompletionItem[] = - completions.entries.reduce((accum: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { - if (entry.name.indexOf(prefix) == 0) { - var protoEntry = {}; - protoEntry.name = entry.name; - protoEntry.kind = entry.kind; - if (entry.kindModifiers && (entry.kindModifiers.length > 0)) { - protoEntry.kindModifiers = entry.kindModifiers; - } - var details = compilerService.languageService.getCompletionEntryDetails(file, pos, entry.name); - if (details && (details.documentation) && (details.documentation.length > 0)) { - protoEntry.documentation = details.documentation; - } - if (details && (details.displayParts) && (details.displayParts.length > 0)) { - protoEntry.displayParts = details.documentation; - } - accum.push(protoEntry); - } - return accum; - }, []); - this.output(compressedEntries, CommandNames.Completions, reqSeq); + if (details && (details.displayParts) && (details.displayParts.length > 0)) { + protoEntry.displayParts = details.documentation; } + accum.push(protoEntry); } - } - if (!completions) { - this.output(undefined, CommandNames.Completions, reqSeq, "no completions"); - } + return accum; + }, []); } - - geterr(ms: number, files: string[]) { - var checkList = files.reduce((accum: PendingErrorCheck[], filename: string) => { - filename = ts.normalizePath(filename); - var project = this.projectService.getProjectForFile(filename); + + geterr(delay: number, fileNames: string[]) { + var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { + fileName = ts.normalizePath(fileName); + var project = this.projectService.getProjectForFile(fileName); if (project) { - accum.push({ filename: filename, project: project }); + accum.push({ fileName, project }); } return accum; }, []); + if (checkList.length > 0) { - this.updateErrorCheck(checkList, this.changeSeq,(n) => n == this.changeSeq, ms) + this.updateErrorCheck(checkList, this.changeSeq,(n) => n == this.changeSeq, delay) } } @@ -997,352 +684,165 @@ module ts.server { } } - navto(searchTerm: string, rawfile: string, cmd: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + navto(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var navItems: ts.NavigateToItem[]; - var cancellationToken = compilerService.host.getCancellationToken(); - if (this.pendingOperation) { - cancellationToken.cancel(); - cancellationToken.reset(); - } - try { - this.pendingOperation = true; - navItems = sortNavItems(compilerService.languageService.getNavigateToItems(searchTerm)); - } - catch (err) { - this.logError(err, cmd); - navItems = undefined; - } - this.pendingOperation = false; - if (navItems) { - var bakedNavItems: ServerProtocol.NavtoItem[] = navItems.map((navItem) => { - var start = compilerService.host.positionToLineCol(navItem.fileName, - navItem.textSpan.start); - var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); - this.abbreviate(start); - var bakedItem: ServerProtocol.NavtoItem = { - name: navItem.name, - kind: navItem.kind, - file: this.encodeFilename(navItem.fileName), - start: start, - end: end, - }; - if (navItem.kindModifiers && (navItem.kindModifiers != "")) { - bakedItem.kindModifiers = navItem.kindModifiers; - } - if (navItem.matchKind != 'none') { - bakedItem.matchKind = navItem.matchKind; - } - if (navItem.containerName && (navItem.containerName.length > 0)) { - bakedItem.containerName = navItem.containerName; - } - if (navItem.containerKind && (navItem.containerKind.length > 0)) { - bakedItem.containerKind = navItem.containerKind; - } - this.abbreviate(bakedItem); - return bakedItem; - }); - - this.output(bakedNavItems, CommandNames.Navto, reqSeq); - } - else { - this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); - } + if (!project) { + throw Errors.NoProject; } - else { - this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); + + var compilerService = project.compilerService; + var navItems = sortNavItems(compilerService.languageService.getNavigateToItems(searchTerm)); + if (!navItems) { + throw Errors.NoContent; } - } - getMatchingBrace(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); - var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - var spans: ts.TextSpan[]; - try { - spans = compilerService.languageService.getBraceMatchingAtPosition(file, pos); + return navItems.map((navItem) => { + var start = compilerService.host.positionToLineCol(navItem.fileName, navItem.textSpan.start); + var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); + var bakedItem: ServerProtocol.NavtoItem = { + name: navItem.name, + kind: navItem.kind, + file: this.encodeFilename(navItem.fileName), + start: start, + end: end, + }; + if (navItem.kindModifiers && (navItem.kindModifiers != "")) { + bakedItem.kindModifiers = navItem.kindModifiers; } - catch (err) { - this.logError(err, CommandNames.Brace); - spans = undefined; + if (navItem.matchKind != 'none') { + bakedItem.matchKind = navItem.matchKind; } - if (spans) { - var bakedSpans: ServerProtocol.TextSpan[] = spans.map(span => ({ - start: span && - compilerService.host.positionToLineCol(file, span.start), - end: span && - compilerService.host.positionToLineCol(file, span.start + span.length) - })); - this.output(bakedSpans, CommandNames.Brace, reqSeq); + if (navItem.containerName && (navItem.containerName.length > 0)) { + bakedItem.containerName = navItem.containerName; } - else { - this.output(undefined, CommandNames.Brace, reqSeq, "no matching braces"); + if (navItem.containerKind && (navItem.containerKind.length > 0)) { + bakedItem.containerKind = navItem.containerKind; } + return bakedItem; + }); + } + + getBraceMatching(line: number, col: number, fileName: string): ServerProtocol.TextSpan[] { + var file = ts.normalizePath(fileName); + + var project = this.projectService.getProjectForFile(file); + if (!project) { + throw Errors.NoProject; } - else { - this.output(undefined, CommandNames.Brace, reqSeq, "no matching braces"); + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + + var spans = compilerService.languageService.getBraceMatchingAtPosition(file, position); + if (!spans) { + throw Errors.NoContent; } + + return spans.map(span => ({ + start: compilerService.host.positionToLineCol(file, span.start), + end: compilerService.host.positionToLineCol(file, span.start + span.length) + })); } - executeJSONcmd(cmd: string) { + onMessage(message: string) { try { - var req = JSON.parse(cmd); - switch (req.command) { + var request = JSON.parse(message); + var response: any; + switch (request.command) { case CommandNames.Definition: { - var defArgs = req.arguments; - this.goToDefinition(defArgs.line, defArgs.col, defArgs.file, req.seq); + var defArgs = request.arguments; + response = this.goToDefinition(defArgs.line, defArgs.col, defArgs.file); break; } case CommandNames.References: { - var refArgs = req.arguments; - this.findReferences(refArgs.line, refArgs.col, refArgs.file, req.seq); + var refArgs = request.arguments; + response = this.findReferences(refArgs.line, refArgs.col, refArgs.file); break; } case CommandNames.Rename: { - var renameArgs = req.arguments; - this.rename(renameArgs.line, renameArgs.col, renameArgs.file, req.seq); - break; - } - case CommandNames.Type: { - var typeArgs = req.arguments; - this.goToType(typeArgs.line, typeArgs.col, typeArgs.file, req.seq); + var renameArgs = request.arguments; + response = this.rename(renameArgs.line, renameArgs.col, renameArgs.file); break; } case CommandNames.Open: { - var openArgs = req.arguments; + var openArgs = request.arguments; this.openClientFile(openArgs.file); break; } case CommandNames.Quickinfo: { - var quickinfoArgs = req.arguments; - this.quickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file, req.seq); + var quickinfoArgs = request.arguments; + response = this.quickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); break; } case CommandNames.Format: { - var formatArgs = req.arguments; - this.format(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file, - cmd, req.seq); + var formatArgs = request.arguments; + response = this.format(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file); break; } case CommandNames.Formatonkey: { - var formatOnKeyArgs = req.arguments; - this.formatOnKey(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file, - cmd, req.seq); + var formatOnKeyArgs = request.arguments; + response = this.formatOnKey(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file); break; } case CommandNames.Completions: { - var completionsArgs = req.arguments; - this.completions(req.arguments.line, req.arguments.col, completionsArgs.prefix, req.arguments.file, - cmd, req.seq); + var completionsArgs = request.arguments; + response = this.completions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); break; } case CommandNames.Geterr: { - var geterrArgs = req.arguments; - this.geterr(geterrArgs.delay, geterrArgs.files); + var geterrArgs = request.arguments; + response = this.geterr(geterrArgs.delay, geterrArgs.files); break; } case CommandNames.Change: { - var changeArgs = req.arguments; + var changeArgs = request.arguments; this.change(changeArgs.line, changeArgs.col, changeArgs.deleteLen, changeArgs.insertString, changeArgs.file); break; } case CommandNames.Reload: { - var reloadArgs = req.arguments; - this.reload(reloadArgs.file, reloadArgs.tmpfile, req.seq); + var reloadArgs = request.arguments; + this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); break; } case CommandNames.Saveto: { - var savetoArgs = req.arguments; + var savetoArgs = request.arguments; this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); break; } case CommandNames.Close: { - var closeArgs = req.arguments; + var closeArgs = request.arguments; this.closeClientFile(closeArgs.file); break; } case CommandNames.Navto: { - var navtoArgs = req.arguments; - this.navto(navtoArgs.searchTerm, navtoArgs.file, cmd, req.seq); - break; - } - case CommandNames.Abbrev: { - this.sendAbbrev(); + var navtoArgs = request.arguments; + response = this.navto(navtoArgs.searchTerm, navtoArgs.file); break; } case CommandNames.Brace: { - var defArgs = req.arguments; - this.getMatchingBrace(defArgs.line, defArgs.col, defArgs.file, req.seq); + var braceArguments = request.arguments; + response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file); break; } default: { - this.projectService.log("Unrecognized JSON command: " + cmd); + this.projectService.log("Unrecognized JSON command: " + message); + this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command); break; } } - } catch (err) { - this.logError(err, cmd); - } - } - - sendAbbrev(reqSeq = 0) { - if (!this.fetchedAbbrev) { - this.output(this.abbrevTable, CommandNames.Abbrev, reqSeq); - } - this.fetchedAbbrev = true; - } - executeCmd(cmd: string) { - var line: number, col: number, file: string; - var m: string[]; - - try { - if (m = cmd.match(/^dbg start$/)) { - this.debugSession = new JsDebugSession(this.host.getDebuggerClient()); - } - else if (m = cmd.match(/^dbg cont$/)) { - if (this.debugSession) { - this.debugSession.cont((err, body, res) => { - }); - } - } - else if (m = cmd.match(/^dbg src$/)) { - if (this.debugSession) { - this.debugSession.listSrc(); - } - } - else if (m = cmd.match(/^dbg brk (\d+) (.*)$/)) { - line = parseInt(m[1]); - file = ts.normalizePath(m[2]); - if (this.debugSession) { - this.debugSession.setBreakpointOnLine(line, file); - } - } - else if (m = cmd.match(/^dbg eval (.*)$/)) { - var code = m[1]; - if (this.debugSession) { - this.debugSession.evaluate(code); - } - } - else if (m = cmd.match(/^definition (\d+) (\d+) (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - file = m[3]; - this.goToDefinition(line, col, file); - } - else if (m = cmd.match(/^rename (\d+) (\d+) (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - file = m[3]; - this.rename(line, col, file); - } - else if (m = cmd.match(/^type (\d+) (\d+) (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - file = m[3]; - this.goToType(line, col, file); - } - else if (m = cmd.match(/^open (.*)$/)) { - file = m[1]; - this.openClientFile(file); - } - else if (m = cmd.match(/^references (\d+) (\d+) (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - file = m[3]; - this.findReferences(line, col, file); - } - else if (m = cmd.match(/^quickinfo (\d+) (\d+) (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - file = m[3]; - this.quickInfo(line, col, file); - } - else if (m = cmd.match(/^format (\d+) (\d+) (\d+) (\d+) (.*)$/)) { - // format line col endLine endCol file - line = parseInt(m[1]); - col = parseInt(m[2]); - var endLine = parseInt(m[3]); - var endCol = parseInt(m[4]); - file = m[5]; - this.format(line, col, endLine, endCol, file, cmd); - } - else if (m = cmd.match(/^formatonkey (\d+) (\d+) (\{\".*\"\})\s* (.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - var key = JSON.parse(m[3].substring(1, m[3].length - 1)); - file = m[4]; - this.formatOnKey(line, col, key, file, cmd); - } - else if (m = cmd.match(/^completions (\d+) (\d+) (\{.*\})?\s*(.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - var prefix = ""; - file = m[4]; - if (m[3]) { - prefix = m[3].substring(1, m[3].length - 1); - } - this.completions(line, col, prefix, file, cmd); - } - else if (m = cmd.match(/^geterr (\d+) (.*)$/)) { - var ms = parseInt(m[1]); - var rawFiles = m[2]; - this.geterr(ms, rawFiles.split(';')); - } - else if (m = cmd.match(/^change (\d+) (\d+) (\d+) (\d+) (\{\".*\"\})?\s*(.*)$/)) { - line = parseInt(m[1]); - col = parseInt(m[2]); - var deleteLen = parseInt(m[3]); - var insertLen = parseInt(m[4]); - var insertString: string; - if (insertLen) { - insertString = JSON.parse(m[5].substring(1, m[5].length - 1)); - } - file = m[6]; - this.change(line, col, deleteLen, insertString, file); - } - else if (m = cmd.match(/^reload (.*) from (.*)$/)) { - this.reload(m[1], m[2]); - } - // TODO: change this to saveto - else if (m = cmd.match(/^save (.*) to (.*)$/)) { - this.saveToTmp(m[1], m[2]); - } - else if (m = cmd.match(/^close (.*)$/)) { - this.closeClientFile(m[1]); - } - else if (m = cmd.match(/^navto (\{.*\}) (.*)$/)) { - var searchTerm = m[1]; - searchTerm = searchTerm.substring(1, searchTerm.length - 1); - this.navto(searchTerm, m[2], cmd); - } - else if (m = cmd.match(/^navbar (.*)$/)) { - this.navbar(m[1]); - } - else if (m = cmd.match(/^abbrev/)) { - this.sendAbbrev(); - } - else if (m = cmd.match(/^pretty/)) { - this.prettyJSON = true; - } - else if (m = cmd.match(/^printproj/)) { - this.projectService.printProjects(); - } - else if (m = cmd.match(/^fileproj (.*)$/)) { - file = ts.normalizePath(m[1]); - this.projectService.printProjectsForFile(file); - } - else { - this.output(undefined, CommandNames.Unknown, 0, "Unrecognized command " + cmd); + if (response) { + this.output(response, request.command, request.seq); } + } catch (err) { - this.logError(err, cmd); + if (err instanceof OperationCanceledException) { + // Handle cancellation exceptions + } + this.logError(err, message); + this.output(undefined, request ? request.command : CommandNames.Unknown, request ? request.seq : 0, "Error processing request. " + err.message); } } } diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index 73c394041545c..95e671f561e67 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -188,18 +188,6 @@ declare module ServerProtocol { } } - /** - Type request; value of command field is "type". Return response - giving the code locations that define the type of the symbol - found in file at location line, col. - */ - export interface TypeRequest extends CodeLocationRequest { - } - - export interface TypeResponse extends Response { - body?: CodeSpan[]; - } - /** Open request; value of command field is "open". Notify the server that the client has file open. The server will not @@ -483,8 +471,8 @@ declare module ServerProtocol { kindModifiers?: string; /** The file in which the symbol is found; the value of this - field will always be a string unless the client opts-in to - file encoding by sending the "abbrev" request. + field will always be a string, number of a mapping between + a string and a number. */ file: EncodedFile; /** The location within file at which the symbol is found*/ @@ -528,27 +516,6 @@ declare module ServerProtocol { arguments: ChangeRequestArgs; } - /* - The following messages describe an OPTIONAL compression scheme - that clients can choose to use. If a client does not opt-in to - this scheme by sending an "abbrev" request, the server will not - compress messages. - */ - - /** - Abbrev request message; value of command field is "abbrev". - Server returns an array of mappings from common message field - names and common message field string values to shortened - versions of these strings. Once a client opts-in by requesting - the abbreviations, the server may send responses whose field - names are shortened. The server may also send file names as - instances of AssignFileId, or as file ids, if the corresponding - id assignment had been previously returned. Currently, only - responses to the "navto" request can be encoded. - */ - export interface AbbrevRequest extends Request { - } - /** If an object of this type is returned in place of a string as the value of a file field in a response message, add the @@ -572,25 +539,6 @@ declare module ServerProtocol { */ export type EncodedFile = number | IdFile | string; - /** - Response to abbrev opt-in request message. This is a map of - string field names and common string field values to shortened - strings. Once the server sends this response, it will assume - that it can use the shortened field names and field values. In - addition, the server will assume it can assign ids to file - names by returning an AssignFileId instance in place of a file - name. Once an AssignFileId instance is returned, the server - may send the file id (a number) in place of the file name. - */ - export interface AbbrevResponse extends Response { - body?: { - /** Map from full string (either field name or string - field value) to shortened string */ - [fullString: string]: string; - } - } - - /** Response to "brace" request. */ export interface BraceResponse extends Response { body?: TextSpan[]; diff --git a/src/server/server.ts b/src/server/server.ts index ec0ee293ce3e1..9f06e9b027723 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,18 +1,18 @@ -/// -/// - -module ts.server { +/// +/// + +module ts.server { var nodeproto: typeof NodeJS._debugger = require('_debugger'); var readline: NodeJS.ReadLine = require('readline'); - var path: NodeJS.Path = require('path'); + var path: NodeJS.Path = require('path'); var fs: typeof NodeJS.fs = require('fs'); - + var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false, - }); - + }); + class Logger implements ts.server.Logger { fd = -1; seq = 0; @@ -71,17 +71,17 @@ module ts.server { } } } - - class IOSession extends Session { - protocol: NodeJS._debugger.Protocol; - - constructor(host: ServerHost, logger: ts.server.Logger, useProtocol: boolean, prettyJSON: boolean) { - super(host, logger, useProtocol, prettyJSON); + + class IOSession extends Session { + protocol: NodeJS._debugger.Protocol; + + constructor(host: ServerHost, logger: ts.server.Logger, useProtocol: boolean, prettyJSON: boolean) { + super(host, logger, useProtocol, prettyJSON); if (useProtocol) { this.initProtocol(); - } - } - + } + } + initProtocol() { this.protocol = new nodeproto.Protocol(); // note: onResponse was named by nodejs authors; we are re-purposing the Protocol @@ -89,25 +89,16 @@ module ts.server { this.protocol.onResponse = (pkt) => { this.handleRequest(pkt); }; - } - + } + handleRequest(req: NodeJS._debugger.Packet) { this.projectService.log("Got JSON msg:\n" + req.raw); - } - + } + listen() { rl.on('line',(input: string) => { - var cmd = input.trim(); - if (cmd.indexOf("{") == 0) { - // assumption is JSON on single line - // plan is to also carry this protocol - // over tcp, in which case JSON would - // have a Content-Length header - this.executeJSONcmd(cmd); - } - else { - this.executeCmd(cmd); - } + var message = input.trim(); + this.onMessage(message); }); rl.on('close',() => { @@ -115,20 +106,13 @@ module ts.server { this.projectService.log("Exiting..."); process.exit(0); }); - } - } - + } + } + // This places log file in the directory containing editorServices.js // TODO: check that this location is writable - var logger = new Logger(__dirname + "/.log" + process.pid.toString()); - - var host: ServerHost = ts.sys; - - // Wire the debugging interface - if (!host.getDebuggerClient) { - host.getDebuggerClient = () => new nodeproto.Client(); - } - - // Start listening - new IOSession(host, logger, /* useProtocol */ true, /* prettyJSON */ false).listen(); + var logger = new Logger(__dirname + "/.log" + process.pid.toString()); + + // Start listening + new IOSession(ts.sys, logger, /* useProtocol */ true, /* prettyJSON */ false).listen(); } \ No newline at end of file From dfd8a0620dcb3130aec0c82b007edd7feb735236 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 15:50:06 -0800 Subject: [PATCH 19/45] Always default to using JSON and do not format it --- src/harness/harnessLanguageService.ts | 2 +- src/server/protocol.ts | 37 +++------------------------ src/server/server.ts | 24 +++-------------- 3 files changed, 7 insertions(+), 56 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 2e50ca59b97ad..9f0062cfd711e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -583,7 +583,7 @@ module Harness.LanguageService { // This host is just a proxy for the clientHost, it uses the client // host to answer server queries about files on disk var serverHost = new SessionServerHost(clientHost); - var server = new ts.server.Session(serverHost, serverHost, /*useProtocol*/ true, /*prettyJSON*/ false); + var server = new ts.server.Session(serverHost, serverHost); // Fake the connection between the client and the server serverHost.writeMessage = client.onMessage.bind(client); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 16abbf029d78b..b9eb72a78ed3a 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -157,7 +157,7 @@ module ts.server { immediateId: any; changeSeq = 0; - constructor(private host: ServerHost, private logger: Logger, protected useProtocol: boolean, protected prettyJSON: boolean) { + constructor(private host: ServerHost, private logger: Logger) { this.projectService = new ProjectService(host, logger); } @@ -178,13 +178,7 @@ module ts.server { } send(msg: NodeJS._debugger.Message) { - var json: string; - if (this.prettyJSON) { - json = JSON.stringify(msg, null, " "); - } - else { - json = JSON.stringify(msg); - } + var json = JSON.stringify(msg); this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) + '\r\n\r\n' + json); } @@ -229,32 +223,7 @@ module ts.server { } output(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { - if (this.useProtocol) { - this.response(info, cmdName, reqSeq, errorMsg); - } - else if (this.prettyJSON) { - if (!errorMsg) { - this.sendLineToClient(JSON.stringify(info, null, " ").trim()); - } - else { - this.sendLineToClient(JSON.stringify(errorMsg)); - } - } else { - if (!errorMsg) { - var infoStr = JSON.stringify(info).trim(); - // [8 digits of length,infoStr] + '\n' - var len = infoStr.length + paddedLength + 4; - var lenStr = len.toString(); - var padLen = paddedLength - lenStr.length; - for (var i = 0; i < padLen; i++) { - lenStr = '0' + lenStr; - } - this.sendLineToClient("[" + lenStr + "," + infoStr + "]"); - } - else { - this.sendLineToClient(JSON.stringify("error: " + errorMsg)); - } - } + this.response(info, cmdName, reqSeq, errorMsg); } semanticCheck(file: string, project: Project) { diff --git a/src/server/server.ts b/src/server/server.ts index 9f06e9b027723..974fb491674d9 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -73,26 +73,8 @@ module ts.server { } class IOSession extends Session { - protocol: NodeJS._debugger.Protocol; - - constructor(host: ServerHost, logger: ts.server.Logger, useProtocol: boolean, prettyJSON: boolean) { - super(host, logger, useProtocol, prettyJSON); - if (useProtocol) { - this.initProtocol(); - } - } - - initProtocol() { - this.protocol = new nodeproto.Protocol(); - // note: onResponse was named by nodejs authors; we are re-purposing the Protocol - // class in this case so that it supports a server instead of a client - this.protocol.onResponse = (pkt) => { - this.handleRequest(pkt); - }; - } - - handleRequest(req: NodeJS._debugger.Packet) { - this.projectService.log("Got JSON msg:\n" + req.raw); + constructor(host: ServerHost, logger: ts.server.Logger) { + super(host, logger); } listen() { @@ -114,5 +96,5 @@ module ts.server { var logger = new Logger(__dirname + "/.log" + process.pid.toString()); // Start listening - new IOSession(ts.sys, logger, /* useProtocol */ true, /* prettyJSON */ false).listen(); + new IOSession(ts.sys, logger).listen(); } \ No newline at end of file From b1750453ada87a7037960aef74a0ae2d5643c894 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 17:21:17 -0800 Subject: [PATCH 20/45] use ts.getDefaultLibFileName to get the default library file name --- src/server/editorServices.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 7007704a168b9..85ee71adce50c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -139,13 +139,7 @@ module ts.server { getDefaultLibFileName() { var nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath())); - - if (this.compilationSettings && this.compilationSettings.target == ts.ScriptTarget.ES6) { - return nodeModuleBinDir + "/lib.es6.d.ts"; - } - else { - return nodeModuleBinDir + "/lib.d.ts"; - } + return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings)); } cancel() { From ce828d0caec6fd92924fa640035ed76a6c5474d7 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 17:21:49 -0800 Subject: [PATCH 21/45] Get details for member completions as well as completions matching prefix --- src/server/protocol.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b9eb72a78ed3a..1efc79c5c402e 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -536,8 +536,8 @@ module ts.server { throw Errors.NoContent; } - return completions.entries.reduce((accum: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { - if (entry.name.indexOf(prefix) == 0) { + return completions.entries.reduce((result: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { + if (completions.isMemberCompletion || entry.name.indexOf(prefix) == 0) { var protoEntry = {}; protoEntry.name = entry.name; protoEntry.kind = entry.kind; @@ -551,9 +551,9 @@ module ts.server { if (details && (details.displayParts) && (details.displayParts.length > 0)) { protoEntry.displayParts = details.documentation; } - accum.push(protoEntry); + result.push(protoEntry); } - return accum; + return result; }, []); } From 89267bcd6fb967a6273a41ad51fbbc7445d6df24 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 19:53:12 -0800 Subject: [PATCH 22/45] Move fileWatching logic to the server to allow for testing on non-node systems --- src/compiler/sys.ts | 9 -- src/harness/harnessLanguageService.ts | 30 +---- src/server/editorServices.ts | 177 ++++++-------------------- src/server/server.ts | 119 +++++++++++++++++ 4 files changed, 168 insertions(+), 167 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c666ca991cfe1..5f10a747d421e 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -18,8 +18,6 @@ module ts { readDirectory(path: string, extension?: string): string[]; getMemoryUsage? (): number; exit(exitCode?: number): void; - getModififedTime? (fileName: string): Date; - stat? (fileName: string, callback?: (err: any, stats: any) => any): void; } export interface FileWatcher { @@ -305,13 +303,6 @@ module ts { }, exit(exitCode?: number): void { process.exit(exitCode); - }, - getModififedTime(fileName: string): Date { - var stats = _fs.statSync(fileName); - return stats.mtime; - }, - stat(fileName: string, callback?: (err: any, stats: any) => any) { - _fs.stat(fileName, callback); } }; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 9f0062cfd711e..712f0afe0aaf3 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -488,6 +488,10 @@ module Harness.LanguageService { } readFile(fileName: string): string { + if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) { + fileName = Harness.Compiler.defaultLibFileName; + } + var snapshot = this.host.getScriptSnapshot(fileName); return snapshot && snapshot.getText(0, snapshot.getLength()); } @@ -525,29 +529,9 @@ module Harness.LanguageService { readDirectory(path: string, extension?: string): string[] { throw new Error("Not implemented Yet."); } - - getModififedTime(fileName: string): Date { - return new Date(); - } - - stat(path: string, callback?: (err: any, stats: any) => any) { - return 0; - } - - lineColToPosition(fileName: string, line: number, col: number): number { - return this.host.lineColToPosition(fileName, line, col); - } - - positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { - return this.host.positionToZeroBasedLineCol(fileName, position); - } - - getFileLength(fileName: string): number { - return this.host.getScriptSnapshot(fileName).getLength(); - } - - getFileNames(): string[] { - return this.host.getScriptFileNames(); + + watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher { + return { close() { } }; } close(): void { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 85ee71adce50c..9e6363b509fcc 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -62,18 +62,15 @@ module ts.server { children: ScriptInfo[] = []; // files referenced by this file defaultProject: Project; // project to use by default for file - mtime: Date; - constructor(private host: ServerHost, public filename: string, public content: string, public isOpen = false) { + fileWatcher: FileWatcher; + + constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) { this.svc = ScriptVersionCache.fromString(content); - if (!isOpen) { - this.mtime = this.host.getModififedTime(filename); - } } close() { this.isOpen = false; - this.mtime = this.host.getModififedTime(this.filename); } addChild(childInfo: ScriptInfo) { @@ -203,7 +200,7 @@ module ts.server { removeReferencedFile(info: ScriptInfo) { if (!info.isOpen) { - this.filenameToScript[info.filename] = undefined; + this.filenameToScript[info.fileName] = undefined; } } @@ -212,7 +209,7 @@ module ts.server { if (!scriptInfo) { scriptInfo = this.project.openReferencedFile(filename); if (scriptInfo) { - this.filenameToScript[scriptInfo.filename] = scriptInfo; + this.filenameToScript[scriptInfo.fileName] = scriptInfo; } } else { @@ -221,9 +218,9 @@ module ts.server { } addRoot(info: ScriptInfo) { - var scriptInfo = ts.lookUp(this.filenameToScript, info.filename); + var scriptInfo = ts.lookUp(this.filenameToScript, info.fileName); if (!scriptInfo) { - this.filenameToScript[info.filename] = info; + this.filenameToScript[info.fileName] = info; return info; } } @@ -363,7 +360,7 @@ module ts.server { } getSourceFile(info: ScriptInfo) { - return this.filenameToSourceFile[info.filename]; + return this.filenameToSourceFile[info.fileName]; } getSourceFileFromName(filename: string) { @@ -439,112 +436,6 @@ module ts.server { return copiedList; } - // REVIEW: for now this implementation uses polling. - // The advantage of polling is that it works reliably - // on all os and with network mounted files. - // For 90 referenced files, the average time to detect - // changes is 2*msInterval (by default 5 seconds). - // The overhead of this is .04 percent (1/2500) with - // average pause of < 1 millisecond (and max - // pause less than 1.5 milliseconds); question is - // do we anticipate reference sets in the 100s and - // do we care about waiting 10-20 seconds to detect - // changes for large reference sets? If so, do we want - // to increase the chunk size or decrease the interval - // time dynamically to match the large reference set? - export class WatchedFileSet { - watchedFiles: ScriptInfo[] = []; - nextFileToCheck = 0; - watchTimer: NodeJS.Timer; - - // average async stat takes about 30 microseconds - // set chunk size to do 30 files in < 1 millisecond - constructor(private host: ServerHost, public fileEvent: (info: ScriptInfo, eventName: string) => void, - public msInterval = 2500, public chunkSize = 30) { - } - - checkWatchedFileChanged(checkedIndex: number, stats: NodeJS.fs.Stats) { - var info = this.watchedFiles[checkedIndex]; - if (info && (!info.isOpen)) { - if (info.mtime.getTime() != stats.mtime.getTime()) { - info.svc.reloadFromFile(info.filename); - } - } - } - - fileDeleted(info: ScriptInfo) { - if (this.fileEvent) { - this.fileEvent(info, "deleted"); - } - } - - static fileDeleted = 34; - - poll(checkedIndex: number) { - var watchedFile = this.watchedFiles[checkedIndex]; - if (!watchedFile) { - return; - } - if (measurePerf) { - var start = process.hrtime(); - } - this.host.stat(watchedFile.filename,(err, stats) => { - if (err) { - var msg = err.message; - if (err.errno) { - msg += " errno: " + err.errno.toString(); - } - if (err.errno == WatchedFileSet.fileDeleted) { - this.fileDeleted(watchedFile); - } - } - else { - this.checkWatchedFileChanged(checkedIndex, stats); - } - }); - if (measurePerf) { - var elapsed = process.hrtime(start); - var elapsedNano = 1e9 * elapsed[0] + elapsed[1]; - } - } - - // this implementation uses polling and - // stat due to inconsistencies of fs.watch - // and efficiency of stat on modern filesystems - startWatchTimer() { - this.watchTimer = setInterval(() => { - var count = 0; - var nextToCheck = this.nextFileToCheck; - var firstCheck = -1; - while ((count < this.chunkSize) && (nextToCheck != firstCheck)) { - this.poll(nextToCheck); - if (firstCheck < 0) { - firstCheck = nextToCheck; - } - nextToCheck++; - if (nextToCheck == this.watchedFiles.length) { - nextToCheck = 0; - } - count++; - } - this.nextFileToCheck = nextToCheck; - }, this.msInterval); - } - - // TODO: remove watch file if opened by editor or no longer referenced - // assume normalized and absolute pathname - addFile(info: ScriptInfo) { - this.watchedFiles.push(info); - if (this.watchedFiles.length == 1) { - this.startWatchTimer(); - } - } - - removeFile(info: ScriptInfo) { - this.watchedFiles = copyListRemovingItem(info, this.watchedFiles); - } - } - interface ProjectServiceEventHandler { (eventName: string, project: Project): void; } @@ -556,19 +447,31 @@ module ts.server { openFilesReferenced: ScriptInfo[] = []; // projects covering open files inferredProjects: Project[] = []; - watchedFileSet: WatchedFileSet; constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { if (measurePerf) { calibrateTimer(); } ts.disableIncrementalParsing = true; - this.watchedFileSet = new WatchedFileSet(this.host,(info, eventName) => { - if (eventName == "deleted") { - this.fileDeletedInFilesystem(info); + } + + watchedFileChanged(fileName: string) { + var info = this.filenameToScriptInfo[fileName]; + if (!info) { + this.psLogger.info("Error: got watch notification for unknown file: " + fileName); + } + + if (!this.host.fileExists(fileName)) { + // File was deleted + this.fileDeletedInFilesystem(info); + } + else { + if (info && (!info.isOpen)) { + info.svc.reloadFromFile(info.fileName); } - }); + } } + log(msg: string, type = "Err") { this.psLogger.msg(msg, type); @@ -587,11 +490,15 @@ module ts.server { } fileDeletedInFilesystem(info: ScriptInfo) { - this.psLogger.info(info.filename + " deleted"); - this.watchedFileSet.removeFile(info); + this.psLogger.info(info.fileName + " deleted"); + + if (info.fileWatcher) { + info.fileWatcher.close(); + info.fileWatcher = undefined; + } if (!info.isOpen) { - this.filenameToScriptInfo[info.filename] = undefined; + this.filenameToScriptInfo[info.fileName] = undefined; var referencingProjects = this.findReferencingProjects(info); for (var i = 0, len = referencingProjects.length; i < len; i++) { referencingProjects[i].removeReferencedFile(info); @@ -697,13 +604,13 @@ module ts.server { /** * @param filename is absolute pathname */ - openFile(filename: string, openedByClient = false) { - filename = ts.normalizePath(filename); - var info = ts.lookUp(this.filenameToScriptInfo, filename); + openFile(fileName: string, openedByClient = false) { + fileName = ts.normalizePath(fileName); + var info = ts.lookUp(this.filenameToScriptInfo, fileName); if (!info) { var content: string; - if (this.host.fileExists(filename)) { - content = this.host.readFile(filename); + if (this.host.fileExists(fileName)) { + content = this.host.readFile(fileName); } if (!content) { if (openedByClient) { @@ -711,10 +618,10 @@ module ts.server { } } if (content !== undefined) { - info = new ScriptInfo(this.host, filename, content, openedByClient); - this.filenameToScriptInfo[filename] = info; + info = new ScriptInfo(this.host, fileName, content, openedByClient); + this.filenameToScriptInfo[fileName] = info; if (!info.isOpen) { - this.watchedFileSet.addFile(info); + info.fileWatcher = this.host.watchFile(fileName, _ => { this.watchedFileChanged(fileName); }); } } } @@ -800,11 +707,11 @@ module ts.server { } this.psLogger.info("Open file roots: ") for (var i = 0, len = this.openFileRoots.length; i < len; i++) { - this.psLogger.info(this.openFileRoots[i].filename); + this.psLogger.info(this.openFileRoots[i].fileName); } this.psLogger.info("Open files referenced: ") for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { - this.psLogger.info(this.openFilesReferenced[i].filename); + this.psLogger.info(this.openFilesReferenced[i].fileName); } this.psLogger.endGroup(); } diff --git a/src/server/server.ts b/src/server/server.ts index 974fb491674d9..6d210ff0d13ce 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -72,6 +72,102 @@ module ts.server { } } + interface WatchedFile { + fileName: string; + callback: (fileName: string) => void; + mtime: Date; + } + + class WatchedFileSet { + 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 + constructor(public interval = 2500, public chunkSize = 30) { + } + + private static copyListRemovingItem(item: T, list: T[]) { + var copiedList: T[] = []; + for (var i = 0, len = list.length; i < len; i++) { + if (list[i] != item) { + copiedList.push(list[i]); + } + } + return copiedList; + } + + private static getModifiedTime(fileName: string): Date { + return fs.statSync(fileName).mtime; + } + + private poll(checkedIndex: number) { + var watchedFile = this.watchedFiles[checkedIndex]; + if (!watchedFile) { + return; + } + + 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); + } + } + else if (watchedFile.mtime.getTime() != stats.mtime.getTime()) { + watchedFile.mtime = WatchedFileSet.getModifiedTime(watchedFile.fileName); + watchedFile.callback(watchedFile.fileName); + } + }); + } + + // this implementation uses polling and + // stat due to inconsistencies of fs.watch + // and efficiency of stat on modern filesystems + private startWatchTimer() { + this.watchTimer = setInterval(() => { + var count = 0; + var nextToCheck = this.nextFileToCheck; + var firstCheck = -1; + while ((count < this.chunkSize) && (nextToCheck != firstCheck)) { + this.poll(nextToCheck); + if (firstCheck < 0) { + firstCheck = nextToCheck; + } + nextToCheck++; + if (nextToCheck === this.watchedFiles.length) { + nextToCheck = 0; + } + count++; + } + this.nextFileToCheck = nextToCheck; + }, this.interval); + } + + addFile(fileName: string, callback: (fileName: string) => void ): WatchedFile { + var file: WatchedFile = { + fileName, + callback, + mtime: WatchedFileSet.getModifiedTime(fileName) + }; + + this.watchedFiles.push(file); + if (this.watchedFiles.length === 1) { + this.startWatchTimer(); + } + return file; + } + + removeFile(file: WatchedFile) { + this.watchedFiles = WatchedFileSet.copyListRemovingItem(file, this.watchedFiles); + } + } + class IOSession extends Session { constructor(host: ServerHost, logger: ts.server.Logger) { super(host, logger); @@ -95,6 +191,29 @@ module ts.server { // TODO: check that this location is writable var logger = new Logger(__dirname + "/.log" + process.pid.toString()); + + // REVIEW: for now this implementation uses polling. + // The advantage of polling is that it works reliably + // on all os and with network mounted files. + // For 90 referenced files, the average time to detect + // changes is 2*msInterval (by default 5 seconds). + // The overhead of this is .04 percent (1/2500) with + // average pause of < 1 millisecond (and max + // pause less than 1.5 milliseconds); question is + // do we anticipate reference sets in the 100s and + // do we care about waiting 10-20 seconds to detect + // changes for large reference sets? If so, do we want + // to increase the chunk size or decrease the interval + // time dynamically to match the large reference set? + var watchedFileSet = new WatchedFileSet(); + ts.sys.watchFile = function (fileName, callback) { + var watchedFile = watchedFileSet.addFile(fileName, callback); + return { + close: () => watchedFileSet.removeFile(watchedFile) + } + + }; + // Start listening new IOSession(ts.sys, logger).listen(); } \ No newline at end of file From d396ddfa7b211216752647419dfc65f6f80a93cc Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 19:53:23 -0800 Subject: [PATCH 23/45] Add test for completions --- src/server/client.ts | 3 ++- tests/cases/fourslash/server/completions.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/server/completions.ts diff --git a/src/server/client.ts b/src/server/client.ts index 1aa7547f5411f..aa1ddb34bf742 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -177,10 +177,11 @@ module ts.server { getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CodeLocationRequestArgs = { + var args: ServerProtocol.CompletionsRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, + prefix: undefined }; var request = this.processRequest(CommandNames.Completions, args); diff --git a/tests/cases/fourslash/server/completions.ts b/tests/cases/fourslash/server/completions.ts new file mode 100644 index 0000000000000..82d3b5b2400cd --- /dev/null +++ b/tests/cases/fourslash/server/completions.ts @@ -0,0 +1,17 @@ +/// + +////var x: string[] = []; +////x.forEach(function (y) { y/*1*/ +////x.forEach(y => y/*2*/ + +goTo.marker('1'); +edit.insert('.'); +verify.memberListContains('trim'); +verify.memberListCount(20); +edit.insert('});'); // need the following lines to not have parse errors in order for completion list to appear + +goTo.marker('2'); +edit.insert('.'); +verify.memberListContains('trim'); +verify.memberListCount(20); + \ No newline at end of file From 9867e062a2b3a0d2a8850ee6aef9654c01cb9056 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 20:23:16 -0800 Subject: [PATCH 24/45] Add a new definition test --- tests/cases/fourslash/server/definition.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/cases/fourslash/server/definition.ts diff --git a/tests/cases/fourslash/server/definition.ts b/tests/cases/fourslash/server/definition.ts new file mode 100644 index 0000000000000..531e45628433d --- /dev/null +++ b/tests/cases/fourslash/server/definition.ts @@ -0,0 +1,12 @@ +/// + +// @Filename: b.ts +////import n = require('a/*1*/'); +////var x = new n.Foo(); + +// @Filename: a.ts +//// /*2*/export class Foo {} + +goTo.marker('1'); +goTo.definition(); +verify.caretAtMarker('2'); \ No newline at end of file From 11e246036bf962d0f7d6a7ac1ac12e8df46fe1cc Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 21:49:10 -0800 Subject: [PATCH 25/45] Add test for find references --- src/server/client.ts | 2 +- src/server/protocol.ts | 1 + src/server/protodef.d.ts | 3 +++ tests/cases/fourslash/server/references.ts | 18 ++++++++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/server/references.ts diff --git a/src/server/client.ts b/src/server/client.ts index aa1ddb34bf742..3295ec7619379 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -301,7 +301,7 @@ module ts.server { return { fileName: entry.file, textSpan: ts.createTextSpanFromBounds(start, end), - isWriteAccess: false, + isWriteAccess: entry.isWriteAccess, }; }); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1efc79c5c402e..3e28806b69621 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -409,6 +409,7 @@ module ts.server { start: start, lineText: lineText, end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), + isWriteAccess: ref.isWriteAccess }; }).sort(compareFileStart); return { diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index 95e671f561e67..b68616a13ec63 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -127,6 +127,9 @@ declare module ServerProtocol { loaded the referencing files). */ lineText: string; + + /** True if reference is a write location, false otherwise. */ + isWriteAccess: boolean; } /** The body of a "references" response message. */ diff --git a/tests/cases/fourslash/server/references.ts b/tests/cases/fourslash/server/references.ts new file mode 100644 index 0000000000000..55b2161555139 --- /dev/null +++ b/tests/cases/fourslash/server/references.ts @@ -0,0 +1,18 @@ +/// + +// Global class reference. + +// @Filename: referencesForGlobals_1.ts +////class /*2*/globalClass { +//// public f() { } +////} + +// @Filename: referencesForGlobals_2.ts +/////// +////var c = /*1*/globalClass(); + +goTo.marker("1"); +verify.referencesCountIs(2); + +goTo.marker("2"); +verify.referencesCountIs(2); \ No newline at end of file From 178e8f76e65f3c0971a1015753d0c6f11ea2e52f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 22:28:38 -0800 Subject: [PATCH 26/45] Add test for quickInfo --- src/server/client.ts | 11 +++++---- tests/cases/fourslash/server/quickinfo.ts | 27 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/server/quickinfo.ts diff --git a/src/server/client.ts b/src/server/client.ts index 3295ec7619379..599de2d8505ea 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -152,10 +152,11 @@ module ts.server { } getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { + var lineCol = this.positionToOneBasedLineCol(fileName, position); var args: ServerProtocol.CodeLocationRequestArgs = { file: fileName, - line: 0, - col: 1 + line: lineCol.line, + col: lineCol.col }; var request = this.processRequest(CommandNames.Quickinfo, args); @@ -168,10 +169,8 @@ module ts.server { kind: response.body.kind, kindModifiers: response.body.kindModifiers, textSpan: ts.createTextSpanFromBounds(start, end), - displayParts: undefined, - documentation: undefined, - documentationString: response.body.documentation, - displayString: response.body.displayString + displayParts: [{ kind: "text", text: response.body.displayString }], + documentation: [{ kind: "text", text: response.body.documentation }] }; } diff --git a/tests/cases/fourslash/server/quickinfo.ts b/tests/cases/fourslash/server/quickinfo.ts new file mode 100644 index 0000000000000..b5c7dc0a01c0f --- /dev/null +++ b/tests/cases/fourslash/server/quickinfo.ts @@ -0,0 +1,27 @@ +/// + +////interface One { +//// commonProperty: number; +//// commonFunction(): number; +////} +//// +////interface Two { +//// commonProperty: string +//// commonFunction(): number; +////} +//// +////var /*1*/x : One | Two; +//// +////x./*2*/commonProperty; +////x./*3*/commonFunction; + + +goTo.marker("1"); +verify.quickInfoIs('(var) x: One | Two'); + + +goTo.marker("2"); +verify.quickInfoIs('(property) commonProperty: string | number'); + +goTo.marker("3"); +verify.quickInfoIs('(method) commonFunction(): number'); From 40d3cb78d50bc5adc262442c61d8e9609f856910 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 23:07:20 -0800 Subject: [PATCH 27/45] Add test for rename --- src/server/client.ts | 45 ++++++++++++++++++++- src/server/protocol.ts | 55 ++++++-------------------- src/server/protodef.d.ts | 7 ++++ tests/cases/fourslash/server/rename.ts | 11 ++++++ 4 files changed, 73 insertions(+), 45 deletions(-) create mode 100644 tests/cases/fourslash/server/rename.ts diff --git a/src/server/client.ts b/src/server/client.ts index 599de2d8505ea..960c0723c3061 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -322,11 +322,52 @@ module ts.server { } getRenameInfo(fileName: string, position: number): RenameInfo { - throw new Error("Not Implemented Yet."); + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var args: ServerProtocol.RenameRequestArgs = { + file: fileName, + line: lineCol.line, + col: lineCol.col + }; + + var request = this.processRequest(CommandNames.Rename, args); + var response = this.processResponse(request); + + return { + canRename: response.body.info.canRename, + displayName: response.body.info.displayName, + fullDisplayName: response.body.info.fullDisplayName, + kind: response.body.info.kind, + kindModifiers: response.body.info.kindModifiers, + localizedErrorMessage: response.body.info.localizedErrorMessage, + triggerSpan: ts.createTextSpanFromBounds(position, position) + }; } findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - throw new Error("Not Implemented Yet."); + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var args: ServerProtocol.RenameRequestArgs = { + file: fileName, + line: lineCol.line, + col: lineCol.col, + findInStrings, + findInComments + }; + + var request = this.processRequest(CommandNames.Rename, args); + var response = this.processResponse(request); + + if (!response.body.info.canRename) { + return []; + } + + return response.body.locs.map((entry) => { + var start = this.lineColToPosition(entry.file, entry.start); + var end = this.lineColToPosition(entry.file, entry.end); + return { + textSpan: ts.createTextSpanFromBounds(start, end), + fileName: entry.file + }; + }); } getNavigationBarItems(fileName: string): NavigationBarItem[] { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 3e28806b69621..1404b3ea4b1a4 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -307,7 +307,7 @@ module ts.server { })); } - rename(line: number, col: number, fileName: string): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { + rename(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -328,48 +328,17 @@ module ts.server { }; } - var renameLocs = compilerService.languageService.findRenameLocations(file, position, false, false); - if (renameLocs) { - var bakedRenameLocs = renameLocs.map(loc=> ({ - file: loc.fileName, - start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), - end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), - })).sort((a, b) => { - if (a.file < b.file) { - return -1; - } - else if (a.file > b.file) { - return 1; - } - else { - // reverse sort assuming no overlap - if (a.start.line < b.start.line) { - return 1; - } - else if (a.start.line > b.start.line) { - return -1; - } - else { - return b.start.col - a.start.col; - } - } - }).reduce((accum, cur) => { - var curFileAccum: FileRanges; - if (accum.length > 0) { - curFileAccum = accum[accum.length - 1]; - if (curFileAccum.file != cur.file) { - curFileAccum = undefined; - } - } - if (!curFileAccum) { - curFileAccum = { file: cur.file, locs: [] }; - accum.push(curFileAccum); - } - curFileAccum.locs.push({ start: cur.start, end: cur.end }); - return accum; - }, []); + var renameLocs = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); + if (!renameLocs) { + throw Errors.NoContent; } + var bakedRenameLocs = renameLocs.map(loc => ({ + file: loc.fileName, + start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), + end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), + })); + return { info: renameInfo, locs: bakedRenameLocs }; } @@ -731,8 +700,8 @@ module ts.server { break; } case CommandNames.Rename: { - var renameArgs = request.arguments; - response = this.rename(renameArgs.line, renameArgs.col, renameArgs.file); + var renameArgs = request.arguments; + response = this.rename(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); break; } case CommandNames.Open: { diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index b68616a13ec63..fb88d30d2efb7 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -151,6 +151,12 @@ declare module ServerProtocol { body?: ReferencesResponseBody; } + export interface RenameRequestArgs extends CodeLocationRequestArgs { + findInComments?: boolean; + findInStrings?: boolean; + } + + /** Rename request; value of command field is "rename". Return response giving the code locations that reference the symbol @@ -158,6 +164,7 @@ declare module ServerProtocol { name of the symbol so that client can print it unambiguously. */ export interface RenameRequest extends CodeLocationRequest { + arguments: RenameRequestArgs; } /** Information about the item to be renamed. */ diff --git a/tests/cases/fourslash/server/rename.ts b/tests/cases/fourslash/server/rename.ts new file mode 100644 index 0000000000000..4f8b7b98cd49f --- /dev/null +++ b/tests/cases/fourslash/server/rename.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to [|Bar|] in a comment. +//// "this is a reference to [|Bar|] in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true); \ No newline at end of file From 2a02655f728be9b3d54e1b5c783ed588c280399f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 16:23:36 -0800 Subject: [PATCH 28/45] Add navbar test --- src/harness/fourslash.ts | 2 +- src/server/client.ts | 26 ++++++++- src/server/protocol.ts | 78 +++++++++++++------------- src/server/protodef.d.ts | 28 +++++++++ tests/cases/fourslash/server/navbar.ts | 52 +++++++++++++++++ 5 files changed, 144 insertions(+), 42 deletions(-) create mode 100644 tests/cases/fourslash/server/navbar.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index bc7a8718a76a4..bb144742f45fd 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1932,7 +1932,7 @@ module FourSlash { } var missingItem = { name: name, kind: kind }; - this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')'); + this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items, null, " ") + ')'); } private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) { diff --git a/src/server/client.ts b/src/server/client.ts index 960c0723c3061..d23bc5c7359ba 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -370,8 +370,32 @@ module ts.server { }); } + decodeNavigationBarItems(items: ServerProtocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { + if (!items) { + return []; + } + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers || "", + spans: item.spans.map(span=> createTextSpanFromBounds(this.lineColToPosition(fileName, span.start), this.lineColToPosition(fileName, span.end))), + childItems: this.decodeNavigationBarItems(item.childItems, fileName), + indent: 0, + bolded: false, + grayed: false + })); + } + getNavigationBarItems(fileName: string): NavigationBarItem[] { - throw new Error("Not Implemented Yet."); + var args: ServerProtocol.FileRequestArgs = { + file: fileName + }; + + var request = this.processRequest(CommandNames.NavBar, args); + var response = this.processResponse(request); + + return this.decodeNavigationBarItems(response.body, fileName); } getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1404b3ea4b1a4..0e6a0ff8f6553 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -4,13 +4,6 @@ /// /// -module ts { - export interface NavigationBarItem { - displayString?: string; - docString?: string; - } -} - module ts.server { var paddedLength = 8; @@ -129,6 +122,7 @@ module ts.server { export var Format = "format"; export var Formatonkey = "formatonkey"; export var Geterr = "geterr"; + export var NavBar = "navbar"; export var Navto = "navto"; export var Open = "open"; export var Quickinfo = "quickinfo"; @@ -426,7 +420,7 @@ module ts.server { if (!project) { throw Errors.NoProject; } - + var compilerService = project.compilerService; var startPosition = compilerService.host.lineColToPosition(file, line, col); var endPosition = compilerService.host.lineColToPosition(file, endLine, endCol); @@ -436,7 +430,7 @@ module ts.server { if (!edits) { throw Errors.NoContent; } - + return edits.map((edit) => { return { start: compilerService.host.positionToLineCol(file, edit.span.start), @@ -500,7 +494,7 @@ module ts.server { var compilerService = project.compilerService; var position = compilerService.host.lineColToPosition(file, line, col); - + var completions = compilerService.languageService.getCompletionsAtPosition(file, position); if (!completions) { throw Errors.NoContent; @@ -526,7 +520,7 @@ module ts.server { return result; }, []); } - + geterr(delay: number, fileNames: string[]) { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); @@ -587,40 +581,39 @@ module ts.server { this.projectService.closeClientFile(file); } - decorateNavBarItem(navBarItem: ts.NavigationBarItem, compilerService: CompilerService, file: string) { - if (navBarItem.spans.length == 1) { - var span = navBarItem.spans[0]; - var offset = span.start; - var textForSpan = compilerService.host.getScriptSnapshot(file).getText(offset, offset + span.length); - var adj = textForSpan.indexOf(navBarItem.text); - if (adj > 0) { - offset += adj; - } - var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, - offset + (navBarItem.text.length / 2)); - if (quickInfo) { - var displayString = ts.displayPartsToString(quickInfo.displayParts); - var docString = ts.displayPartsToString(quickInfo.documentation); - navBarItem.displayString = displayString; - navBarItem.docString = docString; - } - } - if (navBarItem.childItems.length > 0) { - navBarItem.childItems = - navBarItem.childItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); + decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): ServerProtocol.NavigationBarItem[] { + if (!items) { + return undefined; } - return navBarItem; + + var compilerService = project.compilerService; + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers, + spans: item.spans.map(span => ({ + start: compilerService.host.positionToLineCol(fileName, span.start), + end: compilerService.host.positionToLineCol(fileName, ts.textSpanEnd(span)) + })), + childItems: this.decorateNavigationBarItem(project, fileName, item.childItems) + })); } - navbar(rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + navbar(fileName: string): ServerProtocol.NavigationBarItem[] { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var navBarItems = compilerService.languageService.getNavigationBarItems(file); - var bakedNavBarItems = navBarItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); - this.sendLineToClient(JSON.stringify(bakedNavBarItems, null, " ")); + if (!project) { + throw Errors.NoProject; } + + var compilerService = project.compilerService; + var items = compilerService.languageService.getNavigationBarItems(file); + if (!items) { + throw Errors.NoContent; + } + + return this.decorateNavigationBarItem(project, fileName, items); } navto(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { @@ -765,6 +758,11 @@ module ts.server { response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file); break; } + case CommandNames.NavBar: { + var navBarArgs = request.arguments; + response = this.navbar(navBarArgs.file); + break; + } default: { this.projectService.log("Unrecognized JSON command: " + message); this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command); diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index fb88d30d2efb7..12fd7163c1ad8 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -562,6 +562,34 @@ declare module ServerProtocol { export interface BraceRequest extends CodeLocationRequest { } + /** + NavBar itesm request; value of command field is "navbar". + Return response giving the list of navigation bar entries + extracted from the requested file. + */ + export interface NavBarRequest extends FileRequest { + } + + export interface NavigationBarItem { + /** The item's display text */ + text: string; + + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + + /** The definition locations of the item */ + spans: TextSpan[]; + + /** Optional children */ + childItems?: NavigationBarItem[]; + } + + export interface NavBarResponse extends Response { + body?: NavigationBarItem[]; + } } diff --git a/tests/cases/fourslash/server/navbar.ts b/tests/cases/fourslash/server/navbar.ts new file mode 100644 index 0000000000000..e18f49c1db9dc --- /dev/null +++ b/tests/cases/fourslash/server/navbar.ts @@ -0,0 +1,52 @@ +/// + +////// Interface +////{| "itemName": "IPoint", "kind": "interface", "parentName": "" |}interface IPoint { +//// {| "itemName": "getDist", "kind": "method", "parentName": "IPoint" |}getDist(): number; +//// {| "itemName": "new()", "kind": "construct", "parentName": "IPoint" |}new(): IPoint; +//// {| "itemName": "()", "kind": "call", "parentName": "IPoint" |}(): any; +//// {| "itemName": "[]", "kind": "index", "parentName": "IPoint" |}[x:string]: number; +//// {| "itemName": "prop", "kind": "property", "parentName": "IPoint" |}prop: string; +////} +//// +/////// Module +////{| "itemName": "Shapes", "kind": "module", "parentName": "" |}module Shapes { +//// +//// // Class +//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point implements IPoint { +//// {| "itemName": "constructor", "kind": "constructor", "parentName": "Shapes.Point" |}constructor (public x: number, public y: number) { } +//// +//// // Instance member +//// {| "itemName": "getDist", "kind": "method", "parentName": "Shapes.Point" |}getDist() { return Math.sqrt(this.x * this.x + this.y * this.y); } +//// +//// // Getter +//// {| "itemName": "value", "kind": "getter", "parentName": "Shapes.Point" |}get value(): number { return 0; } +//// +//// // Setter +//// {| "itemName": "value", "kind": "setter", "parentName": "Shapes.Point" |}set value(newValue: number) { return; } +//// +//// // Static member +//// {| "itemName": "origin", "kind": "property", "parentName": "Shapes.Point" |}static origin = new Point(0, 0); +//// +//// // Static method +//// {| "itemName": "getOrigin", "kind": "method", "parentName": "Shapes.Point" |}private static getOrigin() { return Point.origin;} +//// } +//// +//// {| "itemName": "Values", "kind": "enum", "parentName": "Shapes" |}enum Values { +//// value1, +//// {| "itemName": "value2", "kind": "property", "parentName": "Shapes.Values" |}value2, +//// value3, +//// } +////} +//// +////// Local variables +////{| "itemName": "p", "kind": "var", "parentName": "" |}var p: IPoint = new Shapes.Point(3, 4); +////{| "itemName": "dist", "kind": "var", "parentName": "" |}var dist = p.getDist(); + +test.markers().forEach((marker) => { + if (marker.data) { + verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName); + } +}); + +verify.getScriptLexicalStructureListCount(23); From 50ca35a979c6ba8744061546b50e743c73280df2 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 16:28:39 -0800 Subject: [PATCH 29/45] Make method names inline with matching LS function names --- src/server/protocol.ts | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0e6a0ff8f6553..04b3ce065b01b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -277,7 +277,7 @@ module ts.server { } } - goToDefinition(line: number, col: number, fileName: string): ServerProtocol.CodeSpan[] { + getDefinition(line: number, col: number, fileName: string): ServerProtocol.CodeSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -301,7 +301,7 @@ module ts.server { })); } - rename(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { + getRenameLocations(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -336,7 +336,7 @@ module ts.server { return { info: renameInfo, locs: bakedRenameLocs }; } - findReferences(line: number, col: number, fileName: string): ServerProtocol.ReferencesResponseBody { + getReferences(line: number, col: number, fileName: string): ServerProtocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects var file = ts.normalizePath(fileName); @@ -388,7 +388,7 @@ module ts.server { this.projectService.openClientFile(file); } - quickInfo(line: number, col: number, fileName: string): ServerProtocol.QuickInfoResponseBody { + getQuickInfo(line: number, col: number, fileName: string): ServerProtocol.QuickInfoResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -414,7 +414,7 @@ module ts.server { }; } - format(line: number, col: number, endLine: number, endCol: number, fileName: string): ServerProtocol.CodeEdit[] { + getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): ServerProtocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -440,7 +440,7 @@ module ts.server { }); } - formatOnKey(line: number, col: number, key: string, fileName: string): ServerProtocol.CodeEdit[] { + getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): ServerProtocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -482,7 +482,7 @@ module ts.server { }); } - completions(line: number, col: number, prefix: string, fileName: string): ServerProtocol.CompletionItem[] { + getCompletions(line: number, col: number, prefix: string, fileName: string): ServerProtocol.CompletionItem[] { if (!prefix) { prefix = ""; } @@ -521,7 +521,7 @@ module ts.server { }, []); } - geterr(delay: number, fileNames: string[]) { + getDiagnostics(delay: number, fileNames: string[]) { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(fileName); @@ -600,7 +600,7 @@ module ts.server { })); } - navbar(fileName: string): ServerProtocol.NavigationBarItem[] { + getNavigationBarItems(fileName: string): ServerProtocol.NavigationBarItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -616,7 +616,7 @@ module ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - navto(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { + getNavigateToItems(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -684,17 +684,17 @@ module ts.server { switch (request.command) { case CommandNames.Definition: { var defArgs = request.arguments; - response = this.goToDefinition(defArgs.line, defArgs.col, defArgs.file); + response = this.getDefinition(defArgs.line, defArgs.col, defArgs.file); break; } case CommandNames.References: { var refArgs = request.arguments; - response = this.findReferences(refArgs.line, refArgs.col, refArgs.file); + response = this.getReferences(refArgs.line, refArgs.col, refArgs.file); break; } case CommandNames.Rename: { var renameArgs = request.arguments; - response = this.rename(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); + response = this.getRenameLocations(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); break; } case CommandNames.Open: { @@ -704,27 +704,27 @@ module ts.server { } case CommandNames.Quickinfo: { var quickinfoArgs = request.arguments; - response = this.quickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); + response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); break; } case CommandNames.Format: { var formatArgs = request.arguments; - response = this.format(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file); + response = this.getFormattingEditsForRange(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file); break; } case CommandNames.Formatonkey: { var formatOnKeyArgs = request.arguments; - response = this.formatOnKey(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file); + response = this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file); break; } case CommandNames.Completions: { var completionsArgs = request.arguments; - response = this.completions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); + response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); break; } case CommandNames.Geterr: { var geterrArgs = request.arguments; - response = this.geterr(geterrArgs.delay, geterrArgs.files); + response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files); break; } case CommandNames.Change: { @@ -750,7 +750,7 @@ module ts.server { } case CommandNames.Navto: { var navtoArgs = request.arguments; - response = this.navto(navtoArgs.searchTerm, navtoArgs.file); + response = this.getNavigateToItems(navtoArgs.searchTerm, navtoArgs.file); break; } case CommandNames.Brace: { @@ -760,7 +760,7 @@ module ts.server { } case CommandNames.NavBar: { var navBarArgs = request.arguments; - response = this.navbar(navBarArgs.file); + response = this.getNavigationBarItems(navBarArgs.file); break; } default: { From 8944df18d44da30b689ec53861d7214e534d8ee0 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 17:37:26 -0800 Subject: [PATCH 30/45] use EncodedFile everywhere in responses --- src/server/client.ts | 71 +++++++++++++++++++++++----------------- src/server/protocol.ts | 64 ++++++++++++++++++------------------ src/server/protodef.d.ts | 8 +++-- 3 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index d23bc5c7359ba..24666c8f4c799 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -6,11 +6,20 @@ module ts.server { writeMessage(message: string): void; } + interface RenameEntry extends RenameInfo { + fileName: string; + position: number; + locations: RenameLocation[]; + findInStrings: boolean; + findInComments: boolean; + } + export class SessionClient implements LanguageService { private sequence: number = 0; private fileMapping: ts.Map = {}; private lineMaps: ts.Map = {}; private messages: string[] = []; + private lastRenameEntry: RenameEntry; constructor(private host: SessionClientHost) { } @@ -54,7 +63,7 @@ module ts.server { }; } - private getFileNameFromEncodedFile(fileId: ServerProtocol.EncodedFile): string { + private decodeEncodedFileId(fileId: ServerProtocol.EncodedFile): string { var fileName: string; if (typeof fileId === "object") { fileName = (fileId).file; @@ -203,7 +212,7 @@ module ts.server { var response = this.processResponse(request); return response.body.map(entry => { - var fileName = this.getFileNameFromEncodedFile(entry.file); + var fileName = this.decodeEncodedFileId(entry.file); var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); @@ -270,12 +279,13 @@ module ts.server { var response = this.processResponse(request); return response.body.map(entry => { + var fileName = this.decodeEncodedFileId(entry.file); var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); return { containerKind: "", containerName: "", - fileName: entry.file, + fileName: fileName, textSpan: ts.createTextSpanFromBounds(start, end), kind: "", name: "" @@ -295,10 +305,11 @@ module ts.server { var response = this.processResponse(request); return response.body.refs.map(entry => { + var fileName = this.decodeEncodedFileId(entry.file); var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); return { - fileName: entry.file, + fileName: fileName, textSpan: ts.createTextSpanFromBounds(start, end), isWriteAccess: entry.isWriteAccess, }; @@ -321,53 +332,53 @@ module ts.server { throw new Error("Not Implemented Yet."); } - getRenameInfo(fileName: string, position: number): RenameInfo { + getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); var args: ServerProtocol.RenameRequestArgs = { file: fileName, line: lineCol.line, - col: lineCol.col + col: lineCol.col, + findInStrings, + findInComments }; var request = this.processRequest(CommandNames.Rename, args); var response = this.processResponse(request); - return { + return this.lastRenameEntry = { canRename: response.body.info.canRename, displayName: response.body.info.displayName, fullDisplayName: response.body.info.fullDisplayName, kind: response.body.info.kind, kindModifiers: response.body.info.kindModifiers, localizedErrorMessage: response.body.info.localizedErrorMessage, - triggerSpan: ts.createTextSpanFromBounds(position, position) + triggerSpan: ts.createTextSpanFromBounds(position, position), + fileName: fileName, + position: position, + findInStrings: findInStrings, + findInComments: findInComments, + locations: response.body.locs.map((entry) => { + var fileName = this.decodeEncodedFileId(entry.file); + var start = this.lineColToPosition(fileName, entry.start); + var end = this.lineColToPosition(fileName, entry.end); + return { + textSpan: ts.createTextSpanFromBounds(start, end), + fileName: fileName + }; + }) }; } findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.RenameRequestArgs = { - file: fileName, - line: lineCol.line, - col: lineCol.col, - findInStrings, - findInComments - }; - - var request = this.processRequest(CommandNames.Rename, args); - var response = this.processResponse(request); - - if (!response.body.info.canRename) { - return []; + if (!this.lastRenameEntry || + this.lastRenameEntry.fileName !== fileName || + this.lastRenameEntry.position !== position || + this.lastRenameEntry.findInStrings != findInStrings || + this.lastRenameEntry.findInComments != findInComments) { + this.getRenameInfo(fileName, position, findInStrings, findInComments); } - return response.body.locs.map((entry) => { - var start = this.lineColToPosition(entry.file, entry.start); - var end = this.lineColToPosition(entry.file, entry.end); - return { - textSpan: ts.createTextSpanFromBounds(start, end), - fileName: entry.file - }; - }); + return this.lastRenameEntry.locations; } decodeNavigationBarItems(items: ServerProtocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 04b3ce065b01b..386a477cf0f6d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -27,7 +27,7 @@ module ts.server { } interface FileStart { - file: string; + file: ServerProtocol.EncodedFile; start: ILineInfo; } @@ -92,9 +92,9 @@ module ts.server { locs: FileRange[]; } - function formatDiag(file: string, project: Project, diag: ts.Diagnostic) { + function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) { return { - start: project.compilerService.host.positionToLineCol(file, diag.start), + start: project.compilerService.host.positionToLineCol(fileName, diag.start), len: diag.length, text: diag.messageText, }; @@ -204,7 +204,7 @@ module ts.server { this.send(res); } - encodeFilename(fileName: string): ServerProtocol.EncodedFile { + encodeFileName(fileName: string): ServerProtocol.EncodedFile { var id = ts.lookUp(this.fileHash, fileName); if (!id) { id = this.nextFileId++; @@ -216,15 +216,15 @@ module ts.server { } } - output(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { - this.response(info, cmdName, reqSeq, errorMsg); + output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) { + this.response(body, commandName, requestSequence, errorMessage); } semanticCheck(file: string, project: Project) { var diags = project.compilerService.languageService.getSemanticDiagnostics(file); if (diags) { var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); - this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag"); + this.event({ file: this.encodeFileName(file), diagnostics: bakedDiags }, "semanticDiag"); } } @@ -232,7 +232,7 @@ module ts.server { var diags = project.compilerService.languageService.getSyntacticDiagnostics(file); if (diags) { var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); - this.event({ file: file, diagnostics: bakedDiags }, "syntaxDiag"); + this.event({ file: this.encodeFileName(file), diagnostics: bakedDiags }, "syntaxDiag"); } } @@ -293,11 +293,9 @@ module ts.server { } return definitions.map(def => ({ - file: def && def.fileName, - start: def && - compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), - end: def && - compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) + file: this.encodeFileName(def.fileName), + start: compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), + end: compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) })); } @@ -322,15 +320,15 @@ module ts.server { }; } - var renameLocs = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); - if (!renameLocs) { + var renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); + if (!renameLocations) { throw Errors.NoContent; } - var bakedRenameLocs = renameLocs.map(loc => ({ - file: loc.fileName, - start: compilerService.host.positionToLineCol(loc.fileName, loc.textSpan.start), - end: compilerService.host.positionToLineCol(loc.fileName, ts.textSpanEnd(loc.textSpan)), + var bakedRenameLocs = renameLocations.map(location => ({ + file: this.encodeFileName(location.fileName), + start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start), + end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)), })); return { info: renameInfo, locs: bakedRenameLocs }; @@ -368,7 +366,7 @@ module ts.server { var snap = compilerService.host.getScriptSnapshot(ref.fileName); var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); return { - file: ref.fileName, + file: this.encodeFileName(ref.fileName), start: start, lineText: lineText, end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), @@ -383,8 +381,8 @@ module ts.server { }; } - openClientFile(rawfile: string) { - var file = ts.normalizePath(rawfile); + openClientFile(fileName: string) { + var file = ts.normalizePath(fileName); this.projectService.openClientFile(file); } @@ -536,8 +534,8 @@ module ts.server { } } - change(line: number, col: number, deleteLen: number, insertString: string, rawfile: string) { - var file = ts.normalizePath(rawfile); + change(line: number, col: number, deleteLen: number, insertString: string, fileName: string) { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (project) { var compilerService = project.compilerService; @@ -553,9 +551,9 @@ module ts.server { } } - reload(rawfile: string, rawtmpfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); - var tmpfile = ts.normalizePath(rawtmpfile); + reload(fileName: string, tempFileName: string, reqSeq = 0) { + var file = ts.normalizePath(fileName); + var tmpfile = ts.normalizePath(tempFileName); var project = this.projectService.getProjectForFile(file); if (project) { this.changeSeq++; @@ -566,9 +564,9 @@ module ts.server { } } - saveToTmp(rawfile: string, rawtmpfile: string) { - var file = ts.normalizePath(rawfile); - var tmpfile = ts.normalizePath(rawtmpfile); + saveToTmp(fileName: string, tempFileName: string) { + var file = ts.normalizePath(fileName); + var tmpfile = ts.normalizePath(tempFileName); var project = this.projectService.getProjectForFile(file); if (project) { @@ -576,8 +574,8 @@ module ts.server { } } - closeClientFile(rawfile: string) { - var file = ts.normalizePath(rawfile); + closeClientFile(fileName: string) { + var file = ts.normalizePath(fileName); this.projectService.closeClientFile(file); } @@ -635,7 +633,7 @@ module ts.server { var bakedItem: ServerProtocol.NavtoItem = { name: navItem.name, kind: navItem.kind, - file: this.encodeFilename(navItem.fileName), + file: this.encodeFileName(navItem.fileName), start: start, end: end, }; diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index 12fd7163c1ad8..d2dcf4260ef27 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -101,8 +101,12 @@ declare module ServerProtocol { a specific source file. */ export interface CodeSpan extends TextSpan { - /** File containing the definition */ - file: string; + /** + File containing the definition; the value of this + field will always be a string, number of a mapping between + a string and a number. + */ + file: EncodedFile; } /** From 76c7fdf6d171af73be8f060457a56e2bcdd0f67f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 18:24:30 -0800 Subject: [PATCH 31/45] Add test for completionEntryDetails --- src/server/client.ts | 31 ++++++++++++++++---- src/server/protocol.ts | 2 +- tests/cases/fourslash/server/completions2.ts | 18 ++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/server/completions2.ts diff --git a/src/server/client.ts b/src/server/client.ts index 24666c8f4c799..0f06fc106af70 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -6,6 +6,11 @@ module ts.server { writeMessage(message: string): void; } + interface CompletionEntry extends CompletionInfo { + fileName: string; + position: number; + } + interface RenameEntry extends RenameInfo { fileName: string; position: number; @@ -19,6 +24,7 @@ module ts.server { private fileMapping: ts.Map = {}; private lineMaps: ts.Map = {}; private messages: string[] = []; + private lastCompletionEntry: CompletionEntry; private lastRenameEntry: RenameEntry; constructor(private host: SessionClientHost) { @@ -195,13 +201,30 @@ module ts.server { var request = this.processRequest(CommandNames.Completions, args); var response = this.processResponse(request); - return { + return this.lastCompletionEntry = { isMemberCompletion: false, isNewIdentifierLocation: false, - entries: response.body.map(entry => ({ kind: entry.kind, kindModifiers: entry.kindModifiers, name: entry.name })) + entries: response.body.map(entry => ({ + kind: entry.kind, + kindModifiers: entry.kindModifiers, + name: entry.name, + displayParts: entry.displayParts, + documentation: entry.documentation + })), + fileName: fileName, + position: position }; } + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { + debugger; + if (!this.lastCompletionEntry || this.lastCompletionEntry.fileName !== fileName || this.lastCompletionEntry.position !== position) { + this.getCompletionsAtPosition(fileName, position); + } + + return this.lastCompletionEntry.entries.filter(entry => entry.name === entryName)[0]; + } + getNavigateToItems(searchTerm: string): NavigateToItem[] { var args: ServerProtocol.NavtoRequestArgs = { searchTerm, @@ -466,10 +489,6 @@ module ts.server { throw new Error("Not Implemented Yet."); } - getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { - throw new Error("Not Implemented Yet."); - } - getProgram(): Program { throw new Error("SourceFile objects are not serializable through the server protocol."); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 386a477cf0f6d..0b62e86d16e59 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -511,7 +511,7 @@ module ts.server { protoEntry.documentation = details.documentation; } if (details && (details.displayParts) && (details.displayParts.length > 0)) { - protoEntry.displayParts = details.documentation; + protoEntry.displayParts = details.displayParts; } result.push(protoEntry); } diff --git a/tests/cases/fourslash/server/completions2.ts b/tests/cases/fourslash/server/completions2.ts new file mode 100644 index 0000000000000..b34aad66ba264 --- /dev/null +++ b/tests/cases/fourslash/server/completions2.ts @@ -0,0 +1,18 @@ +/// + +////class Foo { +////} +////module Foo { +//// export var x: number; +////} +////Foo./**/ + +goTo.marker(""); +verify.completionListContains("x"); + +// Make an edit +edit.insert("a"); +edit.backspace(); + +// Checking for completion details after edit should work too +verify.completionEntryDetailIs("x", "(var) Foo.x: number"); From d9d2e994950b614f8afc91794879155464038891 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 18:43:39 -0800 Subject: [PATCH 32/45] Remove debugging statemetns --- src/server/client.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 0f06fc106af70..8ff881611d3b8 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -99,8 +99,6 @@ module ts.server { } private processResponse(request: ServerProtocol.Request): T { - debugger; - var lastMessage = this.messages.shift(); Debug.assert(!!lastMessage, "Did not recieve any responses."); @@ -217,7 +215,6 @@ module ts.server { } getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { - debugger; if (!this.lastCompletionEntry || this.lastCompletionEntry.fileName !== fileName || this.lastCompletionEntry.position !== position) { this.getCompletionsAtPosition(fileName, position); } From f5c1bfbca7618e8638f447e1679dbac6e11959e5 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 18:49:22 -0800 Subject: [PATCH 33/45] renmae protodef.d.ts to protocol.d.ts and protocol.ts to session.ts --- Jakefile | 6 ++++-- src/harness/harness.ts | 2 +- src/server/client.ts | 2 +- src/server/{protodef.d.ts => protocol.d.ts} | 0 src/server/server.ts | 2 +- src/server/{protocol.ts => session.ts} | 10 +++++----- 6 files changed, 12 insertions(+), 10 deletions(-) rename src/server/{protodef.d.ts => protocol.d.ts} (100%) rename src/server/{protocol.ts => session.ts} (97%) diff --git a/Jakefile b/Jakefile index acd81b0ccd5ec..bb57469ac3354 100644 --- a/Jakefile +++ b/Jakefile @@ -94,7 +94,8 @@ var servicesSources = [ var serverSources = [ "node.d.ts", "editorServices.ts", - "protocol.ts", + "protocol.d.ts", + "session.ts", "server.ts" ].map(function (f) { return path.join(serverDirectory, f); @@ -141,7 +142,8 @@ var harnessSources = [ ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ - "protocol.ts", + "protocol.d.ts", + "session.ts", "client.ts", "editorServices.ts", ].map(function (f) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index cbc4064119a65..8ee51f89c9c55 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -16,7 +16,7 @@ /// /// -/// +/// /// /// /// diff --git a/src/server/client.ts b/src/server/client.ts index 8ff881611d3b8..9da5ed9fdbd97 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -1,4 +1,4 @@ -/// +/// module ts.server { diff --git a/src/server/protodef.d.ts b/src/server/protocol.d.ts similarity index 100% rename from src/server/protodef.d.ts rename to src/server/protocol.d.ts diff --git a/src/server/server.ts b/src/server/server.ts index 6d210ff0d13ce..8921a4b476824 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,5 +1,5 @@ /// -/// +/// module ts.server { var nodeproto: typeof NodeJS._debugger = require('_debugger'); diff --git a/src/server/protocol.ts b/src/server/session.ts similarity index 97% rename from src/server/protocol.ts rename to src/server/session.ts index 0b62e86d16e59..715b8fa3d6aa7 100644 --- a/src/server/protocol.ts +++ b/src/server/session.ts @@ -1,7 +1,7 @@ /// /// /// -/// +/// /// module ts.server { @@ -587,13 +587,13 @@ module ts.server { var compilerService = project.compilerService; return items.map(item => ({ - text: item.text, - kind: item.kind, - kindModifiers: item.kindModifiers, + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers, spans: item.spans.map(span => ({ start: compilerService.host.positionToLineCol(fileName, span.start), end: compilerService.host.positionToLineCol(fileName, ts.textSpanEnd(span)) - })), + })), childItems: this.decorateNavigationBarItem(project, fileName, item.childItems) })); } From d081c9c463b742e79a3eb5d2d493e126f70ae92b Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 18:52:17 -0800 Subject: [PATCH 34/45] rename output file to tsserver.js --- Jakefile | 2 +- bin/tsserver | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jakefile b/Jakefile index bb57469ac3354..07b0d267a6895 100644 --- a/Jakefile +++ b/Jakefile @@ -400,7 +400,7 @@ compileFile(nodeDefinitionsFile, servicesSources,[builtLocalDirectory, copyright jake.rmRf(tempDirPath, {silent: true}); }); -var serverFile = path.join(builtLocalDirectory, "typescriptServer.js"); +var serverFile = path.join(builtLocalDirectory, "tsserver.js"); compileFile(serverFile, serverSources,[builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true); // Local target to build the compiler and services diff --git a/bin/tsserver b/bin/tsserver index 2327c344c44c3..003eb0d22af9c 100644 --- a/bin/tsserver +++ b/bin/tsserver @@ -1,2 +1,2 @@ #!/usr/bin/env node -require('./typescriptServer.js') +require('./tsserver.js') From ca348385354d563014b7b7b56c76c65750808f6c Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 19:13:52 -0800 Subject: [PATCH 35/45] Remove unused code --- src/server/editorServices.ts | 61 +----------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9e6363b509fcc..4f23e9a7c14f2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -12,7 +12,6 @@ module ts.server { msg(s: string, type?: string): void; } - var measurePerf = false; var lineCollectionCapacity = 4; var indentStrings: string[] = []; var indentBase = " "; @@ -26,37 +25,6 @@ module ts.server { return indentStrings[indentAmt]; } - export function printLine(s: string) { - ts.sys.write(s + '\n'); - } - - function showLines(s: string) { - var strBuilder = ""; - for (var i = 0, len = s.length; i < len; i++) { - if (s.charCodeAt(i) == 10) { - strBuilder += '\\n'; - } - else if (s.charCodeAt(i) == 13) { - strBuilder += '\\r'; - } - else { - strBuilder += s.charAt(i); - } - } - return strBuilder; - } - - function calibrateTimer() { - var count = 20; - var total = 0; - for (var i = 0; i < count; i++) { - var start = process.hrtime(); - var elapsed = process.hrtime(start); - var elapsedNano = 1e9 * elapsed[0] + elapsed[1]; - total += elapsedNano; - } - } - export class ScriptInfo { svc: ScriptVersionCache; children: ScriptInfo[] = []; // files referenced by this file @@ -449,10 +417,7 @@ module ts.server { inferredProjects: Project[] = []; constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { - if (measurePerf) { - calibrateTimer(); - } - ts.disableIncrementalParsing = true; + // ts.disableIncrementalParsing = true; } watchedFileChanged(fileName: string) { @@ -820,7 +785,6 @@ module ts.server { lineCount(): number; isLeaf(): boolean; walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void; - print(indentAmt: number): void; } export interface ILineInfo { @@ -1253,12 +1217,6 @@ module ts.server { } } - print() { - printLine("index TC " + this.root.charCount() + " TL " + this.root.lineCount()); - this.root.print(0); - printLine(""); - } - load(lines: string[]) { if (lines.length > 0) { var leaves: LineLeaf[] = []; @@ -1424,15 +1382,6 @@ module ts.server { return false; } - print(indentAmt: number) { - var strBuilder = getIndent(indentAmt); - strBuilder += ("node ch " + this.children.length + " TC " + this.totalChars + " TL " + this.totalLines + " :"); - printLine(strBuilder); - for (var ch = 0, clen = this.children.length; ch < clen; ch++) { - this.children[ch].print(indentAmt + 1); - } - } - updateCounts() { this.totalChars = 0; this.totalLines = 0; @@ -1492,9 +1441,6 @@ module ts.server { } var adjustedLength = rangeLength - (childCharCount - adjustedStart); child = this.children[++childIndex]; - if (!child) { - this.print(2); - } childCharCount = child.charCount(); while (adjustedLength > childCharCount) { if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) { @@ -1743,10 +1689,5 @@ module ts.server { lineCount() { return 1; } - - print(indentAmt: number) { - var strBuilder = getIndent(indentAmt); - printLine(strBuilder + showLines(this.text)); - } } } \ No newline at end of file From 8a9ac8d33802b8105b68fc8866fd3b760b590c38 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 19:14:33 -0800 Subject: [PATCH 36/45] Change ServerProtocol module to ts.server.protocol --- src/server/client.ts | 86 +-- src/server/protocol.d.ts | 1192 +++++++++++++++++++------------------- src/server/session.ts | 68 +-- 3 files changed, 670 insertions(+), 676 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 9da5ed9fdbd97..22b346aa3ec6a 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -47,11 +47,11 @@ module ts.server { return lineMap; } - private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { + private lineColToPosition(fileName: string, lineCol: ts.server.protocol.LineCol): number { return ts.computePositionFromLineAndCharacter(this.getLineMap(fileName), lineCol.line, lineCol.col); } - private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { + private positionToOneBasedLineCol(fileName: string, position: number): ts.server.protocol.LineCol { var lineCol = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); return { line: lineCol.line, @@ -59,7 +59,7 @@ module ts.server { }; } - private convertCodeEditsToTextChange(fileName: string, codeEdit: ServerProtocol.CodeEdit): ts.TextChange { + private convertCodeEditsToTextChange(fileName: string, codeEdit: ts.server.protocol.CodeEdit): ts.TextChange { var start = this.lineColToPosition(fileName, codeEdit.start); var end = this.lineColToPosition(fileName, codeEdit.end); @@ -69,11 +69,11 @@ module ts.server { }; } - private decodeEncodedFileId(fileId: ServerProtocol.EncodedFile): string { + private decodeEncodedFileId(fileId: ts.server.protocol.EncodedFile): string { var fileName: string; if (typeof fileId === "object") { - fileName = (fileId).file; - this.fileMapping[(fileId).id] = fileName; + fileName = (fileId).file; + this.fileMapping[(fileId).id] = fileName; } else if (typeof fileId === "number") { fileName = ts.lookUp(this.fileMapping, fileId.toString()); @@ -85,8 +85,8 @@ module ts.server { return fileName; } - private processRequest(command: string, arguments?: any): T { - var request: ServerProtocol.Request = { + private processRequest(command: string, arguments?: any): T { + var request: ts.server.protocol.Request = { seq: this.sequence++, type: "request", command: command, @@ -98,7 +98,7 @@ module ts.server { return request; } - private processResponse(request: ServerProtocol.Request): T { + private processResponse(request: ts.server.protocol.Request): T { var lastMessage = this.messages.shift(); Debug.assert(!!lastMessage, "Did not recieve any responses."); @@ -138,12 +138,12 @@ module ts.server { } openFile(fileName: string): void { - var args: ServerProtocol.FileRequestArgs = { file: fileName }; + var args: ts.server.protocol.FileRequestArgs = { file: fileName }; this.processRequest(CommandNames.Open, args); } closeFile(fileName: string): void { - var args: ServerProtocol.FileRequestArgs = { file: fileName }; + var args: ts.server.protocol.FileRequestArgs = { file: fileName }; this.processRequest(CommandNames.Close, args); } @@ -152,7 +152,7 @@ module ts.server { this.lineMaps[fileName] = undefined; var lineCol = this.positionToOneBasedLineCol(fileName, start); - var args: ServerProtocol.ChangeRequestArgs = { + var args: ts.server.protocol.ChangeRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, @@ -166,14 +166,14 @@ module ts.server { getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CodeLocationRequestArgs = { + var args: ts.server.protocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col }; - var request = this.processRequest(CommandNames.Quickinfo, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Quickinfo, args); + var response = this.processResponse(request); var start = this.lineColToPosition(fileName, response.body.start); var end = this.lineColToPosition(fileName, response.body.end); @@ -189,15 +189,15 @@ module ts.server { getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CompletionsRequestArgs = { + var args: ts.server.protocol.CompletionsRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, prefix: undefined }; - var request = this.processRequest(CommandNames.Completions, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Completions, args); + var response = this.processResponse(request); return this.lastCompletionEntry = { isMemberCompletion: false, @@ -223,13 +223,13 @@ module ts.server { } getNavigateToItems(searchTerm: string): NavigateToItem[] { - var args: ServerProtocol.NavtoRequestArgs = { + var args: ts.server.protocol.NavtoRequestArgs = { searchTerm, file: this.host.getScriptFileNames()[0] }; - var request = this.processRequest(CommandNames.Navto, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Navto, args); + var response = this.processResponse(request); return response.body.map(entry => { var fileName = this.decodeEncodedFileId(entry.file); @@ -252,7 +252,7 @@ module ts.server { getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { var startLineCol = this.positionToOneBasedLineCol(fileName, start); var endLineCol = this.positionToOneBasedLineCol(fileName, end); - var args: ServerProtocol.FormatRequestArgs = { + var args: ts.server.protocol.FormatRequestArgs = { file: fileName, line: startLineCol.line, col: startLineCol.col, @@ -261,8 +261,8 @@ module ts.server { }; // TODO: handle FormatCodeOptions - var request = this.processRequest(CommandNames.Format, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Format, args); + var response = this.processResponse(request); return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); } @@ -273,7 +273,7 @@ module ts.server { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.FormatOnKeyRequestArgs = { + var args: ts.server.protocol.FormatOnKeyRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, @@ -281,22 +281,22 @@ module ts.server { }; // TODO: handle FormatCodeOptions - var request = this.processRequest(CommandNames.Formatonkey, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Formatonkey, args); + var response = this.processResponse(request); return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); } getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CodeLocationRequestArgs = { + var args: ts.server.protocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.Definition, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Definition, args); + var response = this.processResponse(request); return response.body.map(entry => { var fileName = this.decodeEncodedFileId(entry.file); @@ -315,14 +315,14 @@ module ts.server { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CodeLocationRequestArgs = { + var args: ts.server.protocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.References, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.References, args); + var response = this.processResponse(request); return response.body.refs.map(entry => { var fileName = this.decodeEncodedFileId(entry.file); @@ -354,7 +354,7 @@ module ts.server { getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.RenameRequestArgs = { + var args: ts.server.protocol.RenameRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, @@ -362,8 +362,8 @@ module ts.server { findInComments }; - var request = this.processRequest(CommandNames.Rename, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Rename, args); + var response = this.processResponse(request); return this.lastRenameEntry = { canRename: response.body.info.canRename, @@ -401,7 +401,7 @@ module ts.server { return this.lastRenameEntry.locations; } - decodeNavigationBarItems(items: ServerProtocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { + decodeNavigationBarItems(items: ts.server.protocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { if (!items) { return []; } @@ -419,12 +419,12 @@ module ts.server { } getNavigationBarItems(fileName: string): NavigationBarItem[] { - var args: ServerProtocol.FileRequestArgs = { + var args: ts.server.protocol.FileRequestArgs = { file: fileName }; - var request = this.processRequest(CommandNames.NavBar, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.NavBar, args); + var response = this.processResponse(request); return this.decodeNavigationBarItems(response.body, fileName); } @@ -455,14 +455,14 @@ module ts.server { getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ServerProtocol.CodeLocationRequestArgs = { + var args: ts.server.protocol.CodeLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.Brace, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Brace, args); + var response = this.processResponse(request); return response.body.map(entry => { var start = this.lineColToPosition(fileName, entry.start); diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index d2dcf4260ef27..b1eef8f1b7f49 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -1,603 +1,597 @@ -/** Declaration module describing the TypeScript Server protocol */ -declare module ServerProtocol { - /** A TypeScript Server message */ - export interface Message { - /** Sequence number of the message */ - seq: number; - /** One of "request", "response", or "event" */ - type: string; - } - - /** Client-initiated request message */ - export interface Request extends Message { - /** The command to execute */ - command: string; - /** Object containing arguments for the command */ - arguments?: any; - } - - /** Server-initiated event message */ - export interface Event extends Message { - /** Name of event */ - event: string; - /** Event-specific information */ - body?: any; - } - - /** Response by server to client request message */ - export interface Response extends Message { - /** Sequence number of the request message */ - request_seq: number; - /** Outcome of the request */ - success: boolean; - /** The command requested */ - command: string; - /** Contains error message if success == false. */ - message?: string; - /** Contains message body if success == true. */ - body?: any; - } - - /** Arguments for FileRequest messages */ - export interface FileRequestArgs { - /** The file for the request (absolute pathname required) */ - file: string; - } - - /** - Request whose sole parameter is a file name - */ - export interface FileRequest extends Request { - arguments: FileRequestArgs; - } - - /** - Instances of this interface specify a code location: - (file, line, col), where line and column are 1-based. - */ - export interface CodeLocationRequestArgs extends FileRequestArgs { - /** The line number for the request (1-based) */ - line: number; - /** The column for the request (1-based) */ - col: number; - } - - /** - A request whose arguments specify a code location (file, line, col) - */ - export interface CodeLocationRequest extends FileRequest { - arguments: CodeLocationRequestArgs; - } - - /** - Go to definition request; value of command field is - "definition". Return response giving the code locations that - define the symbol found in file at location line, col. - */ - export interface DefinitionRequest extends CodeLocationRequest { - } - - /** - Object containing line and column (one-based) of code location - */ - export interface LineCol { - line: number; - col: number; - } - - /** - Object found in response messages defining a span of text in - source code. - */ - export interface TextSpan { - /** First character of the definition */ - start: LineCol; - /** One character past last character of the definition */ - end: LineCol; - } - - /** - Object found in response messages defining a span of text in - a specific source file. - */ - export interface CodeSpan extends TextSpan { - /** - File containing the definition; the value of this - field will always be a string, number of a mapping between - a string and a number. - */ - file: EncodedFile; - } - - /** - Definition response message. Gives text range for definition. - */ - export interface DefinitionResponse extends Response { - body?: CodeSpan[]; - } - - /** - Find references request; value of command field is - "references". Return response giving the code locations that - reference the symbol found in file at location line, col. - */ - export interface ReferencesRequest extends CodeLocationRequest { - } - - export interface ReferencesResponseItem extends CodeSpan { - /** Text of line containing the reference. Including this - with the response avoids latency of editor loading files - to show text of reference line (the server already has - loaded the referencing files). - */ - lineText: string; - - /** True if reference is a write location, false otherwise. */ - isWriteAccess: boolean; - } - - /** The body of a "references" response message. */ - export interface ReferencesResponseBody { - /** The code locations referencing the symbol */ - refs: ReferencesResponseItem[]; - /** The name of the symbol */ - symbolName: string; - /** - The start column of the symbol (on the line provided by the references request) - */ - symbolStartCol: number; - /** The full display name of the symbol */ - symbolDisplayString: string; - } - - /** Response to "references" request. */ - export interface ReferencesResponse extends Response { - body?: ReferencesResponseBody; - } - - export interface RenameRequestArgs extends CodeLocationRequestArgs { - findInComments?: boolean; - findInStrings?: boolean; - } - - - /** - Rename request; value of command field is "rename". Return - response giving the code locations that reference the symbol - found in file at location line, col. Also return full display - name of the symbol so that client can print it unambiguously. - */ - export interface RenameRequest extends CodeLocationRequest { - arguments: RenameRequestArgs; - } - - /** Information about the item to be renamed. */ - export interface RenameInfo { - /** True if item can be renamed */ - canRename: boolean; - /** Error message if item can not be renamed */ - localizedErrorMessage: string; - /** Display name of the item to be renamed */ - displayName: string; - /** Full display name of item to be renamed */ - fullDisplayName: string; - /** The items's kind (such as 'className' or 'parameterName' or plain 'text') */ - kind: string; - /** Optional modifiers for the kind (such as 'public') */ - kindModifiers: string; - } - - /** - Rename response message. - */ - export interface RenameResponse extends Response { - body?: { - /** Information about the item to be renamed */ - info: RenameInfo; - /** - An array of code locations that refer to the - item to be renamed. - */ - locs: CodeSpan[]; - } - } - - /** - Open request; value of command field is "open". Notify the - server that the client has file open. The server will not - monitor the filesystem for changes in this file and will assume - that the client is updating the server (using the change and/or - reload messages) when the file changes. Server does not currently - send a response to an open request. - */ - export interface OpenRequest extends FileRequest { - } - - /** - Close request; value of command field is "close". Notify the - server that the client has closed a previously open file. If - file is still referenced by open files, the server will resume - monitoring the filesystem for changes to file. Server does not - currently send a response to a close request. - */ - export interface CloseRequest extends FileRequest { - } - - /** - Quickinfo request; value of command field is - "quickinfo". Return response giving a quick type and - documentation string for the symbol found in file at location - line, col. - */ - export interface QuickInfoRequest extends CodeLocationRequest { - } - - /** Body of QuickInfoResponse */ - export interface QuickInfoResponseBody { - /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ - kind: string; - /** Optional modifiers for the kind (such as 'public') */ - kindModifiers: string; - /** Starting code location of symbol */ - start: LineCol; - /** One past last character of symbol */ - end: LineCol; - /** Type and kind of symbol */ - displayString: string; - /** Documentation associated with symbol */ - documentation: string; - } - - /** Quickinfo response message */ - export interface QuickInfoResponse extends Response { - body?: QuickInfoResponseBody; - } - - /** Arguments for format messages */ - export interface FormatRequestArgs extends CodeLocationRequestArgs { - /** Last line of range for which to format text in file */ - endLine: number; - /** Last column of range for which to format text in file */ - endCol: number; - } - - /** - Format request; value of command field is "format". Return - response giving zero or more edit instructions. The edit - instructions will be sorted in file order. Applying the edit - instructions in reverse to file will result in correctly - reformatted text. - */ - export interface FormatRequest extends CodeLocationRequest { - arguments: FormatRequestArgs; - } - - /** - Object found in response messages defining an editing - instruction for a span of text in source code. The effect of - this instruction is to replace the text starting at start and - ending one character before end with newText. For an insertion, - the text span is empty. For a deletion, newText is empty. - */ - export interface CodeEdit { - /** First character of the text span to edit. */ - start: LineCol; - /** One character past last character of the text span to edit */ - end: LineCol; - /** - Replace the span defined above with this string (may be - the empty string) - */ - newText: string; - } - - /** Format and format on key response message */ - export interface FormatResponse extends Response { - body?: CodeEdit[]; - } - - /** Arguments for format on key messages */ - export interface FormatOnKeyRequestArgs extends CodeLocationRequestArgs { - /** Key pressed (';', '\n', or '}') */ - key: string; - } - - /** - Format on key request; value of command field is - "formatonkey". Given file location and key typed (as string), - return response giving zero or more edit instructions. The - edit instructions will be sorted in file order. Applying the - edit instructions in reverse to file will result in correctly - reformatted text. - */ - export interface FormatOnKeyRequest extends CodeLocationRequest { - arguments: FormatOnKeyRequestArgs; - } - - /** Arguments for completions messages */ - export interface CompletionsRequestArgs extends CodeLocationRequestArgs { - /** Optional prefix to apply to possible completions. */ - prefix?: string; - } - - /** - Completions request; value of command field is "completions". - Given a file location (file, line, col) and a prefix (which may - be the empty string), return the possible completions that - begin with prefix. - */ - export interface CompletionsRequest extends CodeLocationRequest { - arguments: CompletionsRequestArgs; - } - - /** - Part of a symbol description. - */ - export interface SymbolDisplayPart { - /** Text of an item describing the symbol */ - text: string; - /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ - kind: string; - } - - /** An item found in a completion response */ - export interface CompletionItem { - /** The symbol's name */ - name: string; - /** The symbol's kind (such as 'className' or 'parameterName') */ - kind: string; - /** Optional modifiers for the kind (such as 'public') */ - kindModifiers?: string; - /** Display parts of the symbol (similar to quick info) */ - displayParts?: SymbolDisplayPart[]; - /** Documentation strings for the symbol */ - documentation?: SymbolDisplayPart[]; - } - - export interface CompletionsResponse extends Response { - body?: CompletionItem[]; - } - - /** Arguments for geterr messages. */ - export interface GeterrRequestArgs { - /** - List of file names for which to compute compiler errors. - The files will be checked in list order. - */ - files: string[]; - /** - Delay in milliseconds to wait before starting to compute - errors for the files in the file list - */ - delay: number; - } - - /** - Geterr request; value of command field is "geterr". Wait for - delay milliseconds and then, if during the wait no change or - reload messages have arrived for the first file in the files - list, get the syntactic errors for the file, field requests, - and then get the semantic errors for the file. Repeat with a - smaller delay for each subsequent file on the files list. Best - practice for an editor is to send a file list containing each - file that is currently visible, in most-recently-used order. - */ - export interface GeterrRequest extends Request { - arguments: GeterrRequestArgs; - } - - /** Item of diagnostic information found in a DiagEvent message */ - export interface Diagnostic { - /** Starting code location at which text appies */ - start: LineCol; - /** Length of code location at which text applies */ - len: number; - /** Text of diagnostic message */ - text: string; - } - - /** Event message for "syntaxDiag" and "semanticDiag" event types. - These events provide syntactic and semantic errors for a file. - */ - export interface DiagEvent extends Event { - body?: { - /** The file for which diagnostic information is reported */ - file: string; - /** An array of diagnostic information items */ - diagnostics: Diagnostic[]; - }; - } - - /** Arguments for reload request. */ - export interface ReloadRequestArgs extends FileRequestArgs { - /** - Name of temporary file from which to reload file - contents. May be same as file. - */ - tmpfile: string; - } - - /** - Reload request message; value of command field is "reload". - Reload contents of file with name given by the 'file' argument - from temporary file with name given by the 'tmpfile' argument. - The two names can be identical. - */ - export interface ReloadRequest extends FileRequest { - arguments: ReloadRequestArgs; - } - - /** - Response to "reload" request. This is just an acknowledgement, so - no body field is required. - */ - export interface ReloadResponse extends Response { - } - - /** Arguments for saveto request. */ - export interface SavetoRequestArgs extends FileRequestArgs { - /** - Name of temporary file into which to save server's view of - file contents. - */ - tmpfile: string; - } - - /** - Saveto request message; value of command field is "saveto". - For debugging purposes, save to a temporaryfile (named by - argument 'tmpfile') the contents of file named by argument - 'file'. The server does not currently send a response to a - "saveto" request. - */ - export interface SavetoRequest extends FileRequest { - arguments: SavetoRequestArgs; - } - - /** Arguments for navto request message */ - export interface NavtoRequestArgs extends FileRequestArgs { - /** - Search term to navigate to from current location; term can - be '.*' or an identifier prefix. - */ - searchTerm: string; - } - - /** - Navto request message; value of command field is "navto". - Return list of objects giving code locations and symbols that - match the search term given in argument 'searchTerm'. The - context for the search is given by the named file. - */ - export interface NavtoRequest extends FileRequest { - arguments: NavtoRequestArgs; - } - - /** An item found in a navto response. */ - export interface NavtoItem { - /** The symbol's name */ - name: string; - /** The symbol's kind (such as 'className' or 'parameterName') */ - kind: string; - /** exact, substring, or prefix */ - matchKind?: string; - /** Optional modifiers for the kind (such as 'public') */ - kindModifiers?: string; - /** - The file in which the symbol is found; the value of this - field will always be a string, number of a mapping between - a string and a number. - */ - file: EncodedFile; - /** The location within file at which the symbol is found*/ - start: LineCol; - /** One past the last character of the symbol */ - end: LineCol; - /** - Name of symbol's container symbol (if any); for example, - the class name if symbol is a class member - */ - containerName?: string; - /** Kind of symbol's container symbol (if any) */ - containerKind?: string; - } - - /** - Navto response message. Body is an array of navto items. Each - item gives a symbol that matched the search term. - */ - export interface NavtoResponse extends Response { - body?: NavtoItem[]; - } - - /** Arguments for change request message. */ - export interface ChangeRequestArgs extends CodeLocationRequestArgs { - /** - Length of span deleted at location (file, line, col); nothing deleted - if this field is zero or undefined. - */ - deleteLen?: number; - /** Optional string to insert at location (file, line col). */ - insertString?: string; - } - - /** - Change request message; value of command field is "change". - Update the server's view of the file named by argument 'file'. - Server does not currently send a response to a change request. - */ - export interface ChangeRequest extends CodeLocationRequest { - arguments: ChangeRequestArgs; - } - - /** - If an object of this type is returned in place of a string as - the value of a file field in a response message, add the - mapping id => file to the client's cache of file id mappings, - and interpret the value as if it was the string in the 'file' - field. - */ - export interface IdFile { - /** Id to assign to file */ - id: number; - /** File name that will correspond to id */ - file: string; - } - - /** - The type of an encoded file name. If of type number, the value - is a file id. If of type IdFile, the value is interpreted as - 'file' and in addition the mapping 'id' to 'file' is - established. If of type string, the value is simply the file - name. - */ - export type EncodedFile = number | IdFile | string; - - /** Response to "brace" request. */ - export interface BraceResponse extends Response { - body?: TextSpan[]; - } - - /** - Brace matching request; value of command field is "brace". - Return response giving the code locations of matching braces - found in file at location line, col. - */ - export interface BraceRequest extends CodeLocationRequest { - } - - /** - NavBar itesm request; value of command field is "navbar". - Return response giving the list of navigation bar entries - extracted from the requested file. - */ - export interface NavBarRequest extends FileRequest { - } - +/** Declaration module describing the TypeScript Server protocol */ +declare module ts.server.protocol { + /** A TypeScript Server message */ + export interface Message { + /** Sequence number of the message */ + seq: number; + /** One of "request", "response", or "event" */ + type: string; + } + + /** Client-initiated request message */ + export interface Request extends Message { + /** The command to execute */ + command: string; + /** Object containing arguments for the command */ + arguments?: any; + } + + /** Server-initiated event message */ + export interface Event extends Message { + /** Name of event */ + event: string; + /** Event-specific information */ + body?: any; + } + + /** Response by server to client request message */ + export interface Response extends Message { + /** Sequence number of the request message */ + request_seq: number; + /** Outcome of the request */ + success: boolean; + /** The command requested */ + command: string; + /** Contains error message if success == false. */ + message?: string; + /** Contains message body if success == true. */ + body?: any; + } + + /** Arguments for FileRequest messages */ + export interface FileRequestArgs { + /** The file for the request (absolute pathname required) */ + file: string; + } + + /** + Request whose sole parameter is a file name + */ + export interface FileRequest extends Request { + arguments: FileRequestArgs; + } + + /** + Instances of this interface specify a code location: + (file, line, col), where line and column are 1-based. + */ + export interface CodeLocationRequestArgs extends FileRequestArgs { + /** The line number for the request (1-based) */ + line: number; + /** The column for the request (1-based) */ + col: number; + } + + /** + A request whose arguments specify a code location (file, line, col) + */ + export interface CodeLocationRequest extends FileRequest { + arguments: CodeLocationRequestArgs; + } + + /** + Go to definition request; value of command field is + "definition". Return response giving the code locations that + define the symbol found in file at location line, col. + */ + export interface DefinitionRequest extends CodeLocationRequest { + } + + /** + Object containing line and column (one-based) of code location + */ + export interface LineCol { + line: number; + col: number; + } + + /** + Object found in response messages defining a span of text in + source code. + */ + export interface TextSpan { + /** First character of the definition */ + start: LineCol; + /** One character past last character of the definition */ + end: LineCol; + } + + /** + Object found in response messages defining a span of text in + a specific source file. + */ + export interface CodeSpan extends TextSpan { + /** + File containing the definition; the value of this + field will always be a string, number of a mapping between + a string and a number. + */ + file: EncodedFile; + } + + /** + Definition response message. Gives text range for definition. + */ + export interface DefinitionResponse extends Response { + body?: CodeSpan[]; + } + + /** + Find references request; value of command field is + "references". Return response giving the code locations that + reference the symbol found in file at location line, col. + */ + export interface ReferencesRequest extends CodeLocationRequest { + } + + export interface ReferencesResponseItem extends CodeSpan { + /** Text of line containing the reference. Including this + with the response avoids latency of editor loading files + to show text of reference line (the server already has + loaded the referencing files). + */ + lineText: string; + + /** True if reference is a write location, false otherwise. */ + isWriteAccess: boolean; + } + + /** The body of a "references" response message. */ + export interface ReferencesResponseBody { + /** The code locations referencing the symbol */ + refs: ReferencesResponseItem[]; + /** The name of the symbol */ + symbolName: string; + /** + The start column of the symbol (on the line provided by the references request) + */ + symbolStartCol: number; + /** The full display name of the symbol */ + symbolDisplayString: string; + } + + /** Response to "references" request. */ + export interface ReferencesResponse extends Response { + body?: ReferencesResponseBody; + } + + export interface RenameRequestArgs extends CodeLocationRequestArgs { + findInComments?: boolean; + findInStrings?: boolean; + } + + + /** + Rename request; value of command field is "rename". Return + response giving the code locations that reference the symbol + found in file at location line, col. Also return full display + name of the symbol so that client can print it unambiguously. + */ + export interface RenameRequest extends CodeLocationRequest { + arguments: RenameRequestArgs; + } + + /** Information about the item to be renamed. */ + export interface RenameInfo { + /** True if item can be renamed */ + canRename: boolean; + /** Error message if item can not be renamed */ + localizedErrorMessage: string; + /** Display name of the item to be renamed */ + displayName: string; + /** Full display name of item to be renamed */ + fullDisplayName: string; + /** The items's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers: string; + } + + /** + Rename response message. + */ + export interface RenameResponse extends Response { + body?: { + /** Information about the item to be renamed */ + info: RenameInfo; + /** + An array of code locations that refer to the + item to be renamed. + */ + locs: CodeSpan[]; + } + } + + /** + Open request; value of command field is "open". Notify the + server that the client has file open. The server will not + monitor the filesystem for changes in this file and will assume + that the client is updating the server (using the change and/or + reload messages) when the file changes. Server does not currently + send a response to an open request. + */ + export interface OpenRequest extends FileRequest { + } + + /** + Close request; value of command field is "close". Notify the + server that the client has closed a previously open file. If + file is still referenced by open files, the server will resume + monitoring the filesystem for changes to file. Server does not + currently send a response to a close request. + */ + export interface CloseRequest extends FileRequest { + } + + /** + Quickinfo request; value of command field is + "quickinfo". Return response giving a quick type and + documentation string for the symbol found in file at location + line, col. + */ + export interface QuickInfoRequest extends CodeLocationRequest { + } + + /** Body of QuickInfoResponse */ + export interface QuickInfoResponseBody { + /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers: string; + /** Starting code location of symbol */ + start: LineCol; + /** One past last character of symbol */ + end: LineCol; + /** Type and kind of symbol */ + displayString: string; + /** Documentation associated with symbol */ + documentation: string; + } + + /** Quickinfo response message */ + export interface QuickInfoResponse extends Response { + body?: QuickInfoResponseBody; + } + + /** Arguments for format messages */ + export interface FormatRequestArgs extends CodeLocationRequestArgs { + /** Last line of range for which to format text in file */ + endLine: number; + /** Last column of range for which to format text in file */ + endCol: number; + } + + /** + Format request; value of command field is "format". Return + response giving zero or more edit instructions. The edit + instructions will be sorted in file order. Applying the edit + instructions in reverse to file will result in correctly + reformatted text. + */ + export interface FormatRequest extends CodeLocationRequest { + arguments: FormatRequestArgs; + } + + /** + Object found in response messages defining an editing + instruction for a span of text in source code. The effect of + this instruction is to replace the text starting at start and + ending one character before end with newText. For an insertion, + the text span is empty. For a deletion, newText is empty. + */ + export interface CodeEdit { + /** First character of the text span to edit. */ + start: LineCol; + /** One character past last character of the text span to edit */ + end: LineCol; + /** + Replace the span defined above with this string (may be + the empty string) + */ + newText: string; + } + + /** Format and format on key response message */ + export interface FormatResponse extends Response { + body?: CodeEdit[]; + } + + /** Arguments for format on key messages */ + export interface FormatOnKeyRequestArgs extends CodeLocationRequestArgs { + /** Key pressed (';', '\n', or '}') */ + key: string; + } + + /** + Format on key request; value of command field is + "formatonkey". Given file location and key typed (as string), + return response giving zero or more edit instructions. The + edit instructions will be sorted in file order. Applying the + edit instructions in reverse to file will result in correctly + reformatted text. + */ + export interface FormatOnKeyRequest extends CodeLocationRequest { + arguments: FormatOnKeyRequestArgs; + } + + /** Arguments for completions messages */ + export interface CompletionsRequestArgs extends CodeLocationRequestArgs { + /** Optional prefix to apply to possible completions. */ + prefix?: string; + } + + /** + Completions request; value of command field is "completions". + Given a file location (file, line, col) and a prefix (which may + be the empty string), return the possible completions that + begin with prefix. + */ + export interface CompletionsRequest extends CodeLocationRequest { + arguments: CompletionsRequestArgs; + } + + /** + Part of a symbol description. + */ + export interface SymbolDisplayPart { + /** Text of an item describing the symbol */ + text: string; + /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + kind: string; + } + + /** An item found in a completion response */ + export interface CompletionItem { + /** The symbol's name */ + name: string; + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + /** Display parts of the symbol (similar to quick info) */ + displayParts?: SymbolDisplayPart[]; + /** Documentation strings for the symbol */ + documentation?: SymbolDisplayPart[]; + } + + export interface CompletionsResponse extends Response { + body?: CompletionItem[]; + } + + /** Arguments for geterr messages. */ + export interface GeterrRequestArgs { + /** + List of file names for which to compute compiler errors. + The files will be checked in list order. + */ + files: string[]; + /** + Delay in milliseconds to wait before starting to compute + errors for the files in the file list + */ + delay: number; + } + + /** + Geterr request; value of command field is "geterr". Wait for + delay milliseconds and then, if during the wait no change or + reload messages have arrived for the first file in the files + list, get the syntactic errors for the file, field requests, + and then get the semantic errors for the file. Repeat with a + smaller delay for each subsequent file on the files list. Best + practice for an editor is to send a file list containing each + file that is currently visible, in most-recently-used order. + */ + export interface GeterrRequest extends Request { + arguments: GeterrRequestArgs; + } + + /** Item of diagnostic information found in a DiagEvent message */ + export interface Diagnostic { + /** Starting code location at which text appies */ + start: LineCol; + /** Length of code location at which text applies */ + len: number; + /** Text of diagnostic message */ + text: string; + } + + /** Event message for "syntaxDiag" and "semanticDiag" event types. + These events provide syntactic and semantic errors for a file. + */ + export interface DiagEvent extends Event { + body?: { + /** The file for which diagnostic information is reported */ + file: string; + /** An array of diagnostic information items */ + diagnostics: Diagnostic[]; + }; + } + + /** Arguments for reload request. */ + export interface ReloadRequestArgs extends FileRequestArgs { + /** + Name of temporary file from which to reload file + contents. May be same as file. + */ + tmpfile: string; + } + + /** + Reload request message; value of command field is "reload". + Reload contents of file with name given by the 'file' argument + from temporary file with name given by the 'tmpfile' argument. + The two names can be identical. + */ + export interface ReloadRequest extends FileRequest { + arguments: ReloadRequestArgs; + } + + /** + Response to "reload" request. This is just an acknowledgement, so + no body field is required. + */ + export interface ReloadResponse extends Response { + } + + /** Arguments for saveto request. */ + export interface SavetoRequestArgs extends FileRequestArgs { + /** + Name of temporary file into which to save server's view of + file contents. + */ + tmpfile: string; + } + + /** + Saveto request message; value of command field is "saveto". + For debugging purposes, save to a temporaryfile (named by + argument 'tmpfile') the contents of file named by argument + 'file'. The server does not currently send a response to a + "saveto" request. + */ + export interface SavetoRequest extends FileRequest { + arguments: SavetoRequestArgs; + } + + /** Arguments for navto request message */ + export interface NavtoRequestArgs extends FileRequestArgs { + /** + Search term to navigate to from current location; term can + be '.*' or an identifier prefix. + */ + searchTerm: string; + } + + /** + Navto request message; value of command field is "navto". + Return list of objects giving code locations and symbols that + match the search term given in argument 'searchTerm'. The + context for the search is given by the named file. + */ + export interface NavtoRequest extends FileRequest { + arguments: NavtoRequestArgs; + } + + /** An item found in a navto response. */ + export interface NavtoItem { + /** The symbol's name */ + name: string; + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + /** exact, substring, or prefix */ + matchKind?: string; + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + /** + The file in which the symbol is found; the value of this + field will always be a string, number of a mapping between + a string and a number. + */ + file: EncodedFile; + /** The location within file at which the symbol is found*/ + start: LineCol; + /** One past the last character of the symbol */ + end: LineCol; + /** + Name of symbol's container symbol (if any); for example, + the class name if symbol is a class member + */ + containerName?: string; + /** Kind of symbol's container symbol (if any) */ + containerKind?: string; + } + + /** + Navto response message. Body is an array of navto items. Each + item gives a symbol that matched the search term. + */ + export interface NavtoResponse extends Response { + body?: NavtoItem[]; + } + + /** Arguments for change request message. */ + export interface ChangeRequestArgs extends CodeLocationRequestArgs { + /** + Length of span deleted at location (file, line, col); nothing deleted + if this field is zero or undefined. + */ + deleteLen?: number; + /** Optional string to insert at location (file, line col). */ + insertString?: string; + } + + /** + Change request message; value of command field is "change". + Update the server's view of the file named by argument 'file'. + Server does not currently send a response to a change request. + */ + export interface ChangeRequest extends CodeLocationRequest { + arguments: ChangeRequestArgs; + } + + /** + If an object of this type is returned in place of a string as + the value of a file field in a response message, add the + mapping id => file to the client's cache of file id mappings, + and interpret the value as if it was the string in the 'file' + field. + */ + export interface IdFile { + /** Id to assign to file */ + id: number; + /** File name that will correspond to id */ + file: string; + } + + /** + The type of an encoded file name. If of type number, the value + is a file id. If of type IdFile, the value is interpreted as + 'file' and in addition the mapping 'id' to 'file' is + established. If of type string, the value is simply the file + name. + */ + export type EncodedFile = number | IdFile | string; + + /** Response to "brace" request. */ + export interface BraceResponse extends Response { + body?: TextSpan[]; + } + + /** + Brace matching request; value of command field is "brace". + Return response giving the code locations of matching braces + found in file at location line, col. + */ + export interface BraceRequest extends CodeLocationRequest { + } + + /** + NavBar itesm request; value of command field is "navbar". + Return response giving the list of navigation bar entries + extracted from the requested file. + */ + export interface NavBarRequest extends FileRequest { + } + export interface NavigationBarItem { - /** The item's display text */ - text: string; - - /** The symbol's kind (such as 'className' or 'parameterName') */ - kind: string; - - /** Optional modifiers for the kind (such as 'public') */ - kindModifiers?: string; - - /** The definition locations of the item */ - spans: TextSpan[]; + /** The item's display text */ + text: string; + + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + + /** The definition locations of the item */ + spans: TextSpan[]; /** Optional children */ childItems?: NavigationBarItem[]; - } - - export interface NavBarResponse extends Response { - body?: NavigationBarItem[]; - } -} - - - - - - + } + + export interface NavBarResponse extends Response { + body?: NavigationBarItem[]; + } +} diff --git a/src/server/session.ts b/src/server/session.ts index 715b8fa3d6aa7..e88932dcb770e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -27,7 +27,7 @@ module ts.server { } interface FileStart { - file: ServerProtocol.EncodedFile; + file: ts.server.protocol.EncodedFile; start: ILineInfo; } @@ -188,7 +188,7 @@ module ts.server { } response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { - var res: ServerProtocol.Response = { + var res: ts.server.protocol.Response = { seq: 0, type: "response", command: cmdName, @@ -204,7 +204,7 @@ module ts.server { this.send(res); } - encodeFileName(fileName: string): ServerProtocol.EncodedFile { + encodeFileName(fileName: string): ts.server.protocol.EncodedFile { var id = ts.lookUp(this.fileHash, fileName); if (!id) { id = this.nextFileId++; @@ -277,7 +277,7 @@ module ts.server { } } - getDefinition(line: number, col: number, fileName: string): ServerProtocol.CodeSpan[] { + getDefinition(line: number, col: number, fileName: string): ts.server.protocol.CodeSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -299,7 +299,7 @@ module ts.server { })); } - getRenameLocations(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ServerProtocol.CodeSpan[] } { + getRenameLocations(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ts.server.protocol.CodeSpan[] } { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -325,7 +325,7 @@ module ts.server { throw Errors.NoContent; } - var bakedRenameLocs = renameLocations.map(location => ({ + var bakedRenameLocs = renameLocations.map(location => ({ file: this.encodeFileName(location.fileName), start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start), end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)), @@ -334,7 +334,7 @@ module ts.server { return { info: renameInfo, locs: bakedRenameLocs }; } - getReferences(line: number, col: number, fileName: string): ServerProtocol.ReferencesResponseBody { + getReferences(line: number, col: number, fileName: string): ts.server.protocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects var file = ts.normalizePath(fileName); @@ -360,7 +360,7 @@ module ts.server { var nameSpan = nameInfo.textSpan; var nameColStart = compilerService.host.positionToLineCol(file, nameSpan.start).col; var nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - var bakedRefs: ServerProtocol.ReferencesResponseItem[] = references.map((ref) => { + var bakedRefs: ts.server.protocol.ReferencesResponseItem[] = references.map((ref) => { var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start); var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); var snap = compilerService.host.getScriptSnapshot(ref.fileName); @@ -386,7 +386,7 @@ module ts.server { this.projectService.openClientFile(file); } - getQuickInfo(line: number, col: number, fileName: string): ServerProtocol.QuickInfoResponseBody { + getQuickInfo(line: number, col: number, fileName: string): ts.server.protocol.QuickInfoResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -412,7 +412,7 @@ module ts.server { }; } - getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): ServerProtocol.CodeEdit[] { + getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): ts.server.protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -438,7 +438,7 @@ module ts.server { }); } - getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): ServerProtocol.CodeEdit[] { + getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): ts.server.protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -480,7 +480,7 @@ module ts.server { }); } - getCompletions(line: number, col: number, prefix: string, fileName: string): ServerProtocol.CompletionItem[] { + getCompletions(line: number, col: number, prefix: string, fileName: string): ts.server.protocol.CompletionItem[] { if (!prefix) { prefix = ""; } @@ -579,7 +579,7 @@ module ts.server { this.projectService.closeClientFile(file); } - decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): ServerProtocol.NavigationBarItem[] { + decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): ts.server.protocol.NavigationBarItem[] { if (!items) { return undefined; } @@ -598,7 +598,7 @@ module ts.server { })); } - getNavigationBarItems(fileName: string): ServerProtocol.NavigationBarItem[] { + getNavigationBarItems(fileName: string): ts.server.protocol.NavigationBarItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -614,7 +614,7 @@ module ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - getNavigateToItems(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { + getNavigateToItems(searchTerm: string, fileName: string): ts.server.protocol.NavtoItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -630,7 +630,7 @@ module ts.server { return navItems.map((navItem) => { var start = compilerService.host.positionToLineCol(navItem.fileName, navItem.textSpan.start); var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); - var bakedItem: ServerProtocol.NavtoItem = { + var bakedItem: ts.server.protocol.NavtoItem = { name: navItem.name, kind: navItem.kind, file: this.encodeFileName(navItem.fileName), @@ -653,7 +653,7 @@ module ts.server { }); } - getBraceMatching(line: number, col: number, fileName: string): ServerProtocol.TextSpan[] { + getBraceMatching(line: number, col: number, fileName: string): ts.server.protocol.TextSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -677,87 +677,87 @@ module ts.server { onMessage(message: string) { try { - var request = JSON.parse(message); + var request = JSON.parse(message); var response: any; switch (request.command) { case CommandNames.Definition: { - var defArgs = request.arguments; + var defArgs = request.arguments; response = this.getDefinition(defArgs.line, defArgs.col, defArgs.file); break; } case CommandNames.References: { - var refArgs = request.arguments; + var refArgs = request.arguments; response = this.getReferences(refArgs.line, refArgs.col, refArgs.file); break; } case CommandNames.Rename: { - var renameArgs = request.arguments; + var renameArgs = request.arguments; response = this.getRenameLocations(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); break; } case CommandNames.Open: { - var openArgs = request.arguments; + var openArgs = request.arguments; this.openClientFile(openArgs.file); break; } case CommandNames.Quickinfo: { - var quickinfoArgs = request.arguments; + var quickinfoArgs = request.arguments; response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); break; } case CommandNames.Format: { - var formatArgs = request.arguments; + var formatArgs = request.arguments; response = this.getFormattingEditsForRange(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file); break; } case CommandNames.Formatonkey: { - var formatOnKeyArgs = request.arguments; + var formatOnKeyArgs = request.arguments; response = this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file); break; } case CommandNames.Completions: { - var completionsArgs = request.arguments; + var completionsArgs = request.arguments; response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); break; } case CommandNames.Geterr: { - var geterrArgs = request.arguments; + var geterrArgs = request.arguments; response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files); break; } case CommandNames.Change: { - var changeArgs = request.arguments; + var changeArgs = request.arguments; this.change(changeArgs.line, changeArgs.col, changeArgs.deleteLen, changeArgs.insertString, changeArgs.file); break; } case CommandNames.Reload: { - var reloadArgs = request.arguments; + var reloadArgs = request.arguments; this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); break; } case CommandNames.Saveto: { - var savetoArgs = request.arguments; + var savetoArgs = request.arguments; this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); break; } case CommandNames.Close: { - var closeArgs = request.arguments; + var closeArgs = request.arguments; this.closeClientFile(closeArgs.file); break; } case CommandNames.Navto: { - var navtoArgs = request.arguments; + var navtoArgs = request.arguments; response = this.getNavigateToItems(navtoArgs.searchTerm, navtoArgs.file); break; } case CommandNames.Brace: { - var braceArguments = request.arguments; + var braceArguments = request.arguments; response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file); break; } case CommandNames.NavBar: { - var navBarArgs = request.arguments; + var navBarArgs = request.arguments; response = this.getNavigationBarItems(navBarArgs.file); break; } From bdd0bf341b171ceba3eaecd2f84a21f435aa992f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 19:48:35 -0800 Subject: [PATCH 37/45] Comment formatting --- src/server/protocol.d.ts | 751 +++++++++++++++++++++++++-------------- 1 file changed, 487 insertions(+), 264 deletions(-) diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index b1eef8f1b7f49..e3433e246901c 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -1,156 +1,222 @@ -/** Declaration module describing the TypeScript Server protocol */ +/** + * Declaration module describing the TypeScript Server protocol + */ declare module ts.server.protocol { - /** A TypeScript Server message */ + /** + * A TypeScript Server message + */ export interface Message { - /** Sequence number of the message */ + /** + * Sequence number of the message + */ seq: number; - /** One of "request", "response", or "event" */ + + /** + * One of "request", "response", or "event" + */ type: string; } - /** Client-initiated request message */ + /** + * Client-initiated request message + */ export interface Request extends Message { - /** The command to execute */ + /** + * The command to execute + */ command: string; - /** Object containing arguments for the command */ + + /** + * Object containing arguments for the command + */ arguments?: any; } - /** Server-initiated event message */ + /** + * Server-initiated event message + */ export interface Event extends Message { - /** Name of event */ + /** + * Name of event + */ event: string; - /** Event-specific information */ + + /** + * Event-specific information + */ body?: any; } - /** Response by server to client request message */ + /** + * Response by server to client request message. + */ export interface Response extends Message { - /** Sequence number of the request message */ + /** + * Sequence number of the request message. + */ request_seq: number; - /** Outcome of the request */ + + /** + * Outcome of the request. + */ success: boolean; - /** The command requested */ + + /** + * The command requested. + */ command: string; - /** Contains error message if success == false. */ + + /** + * Contains error message if success == false. + */ message?: string; - /** Contains message body if success == true. */ + + /** + * Contains message body if success == true. + */ body?: any; } - /** Arguments for FileRequest messages */ + /** + * Arguments for FileRequest messages. + */ export interface FileRequestArgs { - /** The file for the request (absolute pathname required) */ + /** + * The file for the request (absolute pathname required). + */ file: string; } /** - Request whose sole parameter is a file name - */ + * Request whose sole parameter is a file name. + */ export interface FileRequest extends Request { arguments: FileRequestArgs; } /** - Instances of this interface specify a code location: - (file, line, col), where line and column are 1-based. - */ + * Instances of this interface specify a code location: + * (file, line, col), where line and column are 1-based. + */ export interface CodeLocationRequestArgs extends FileRequestArgs { - /** The line number for the request (1-based) */ + /** + * The line number for the request (1-based). + */ line: number; - /** The column for the request (1-based) */ + + /** + * The column for the request (1-based). + */ col: number; } /** - A request whose arguments specify a code location (file, line, col) - */ + * A request whose arguments specify a code location (file, line, col). + */ export interface CodeLocationRequest extends FileRequest { arguments: CodeLocationRequestArgs; } /** - Go to definition request; value of command field is - "definition". Return response giving the code locations that - define the symbol found in file at location line, col. - */ + * Go to definition request; value of command field is + * "definition". Return response giving the code locations that + * define the symbol found in file at location line, col. + */ export interface DefinitionRequest extends CodeLocationRequest { } /** - Object containing line and column (one-based) of code location - */ + * Object containing line and column (one-based) of code location. + */ export interface LineCol { line: number; col: number; } /** - Object found in response messages defining a span of text in - source code. - */ + * Object found in response messages defining a span of text in source code. + */ export interface TextSpan { - /** First character of the definition */ + /** + * First character of the definition. + */ start: LineCol; - /** One character past last character of the definition */ + + /** + * One character past last character of the definition. + */ end: LineCol; } /** - Object found in response messages defining a span of text in - a specific source file. - */ + * Object found in response messages defining a span of text in a specific source file. + */ export interface CodeSpan extends TextSpan { /** - File containing the definition; the value of this - field will always be a string, number of a mapping between - a string and a number. - */ + * File containing the definition; the value of this + * field will always be a string, number of a mapping between + * a string and a number. + */ file: EncodedFile; } /** - Definition response message. Gives text range for definition. - */ + * Definition response message. Gives text range for definition. + */ export interface DefinitionResponse extends Response { body?: CodeSpan[]; } /** - Find references request; value of command field is - "references". Return response giving the code locations that - reference the symbol found in file at location line, col. - */ + * Find references request; value of command field is + * "references". Return response giving the code locations that + * reference the symbol found in file at location line, col. + */ export interface ReferencesRequest extends CodeLocationRequest { } export interface ReferencesResponseItem extends CodeSpan { /** Text of line containing the reference. Including this - with the response avoids latency of editor loading files - to show text of reference line (the server already has - loaded the referencing files). - */ + * with the response avoids latency of editor loading files + * to show text of reference line (the server already has + * loaded the referencing files). + */ lineText: string; - /** True if reference is a write location, false otherwise. */ + /** + * True if reference is a write location, false otherwise. + */ isWriteAccess: boolean; } - /** The body of a "references" response message. */ + /** + * The body of a "references" response message. + */ export interface ReferencesResponseBody { - /** The code locations referencing the symbol */ + /** + * The code locations referencing the symbol. + */ refs: ReferencesResponseItem[]; - /** The name of the symbol */ + + /** + * The name of the symbol. + */ symbolName: string; + /** - The start column of the symbol (on the line provided by the references request) - */ + * The start column of the symbol (on the line provided by the references request). + */ symbolStartCol: number; - /** The full display name of the symbol */ + + /** + * The full display name of the symbol. + */ symbolDisplayString: string; } - /** Response to "references" request. */ + /** + * Response to "references" request. + */ export interface ReferencesResponse extends Response { body?: ReferencesResponseBody; } @@ -162,195 +228,281 @@ declare module ts.server.protocol { /** - Rename request; value of command field is "rename". Return - response giving the code locations that reference the symbol - found in file at location line, col. Also return full display - name of the symbol so that client can print it unambiguously. - */ + * Rename request; value of command field is "rename". Return + * response giving the code locations that reference the symbol + * found in file at location line, col. Also return full display + * name of the symbol so that client can print it unambiguously. + */ export interface RenameRequest extends CodeLocationRequest { arguments: RenameRequestArgs; } - /** Information about the item to be renamed. */ + /** + * Information about the item to be renamed. + */ export interface RenameInfo { - /** True if item can be renamed */ + /** + * True if item can be renamed. + */ canRename: boolean; - /** Error message if item can not be renamed */ + + /** + * Error message if item can not be renamed. + */ localizedErrorMessage: string; - /** Display name of the item to be renamed */ + + /** + * Display name of the item to be renamed. + */ displayName: string; - /** Full display name of item to be renamed */ + + /** + * Full display name of item to be renamed. + */ fullDisplayName: string; - /** The items's kind (such as 'className' or 'parameterName' or plain 'text') */ + + /** + * The items's kind (such as 'className' or 'parameterName' or plain 'text'). + */ kind: string; - /** Optional modifiers for the kind (such as 'public') */ + + /** + * Optional modifiers for the kind (such as 'public'). + */ kindModifiers: string; } /** - Rename response message. - */ + * Rename response message. + */ export interface RenameResponse extends Response { body?: { - /** Information about the item to be renamed */ + /** + * Information about the item to be renamed. + */ info: RenameInfo; + /** - An array of code locations that refer to the - item to be renamed. - */ + * An array of code locations that refer to the item to be renamed. + */ locs: CodeSpan[]; } } /** - Open request; value of command field is "open". Notify the - server that the client has file open. The server will not - monitor the filesystem for changes in this file and will assume - that the client is updating the server (using the change and/or - reload messages) when the file changes. Server does not currently - send a response to an open request. - */ + * Open request; value of command field is "open". Notify the + * server that the client has file open. The server will not + * monitor the filesystem for changes in this file and will assume + * that the client is updating the server (using the change and/or + * reload messages) when the file changes. Server does not currently + * send a response to an open request. + */ export interface OpenRequest extends FileRequest { } /** - Close request; value of command field is "close". Notify the - server that the client has closed a previously open file. If - file is still referenced by open files, the server will resume - monitoring the filesystem for changes to file. Server does not - currently send a response to a close request. - */ + * Close request; value of command field is "close". Notify the + * server that the client has closed a previously open file. If + * file is still referenced by open files, the server will resume + * monitoring the filesystem for changes to file. Server does not + * currently send a response to a close request. + */ export interface CloseRequest extends FileRequest { } /** - Quickinfo request; value of command field is - "quickinfo". Return response giving a quick type and - documentation string for the symbol found in file at location - line, col. - */ + * Quickinfo request; value of command field is + * "quickinfo". Return response giving a quick type and + * documentation string for the symbol found in file at location + * line, col. + */ export interface QuickInfoRequest extends CodeLocationRequest { } - /** Body of QuickInfoResponse */ + /** + * Body of QuickInfoResponse. + */ export interface QuickInfoResponseBody { - /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + /** + * The symbol's kind (such as 'className' or 'parameterName' or plain 'text'). + */ kind: string; - /** Optional modifiers for the kind (such as 'public') */ + + /** + * Optional modifiers for the kind (such as 'public'). + */ kindModifiers: string; - /** Starting code location of symbol */ + + /** + * Starting code location of symbol. + */ start: LineCol; - /** One past last character of symbol */ + + /** + * One past last character of symbol. + */ end: LineCol; - /** Type and kind of symbol */ + + /** + * Type and kind of symbol. + */ displayString: string; - /** Documentation associated with symbol */ + + /** + * Documentation associated with symbol. + */ documentation: string; } - /** Quickinfo response message */ + /** + * Quickinfo response message. + */ export interface QuickInfoResponse extends Response { body?: QuickInfoResponseBody; } - /** Arguments for format messages */ + /** + * Arguments for format messages. + */ export interface FormatRequestArgs extends CodeLocationRequestArgs { - /** Last line of range for which to format text in file */ + /** + * Last line of range for which to format text in file. + */ endLine: number; - /** Last column of range for which to format text in file */ + + /** + * Last column of range for which to format text in file. + */ endCol: number; } /** - Format request; value of command field is "format". Return - response giving zero or more edit instructions. The edit - instructions will be sorted in file order. Applying the edit - instructions in reverse to file will result in correctly - reformatted text. - */ + * Format request; value of command field is "format". Return + * response giving zero or more edit instructions. The edit + * instructions will be sorted in file order. Applying the edit + * instructions in reverse to file will result in correctly + * reformatted text. + */ export interface FormatRequest extends CodeLocationRequest { arguments: FormatRequestArgs; } /** - Object found in response messages defining an editing - instruction for a span of text in source code. The effect of - this instruction is to replace the text starting at start and - ending one character before end with newText. For an insertion, - the text span is empty. For a deletion, newText is empty. - */ + * Object found in response messages defining an editing + * instruction for a span of text in source code. The effect of + * this instruction is to replace the text starting at start and + * ending one character before end with newText. For an insertion, + * the text span is empty. For a deletion, newText is empty. + */ export interface CodeEdit { - /** First character of the text span to edit. */ + /** + * First character of the text span to edit. + */ start: LineCol; - /** One character past last character of the text span to edit */ + + /** + * One character past last character of the text span to edit. + */ end: LineCol; + /** - Replace the span defined above with this string (may be - the empty string) - */ + * Replace the span defined above with this string (may be + * the empty string). + */ newText: string; } - /** Format and format on key response message */ + /** + * Format and format on key response message. + */ export interface FormatResponse extends Response { body?: CodeEdit[]; } - /** Arguments for format on key messages */ + /** + * Arguments for format on key messages. + */ export interface FormatOnKeyRequestArgs extends CodeLocationRequestArgs { - /** Key pressed (';', '\n', or '}') */ + /** + * Key pressed (';', '\n', or '}'). + */ key: string; } /** - Format on key request; value of command field is - "formatonkey". Given file location and key typed (as string), - return response giving zero or more edit instructions. The - edit instructions will be sorted in file order. Applying the - edit instructions in reverse to file will result in correctly - reformatted text. - */ + * Format on key request; value of command field is + * "formatonkey". Given file location and key typed (as string), + * return response giving zero or more edit instructions. The + * edit instructions will be sorted in file order. Applying the + * edit instructions in reverse to file will result in correctly + * reformatted text. + */ export interface FormatOnKeyRequest extends CodeLocationRequest { arguments: FormatOnKeyRequestArgs; } - /** Arguments for completions messages */ + /** + * Arguments for completions messages. + */ export interface CompletionsRequestArgs extends CodeLocationRequestArgs { - /** Optional prefix to apply to possible completions. */ + /** + * Optional prefix to apply to possible completions. + */ prefix?: string; } /** - Completions request; value of command field is "completions". - Given a file location (file, line, col) and a prefix (which may - be the empty string), return the possible completions that - begin with prefix. - */ + * Completions request; value of command field is "completions". + * Given a file location (file, line, col) and a prefix (which may + * be the empty string), return the possible completions that + * begin with prefix. + */ export interface CompletionsRequest extends CodeLocationRequest { arguments: CompletionsRequestArgs; } /** - Part of a symbol description. - */ + * Part of a symbol description. + */ export interface SymbolDisplayPart { - /** Text of an item describing the symbol */ + /** + * Text of an item describing the symbol. + */ text: string; - /** The symbol's kind (such as 'className' or 'parameterName' or plain 'text') */ + + /** + * The symbol's kind (such as 'className' or 'parameterName' or plain 'text'). + */ kind: string; } - /** An item found in a completion response */ + /** + * An item found in a completion response. + */ export interface CompletionItem { - /** The symbol's name */ + /** + * The symbol's name. + */ name: string; - /** The symbol's kind (such as 'className' or 'parameterName') */ + + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ kind: string; - /** Optional modifiers for the kind (such as 'public') */ + + /** + * Optional modifiers for the kind (such as 'public'). + */ kindModifiers?: string; - /** Display parts of the symbol (similar to quick info) */ + + /** + * Display parts of the symbol (similar to quick info). + */ displayParts?: SymbolDisplayPart[]; - /** Documentation strings for the symbol */ + + /** + * Documentation strings for the symbol. + */ documentation?: SymbolDisplayPart[]; } @@ -358,236 +510,307 @@ declare module ts.server.protocol { body?: CompletionItem[]; } - /** Arguments for geterr messages. */ + /** + * Arguments for geterr messages. + */ export interface GeterrRequestArgs { /** - List of file names for which to compute compiler errors. - The files will be checked in list order. - */ + * List of file names for which to compute compiler errors. + * The files will be checked in list order. + */ files: string[]; + /** - Delay in milliseconds to wait before starting to compute - errors for the files in the file list - */ + * Delay in milliseconds to wait before starting to compute + * errors for the files in the file list + */ delay: number; } /** - Geterr request; value of command field is "geterr". Wait for - delay milliseconds and then, if during the wait no change or - reload messages have arrived for the first file in the files - list, get the syntactic errors for the file, field requests, - and then get the semantic errors for the file. Repeat with a - smaller delay for each subsequent file on the files list. Best - practice for an editor is to send a file list containing each - file that is currently visible, in most-recently-used order. - */ + * Geterr request; value of command field is "geterr". Wait for + * delay milliseconds and then, if during the wait no change or + * reload messages have arrived for the first file in the files + * list, get the syntactic errors for the file, field requests, + * and then get the semantic errors for the file. Repeat with a + * smaller delay for each subsequent file on the files list. Best + * practice for an editor is to send a file list containing each + * file that is currently visible, in most-recently-used order. + */ export interface GeterrRequest extends Request { arguments: GeterrRequestArgs; } - /** Item of diagnostic information found in a DiagEvent message */ + /** + * Item of diagnostic information found in a DiagEvent message. + */ export interface Diagnostic { - /** Starting code location at which text appies */ + /** + * Starting code location at which text appies. + */ start: LineCol; - /** Length of code location at which text applies */ + + /** + * Length of code location at which text applies. + */ len: number; - /** Text of diagnostic message */ + + /** + * Text of diagnostic message. + */ text: string; } - /** Event message for "syntaxDiag" and "semanticDiag" event types. - These events provide syntactic and semantic errors for a file. - */ + /** + * Event message for "syntaxDiag" and "semanticDiag" event types. + * These events provide syntactic and semantic errors for a file. + */ export interface DiagEvent extends Event { body?: { - /** The file for which diagnostic information is reported */ + /** + * The file for which diagnostic information is reported. + */ file: string; - /** An array of diagnostic information items */ + + /** + * An array of diagnostic information items. + */ diagnostics: Diagnostic[]; }; } - /** Arguments for reload request. */ + /** + * Arguments for reload request. + */ export interface ReloadRequestArgs extends FileRequestArgs { /** - Name of temporary file from which to reload file - contents. May be same as file. - */ + * Name of temporary file from which to reload file + * contents. May be same as file. + */ tmpfile: string; } /** - Reload request message; value of command field is "reload". - Reload contents of file with name given by the 'file' argument - from temporary file with name given by the 'tmpfile' argument. - The two names can be identical. - */ + * Reload request message; value of command field is "reload". + * Reload contents of file with name given by the 'file' argument + * from temporary file with name given by the 'tmpfile' argument. + * The two names can be identical. + */ export interface ReloadRequest extends FileRequest { arguments: ReloadRequestArgs; } /** - Response to "reload" request. This is just an acknowledgement, so - no body field is required. - */ + * Response to "reload" request. This is just an acknowledgement, so + * no body field is required. + */ export interface ReloadResponse extends Response { } - /** Arguments for saveto request. */ + /** + * Arguments for saveto request. + */ export interface SavetoRequestArgs extends FileRequestArgs { /** - Name of temporary file into which to save server's view of - file contents. - */ + * Name of temporary file into which to save server's view of + * file contents. + */ tmpfile: string; } /** - Saveto request message; value of command field is "saveto". - For debugging purposes, save to a temporaryfile (named by - argument 'tmpfile') the contents of file named by argument - 'file'. The server does not currently send a response to a - "saveto" request. - */ + * Saveto request message; value of command field is "saveto". + * For debugging purposes, save to a temporaryfile (named by + * argument 'tmpfile') the contents of file named by argument + * 'file'. The server does not currently send a response to a + * "saveto" request. + */ export interface SavetoRequest extends FileRequest { arguments: SavetoRequestArgs; } - /** Arguments for navto request message */ + /** + * Arguments for navto request message. + */ export interface NavtoRequestArgs extends FileRequestArgs { /** - Search term to navigate to from current location; term can - be '.*' or an identifier prefix. - */ + * Search term to navigate to from current location; term can + * be '.*' or an identifier prefix. + */ searchTerm: string; } /** - Navto request message; value of command field is "navto". - Return list of objects giving code locations and symbols that - match the search term given in argument 'searchTerm'. The - context for the search is given by the named file. - */ + * Navto request message; value of command field is "navto". + * Return list of objects giving code locations and symbols that + * match the search term given in argument 'searchTerm'. The + * context for the search is given by the named file. + */ export interface NavtoRequest extends FileRequest { arguments: NavtoRequestArgs; } - /** An item found in a navto response. */ + /** + * An item found in a navto response. + */ export interface NavtoItem { - /** The symbol's name */ + /** + * The symbol's name. + */ name: string; - /** The symbol's kind (such as 'className' or 'parameterName') */ + + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ kind: string; - /** exact, substring, or prefix */ + + /** + * exact, substring, or prefix. + */ matchKind?: string; - /** Optional modifiers for the kind (such as 'public') */ + + /** + * Optional modifiers for the kind (such as 'public'). + */ kindModifiers?: string; + /** - The file in which the symbol is found; the value of this - field will always be a string, number of a mapping between - a string and a number. - */ + * The file in which the symbol is found; the value of this + * field will always be a string, number of a mapping between + * a string and a number. + */ file: EncodedFile; - /** The location within file at which the symbol is found*/ + + /** + * The location within file at which the symbol is found. + */ start: LineCol; - /** One past the last character of the symbol */ + + /** + * One past the last character of the symbol. + */ end: LineCol; + /** - Name of symbol's container symbol (if any); for example, - the class name if symbol is a class member - */ + * Name of symbol's container symbol (if any); for example, + * the class name if symbol is a class member. + */ containerName?: string; - /** Kind of symbol's container symbol (if any) */ + + /** + * Kind of symbol's container symbol (if any). + */ containerKind?: string; } /** - Navto response message. Body is an array of navto items. Each - item gives a symbol that matched the search term. - */ + * Navto response message. Body is an array of navto items. Each + * item gives a symbol that matched the search term. + */ export interface NavtoResponse extends Response { body?: NavtoItem[]; } - /** Arguments for change request message. */ + /** + * Arguments for change request message. + */ export interface ChangeRequestArgs extends CodeLocationRequestArgs { /** - Length of span deleted at location (file, line, col); nothing deleted - if this field is zero or undefined. - */ + * Length of span deleted at location (file, line, col); nothing deleted + * if this field is zero or undefined. + */ deleteLen?: number; - /** Optional string to insert at location (file, line col). */ + + /** + * Optional string to insert at location (file, line col). + */ insertString?: string; } /** - Change request message; value of command field is "change". - Update the server's view of the file named by argument 'file'. - Server does not currently send a response to a change request. - */ + * Change request message; value of command field is "change". + * Update the server's view of the file named by argument 'file'. + * Server does not currently send a response to a change request. + */ export interface ChangeRequest extends CodeLocationRequest { arguments: ChangeRequestArgs; } /** - If an object of this type is returned in place of a string as - the value of a file field in a response message, add the - mapping id => file to the client's cache of file id mappings, - and interpret the value as if it was the string in the 'file' - field. - */ + * If an object of this type is returned in place of a string as + * the value of a file field in a response message, add the + * mapping id => file to the client's cache of file id mappings, + * and interpret the value as if it was the string in the 'file' + * field. + */ export interface IdFile { - /** Id to assign to file */ + /** + * Id to assign to file. + */ id: number; - /** File name that will correspond to id */ + + /** + * File name that will correspond to id. + */ file: string; } /** - The type of an encoded file name. If of type number, the value - is a file id. If of type IdFile, the value is interpreted as - 'file' and in addition the mapping 'id' to 'file' is - established. If of type string, the value is simply the file - name. - */ + * The type of an encoded file name. If of type number, the value + * is a file id. If of type IdFile, the value is interpreted as + * 'file' and in addition the mapping 'id' to 'file' is + * established. If of type string, the value is simply the file + * name. + */ export type EncodedFile = number | IdFile | string; - /** Response to "brace" request. */ + /** + * Response to "brace" request. + */ export interface BraceResponse extends Response { body?: TextSpan[]; } /** - Brace matching request; value of command field is "brace". - Return response giving the code locations of matching braces - found in file at location line, col. - */ + * Brace matching request; value of command field is "brace". + * Return response giving the code locations of matching braces + * found in file at location line, col. + */ export interface BraceRequest extends CodeLocationRequest { } /** - NavBar itesm request; value of command field is "navbar". - Return response giving the list of navigation bar entries - extracted from the requested file. + * NavBar itesm request; value of command field is "navbar". + * Return response giving the list of navigation bar entries + * extracted from the requested file. */ export interface NavBarRequest extends FileRequest { } export interface NavigationBarItem { - /** The item's display text */ + /** + * The item's display text. + */ text: string; - /** The symbol's kind (such as 'className' or 'parameterName') */ + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ kind: string; - /** Optional modifiers for the kind (such as 'public') */ + /** + * Optional modifiers for the kind (such as 'public'). + */ kindModifiers?: string; - /** The definition locations of the item */ + /** + * The definition locations of the item. + */ spans: TextSpan[]; - /** Optional children */ + /** + * Optional children. + */ childItems?: NavigationBarItem[]; } From bbcdb6125487a68cbed44ffc981758e9a5dc5e11 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 19:55:10 -0800 Subject: [PATCH 38/45] remove unused cancellationToken class --- src/server/editorServices.ts | 39 ++---------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4f23e9a7c14f2..7a055b77eac6e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -72,34 +72,12 @@ module ts.server { } } - export class CancellationToken { - public static None = new CancellationToken(); - - requestPending = false; - - constructor() { - } - - cancel() { - this.requestPending = true; - } - - reset() { - this.requestPending = false; - } - - public isCancellationRequested() { - var temp = this.requestPending; - return temp; - } - } - export class LSHost implements ts.LanguageServiceHost { ls: ts.LanguageService = null; compilationSettings: ts.CompilerOptions; filenameToScript: ts.Map = {}; - constructor(public host: ServerHost, public project: Project, private cancellationToken: CancellationToken = CancellationToken.None) { + constructor(public host: ServerHost, public project: Project) { } getDefaultLibFileName() { @@ -107,14 +85,6 @@ module ts.server { return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings)); } - cancel() { - this.cancellationToken.cancel(); - } - - reset() { - this.cancellationToken.reset(); - } - getScriptSnapshot(filename: string): ts.IScriptSnapshot { var scriptInfo = this.getScriptInfo(filename); if (scriptInfo) { @@ -154,10 +124,6 @@ module ts.server { return this.getScriptInfo(filename).svc.latestVersion().toString(); } - getCancellationToken(): ts.CancellationToken { - return this.cancellationToken; - } - getCurrentDirectory(): string { return ""; } @@ -736,7 +702,6 @@ module ts.server { } export class CompilerService { - cancellationToken = new CancellationToken(); host: LSHost; languageService: ts.LanguageService; classifier: ts.Classifier; @@ -745,7 +710,7 @@ module ts.server { formatCodeOptions: ts.FormatCodeOptions = CompilerService.defaultFormatCodeOptions; constructor(public project: Project) { - this.host = new LSHost(project.projectService.host, project, this.cancellationToken); + this.host = new LSHost(project.projectService.host, project); // override default ES6 (remove when compiler default back at ES5) this.settings.target = ts.ScriptTarget.ES5; this.host.setCompilationSettings(this.settings); From 54e67568318dc852b62a012a92cbc231806f610a Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 15 Feb 2015 20:04:14 -0800 Subject: [PATCH 39/45] Remove unused code and only expose needed types/classes --- src/server/editorServices.ts | 40 +++++++++++++----------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 7a055b77eac6e..5b5ee27d70738 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -13,19 +13,8 @@ module ts.server { } var lineCollectionCapacity = 4; - var indentStrings: string[] = []; - var indentBase = " "; - function getIndent(indentAmt: number) { - if (!indentStrings[indentAmt]) { - indentStrings[indentAmt] = ""; - for (var i = 0; i < indentAmt; i++) { - indentStrings[indentAmt] += indentBase; - } - } - return indentStrings[indentAmt]; - } - export class ScriptInfo { + class ScriptInfo { svc: ScriptVersionCache; children: ScriptInfo[] = []; // files referenced by this file @@ -72,7 +61,7 @@ module ts.server { } } - export class LSHost implements ts.LanguageServiceHost { + class LSHost implements ts.LanguageServiceHost { ls: ts.LanguageService = null; compilationSettings: ts.CompilerOptions; filenameToScript: ts.Map = {}; @@ -270,7 +259,7 @@ module ts.server { } } - export interface ProjectOptions { + interface ProjectOptions { // these fields can be present in the project file files?: string[]; formatCodeOptions?: ts.FormatCodeOptions; @@ -354,7 +343,7 @@ module ts.server { } } - export interface ProjectOpenResult { + interface ProjectOpenResult { success?: boolean; errorMsg?: string; project?: Project; @@ -701,7 +690,7 @@ module ts.server { } - export class CompilerService { + class CompilerService { host: LSHost; languageService: ts.LanguageService; classifier: ts.Classifier; @@ -745,7 +734,7 @@ module ts.server { } - export interface LineCollection { + interface LineCollection { charCount(): number; lineCount(): number; isLeaf(): boolean; @@ -759,7 +748,7 @@ module ts.server { leaf?: LineLeaf; } - export enum CharRangeSection { + enum CharRangeSection { PreStart, Start, Entire, @@ -768,7 +757,7 @@ module ts.server { PostEnd } - export interface ILineIndexWalker { + interface ILineIndexWalker { goSubtree: boolean; done: boolean; leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void; @@ -994,7 +983,7 @@ module ts.server { } // text change information - export class TextChange { + class TextChange { constructor(public pos: number, public deleteLen: number, public insertedText?: string) { } @@ -1004,7 +993,7 @@ module ts.server { } } - export class ScriptVersionCache { + class ScriptVersionCache { changes: TextChange[] = []; versions: LineIndexSnapshot[] = []; minVersion = 0; // no versions earlier than min version will maintain change history @@ -1109,7 +1098,7 @@ module ts.server { } } - export class LineIndexSnapshot implements ts.IScriptSnapshot { + class LineIndexSnapshot implements ts.IScriptSnapshot { index: LineIndex; changesSincePreviousVersion: TextChange[] = []; @@ -1157,8 +1146,7 @@ module ts.server { } } - - export class LineIndex { + class LineIndex { root: LineNode; // set this to true to check each edit for accuracy checkEdits = false; @@ -1338,7 +1326,7 @@ module ts.server { } } - export class LineNode implements LineCollection { + class LineNode implements LineCollection { totalChars = 0; totalLines = 0; children: LineCollection[] = []; @@ -1624,7 +1612,7 @@ module ts.server { } } - export class LineLeaf implements LineCollection { + class LineLeaf implements LineCollection { udata: any; constructor(public text: string) { From 32e2f4d95d007667b827f34c8109be5b32b49ffe Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 16 Feb 2015 12:04:34 -0800 Subject: [PATCH 40/45] Accept APISamples baselines --- tests/baselines/reference/APISample_compile.js | 1 + tests/baselines/reference/APISample_compile.types | 3 +++ tests/baselines/reference/APISample_linter.js | 1 + tests/baselines/reference/APISample_linter.types | 3 +++ tests/baselines/reference/APISample_transform.js | 1 + tests/baselines/reference/APISample_transform.types | 3 +++ tests/baselines/reference/APISample_watcher.js | 1 + tests/baselines/reference/APISample_watcher.types | 3 +++ 8 files changed, 16 insertions(+) diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index 7d89d88b5b321..1b7ca0c37d2ff 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -1605,6 +1605,7 @@ declare module "typescript" { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + [s: string]: boolean | number | string; } interface DefinitionInfo { fileName: string; diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index 8cf789bf20fd0..98063887df465 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -5195,6 +5195,9 @@ declare module "typescript" { PlaceOpenBraceOnNewLineForControlBlocks: boolean; >PlaceOpenBraceOnNewLineForControlBlocks : boolean + + [s: string]: boolean | number | string; +>s : string } interface DefinitionInfo { >DefinitionInfo : DefinitionInfo diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index 0ce5a27796836..ebb2f7503d87e 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -1636,6 +1636,7 @@ declare module "typescript" { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + [s: string]: boolean | number | string; } interface DefinitionInfo { fileName: string; diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index 115a0f5a16b6e..4d92af6dd2ea2 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -5339,6 +5339,9 @@ declare module "typescript" { PlaceOpenBraceOnNewLineForControlBlocks: boolean; >PlaceOpenBraceOnNewLineForControlBlocks : boolean + + [s: string]: boolean | number | string; +>s : string } interface DefinitionInfo { >DefinitionInfo : DefinitionInfo diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index 6ecf4e7c395f8..315b1a97eac20 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -1637,6 +1637,7 @@ declare module "typescript" { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + [s: string]: boolean | number | string; } interface DefinitionInfo { fileName: string; diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index e2797fe526744..bad0f064116c2 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -5291,6 +5291,9 @@ declare module "typescript" { PlaceOpenBraceOnNewLineForControlBlocks: boolean; >PlaceOpenBraceOnNewLineForControlBlocks : boolean + + [s: string]: boolean | number | string; +>s : string } interface DefinitionInfo { >DefinitionInfo : DefinitionInfo diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index eb141f20672a4..543677016a569 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -1674,6 +1674,7 @@ declare module "typescript" { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + [s: string]: boolean | number | string; } interface DefinitionInfo { fileName: string; diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index 692e07f317a0a..7889e492db6d1 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -5464,6 +5464,9 @@ declare module "typescript" { PlaceOpenBraceOnNewLineForControlBlocks: boolean; >PlaceOpenBraceOnNewLineForControlBlocks : boolean + + [s: string]: boolean | number | string; +>s : string } interface DefinitionInfo { >DefinitionInfo : DefinitionInfo From d2712dd793db7032b11e5ef71c1f4a4ee06131b2 Mon Sep 17 00:00:00 2001 From: steveluc Date: Mon, 16 Feb 2015 23:43:05 -0800 Subject: [PATCH 41/45] Removed file mapping compression technique due to brittleness of approach. As necessary, will substitute grouping or paging approaches. --- src/server/client.ts | 24 ++++-------------------- src/server/protocol.d.ts | 40 ++++------------------------------------ src/server/session.ts | 30 +++++++----------------------- 3 files changed, 15 insertions(+), 79 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 22b346aa3ec6a..a3b32be31edad 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -69,22 +69,6 @@ module ts.server { }; } - private decodeEncodedFileId(fileId: ts.server.protocol.EncodedFile): string { - var fileName: string; - if (typeof fileId === "object") { - fileName = (fileId).file; - this.fileMapping[(fileId).id] = fileName; - } - else if (typeof fileId === "number") { - fileName = ts.lookUp(this.fileMapping, fileId.toString()); - Debug.assert(!!fileName, "Did not find filename in previous fileID mappings."); - } - else { - Debug.fail("Got unexpedted fileId type."); - } - return fileName; - } - private processRequest(command: string, arguments?: any): T { var request: ts.server.protocol.Request = { seq: this.sequence++, @@ -232,7 +216,7 @@ module ts.server { var response = this.processResponse(request); return response.body.map(entry => { - var fileName = this.decodeEncodedFileId(entry.file); + var fileName = entry.file; var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); @@ -299,7 +283,7 @@ module ts.server { var response = this.processResponse(request); return response.body.map(entry => { - var fileName = this.decodeEncodedFileId(entry.file); + var fileName = entry.file; var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); return { @@ -325,7 +309,7 @@ module ts.server { var response = this.processResponse(request); return response.body.refs.map(entry => { - var fileName = this.decodeEncodedFileId(entry.file); + var fileName = entry.file; var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); return { @@ -378,7 +362,7 @@ module ts.server { findInStrings: findInStrings, findInComments: findInComments, locations: response.body.locs.map((entry) => { - var fileName = this.decodeEncodedFileId(entry.file); + var fileName = entry.file; var start = this.lineColToPosition(fileName, entry.start); var end = this.lineColToPosition(fileName, entry.end); return { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index e3433e246901c..06974b58cb34d 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -153,11 +153,9 @@ declare module ts.server.protocol { */ export interface CodeSpan extends TextSpan { /** - * File containing the definition; the value of this - * field will always be a string, number of a mapping between - * a string and a number. + * File containing text span. */ - file: EncodedFile; + file: string; } /** @@ -675,11 +673,9 @@ declare module ts.server.protocol { kindModifiers?: string; /** - * The file in which the symbol is found; the value of this - * field will always be a string, number of a mapping between - * a string and a number. + * The file in which the symbol is found. */ - file: EncodedFile; + file: string; /** * The location within file at which the symbol is found. @@ -736,34 +732,6 @@ declare module ts.server.protocol { arguments: ChangeRequestArgs; } - /** - * If an object of this type is returned in place of a string as - * the value of a file field in a response message, add the - * mapping id => file to the client's cache of file id mappings, - * and interpret the value as if it was the string in the 'file' - * field. - */ - export interface IdFile { - /** - * Id to assign to file. - */ - id: number; - - /** - * File name that will correspond to id. - */ - file: string; - } - - /** - * The type of an encoded file name. If of type number, the value - * is a file id. If of type IdFile, the value is interpreted as - * 'file' and in addition the mapping 'id' to 'file' is - * established. If of type string, the value is simply the file - * name. - */ - export type EncodedFile = number | IdFile | string; - /** * Response to "brace" request. */ diff --git a/src/server/session.ts b/src/server/session.ts index e88932dcb770e..812d2fc6c1527 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -5,10 +5,6 @@ /// module ts.server { - var paddedLength = 8; - - var typeNames = ["interface", "class", "enum", "module", "alias", "type"]; - var spaceCache = [" ", " ", " ", " "]; interface StackTraceError extends Error { @@ -27,7 +23,7 @@ module ts.server { } interface FileStart { - file: ts.server.protocol.EncodedFile; + file: string; start: ILineInfo; } @@ -204,18 +200,6 @@ module ts.server { this.send(res); } - encodeFileName(fileName: string): ts.server.protocol.EncodedFile { - var id = ts.lookUp(this.fileHash, fileName); - if (!id) { - id = this.nextFileId++; - this.fileHash[fileName] = id; - return { id: id, file: fileName }; - } - else { - return id; - } - } - output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) { this.response(body, commandName, requestSequence, errorMessage); } @@ -224,7 +208,7 @@ module ts.server { var diags = project.compilerService.languageService.getSemanticDiagnostics(file); if (diags) { var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); - this.event({ file: this.encodeFileName(file), diagnostics: bakedDiags }, "semanticDiag"); + this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag"); } } @@ -232,7 +216,7 @@ module ts.server { var diags = project.compilerService.languageService.getSyntacticDiagnostics(file); if (diags) { var bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); - this.event({ file: this.encodeFileName(file), diagnostics: bakedDiags }, "syntaxDiag"); + this.event({ file: file, diagnostics: bakedDiags }, "syntaxDiag"); } } @@ -293,7 +277,7 @@ module ts.server { } return definitions.map(def => ({ - file: this.encodeFileName(def.fileName), + file: def.fileName, start: compilerService.host.positionToLineCol(def.fileName, def.textSpan.start), end: compilerService.host.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) })); @@ -326,7 +310,7 @@ module ts.server { } var bakedRenameLocs = renameLocations.map(location => ({ - file: this.encodeFileName(location.fileName), + file: location.fileName, start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start), end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)), })); @@ -366,7 +350,7 @@ module ts.server { var snap = compilerService.host.getScriptSnapshot(ref.fileName); var lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); return { - file: this.encodeFileName(ref.fileName), + file: ref.fileName, start: start, lineText: lineText, end: compilerService.host.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)), @@ -633,7 +617,7 @@ module ts.server { var bakedItem: ts.server.protocol.NavtoItem = { name: navItem.name, kind: navItem.kind, - file: this.encodeFileName(navItem.fileName), + file: navItem.fileName, start: start, end: end, }; From cadd57c10fe8036661427c9bf4fa1c22c85658c3 Mon Sep 17 00:00:00 2001 From: steveluc Date: Tue, 17 Feb 2015 02:02:59 -0800 Subject: [PATCH 42/45] Change rename response to return the rename info + nested location information: one array of location information per file. Add interface definition for rename response body. --- src/server/client.ts | 23 ++++++++++-------- src/server/protocol.d.ts | 36 ++++++++++++++++++---------- src/server/session.ts | 51 +++++++++++++++++++++++++++++----------- 3 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index a3b32be31edad..e12a9fecd3ebc 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -348,7 +348,18 @@ module ts.server { var request = this.processRequest(CommandNames.Rename, args); var response = this.processResponse(request); - + var locations: RenameLocation[] = []; + response.body.locs.map((entry: ts.server.protocol.SpanGroup) => { + var fileName = entry.file; + entry.locs.map((loc: ts.server.protocol.TextSpan) => { + var start = this.lineColToPosition(fileName, loc.start); + var end = this.lineColToPosition(fileName, loc.end); + locations.push({ + textSpan: ts.createTextSpanFromBounds(start, end), + fileName: fileName + }); + }); + }); return this.lastRenameEntry = { canRename: response.body.info.canRename, displayName: response.body.info.displayName, @@ -361,15 +372,7 @@ module ts.server { position: position, findInStrings: findInStrings, findInComments: findInComments, - locations: response.body.locs.map((entry) => { - var fileName = entry.file; - var start = this.lineColToPosition(fileName, entry.start); - var end = this.lineColToPosition(fileName, entry.end); - return { - textSpan: ts.createTextSpanFromBounds(start, end), - fileName: fileName - }; - }) + locations: locations }; } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 06974b58cb34d..3fdded3aa6a81 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -247,7 +247,7 @@ declare module ts.server.protocol { /** * Error message if item can not be renamed. */ - localizedErrorMessage: string; + localizedErrorMessage?: string; /** * Display name of the item to be renamed. @@ -270,21 +270,33 @@ declare module ts.server.protocol { kindModifiers: string; } + /** + * A group of text spans, all in 'file'. + */ + export interface SpanGroup { + /** The file to which the spans apply */ + file: string; + /** The text spans in this group */ + locs: TextSpan[]; + } + + export interface RenameResponseBody { + /** + * Information about the item to be renamed. + */ + info: RenameInfo; + + /** + * An array of span groups (one per file) that refer to the item to be renamed. + */ + locs: SpanGroup[]; + } + /** * Rename response message. */ export interface RenameResponse extends Response { - body?: { - /** - * Information about the item to be renamed. - */ - info: RenameInfo; - - /** - * An array of code locations that refer to the item to be renamed. - */ - locs: CodeSpan[]; - } + body?: RenameResponseBody; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 812d2fc6c1527..67811c76412a6 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -18,7 +18,7 @@ module ts.server { strBuilder += " "; } spaceCache[n] = strBuilder; - } + } return spaceCache[n]; } @@ -54,6 +54,7 @@ module ts.server { } function sortNavItems(items: ts.NavigateToItem[]) { + return items.sort((a, b) => { if (a.matchKind < b.matchKind) { return -1; @@ -77,17 +78,6 @@ module ts.server { }) } - interface FileRange { - file?: string; - start: ILineInfo; - end: ILineInfo; - } - - interface FileRanges { - file: string; - locs: FileRange[]; - } - function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) { return { start: project.compilerService.host.positionToLineCol(fileName, diag.start), @@ -283,7 +273,7 @@ module ts.server { })); } - getRenameLocations(line: number, col: number, fileName: string, findInComments: boolean, findInStrings: boolean): { info: RenameInfo; locs: ts.server.protocol.CodeSpan[] } { + getRenameLocations(line: number, col: number, fileName: string,findInComments: boolean, findInStrings: boolean): ts.server.protocol.RenameResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -313,7 +303,40 @@ module ts.server { file: location.fileName, start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start), end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)), - })); + })).sort((a, b) => { + if (a.file < b.file) { + return -1; + } + else if (a.file > b.file) { + return 1; + } + else { + // reverse sort assuming no overlap + if (a.start.line < b.start.line) { + return 1; + } + else if (a.start.line > b.start.line) { + return -1; + } + else { + return b.start.col - a.start.col; + } + } + }).reduce((accum: ts.server.protocol.SpanGroup[], cur: ts.server.protocol.CodeSpan) => { + var curFileAccum: ts.server.protocol.SpanGroup; + if (accum.length > 0) { + curFileAccum = accum[accum.length - 1]; + if (curFileAccum.file != cur.file) { + curFileAccum = undefined; + } + } + if (!curFileAccum) { + curFileAccum = { file: cur.file, locs: [] }; + accum.push(curFileAccum); + } + curFileAccum.locs.push({ start: cur.start, end: cur.end }); + return accum; + }, []); return { info: renameInfo, locs: bakedRenameLocs }; } From 3868fb5a6b26ab132a20edb85665d0c422eed0c2 Mon Sep 17 00:00:00 2001 From: steveluc Date: Wed, 18 Feb 2015 00:14:52 -0800 Subject: [PATCH 43/45] Removed deleteLen from change request; added endLine, endCol that mark the end of the deleted Range (inclusive). DeleteLen was not always accurate because editors normalize \r\n to \n in some cases, affecting the length of ranges. In Diagnostic response items, changed len field to end to address the same range length issue. Flattened MessageDiagnosticChains in diagnostic message text, since clients expect string there. Renamed ts.server.protocol to simply protocol in session.ts and client.ts since module name prefix is clear. Based on protocol feedback: Changed LineCol to Location. Changed CodeLocation interface name prefix to FileLocation. Changed DiagEvent to DiagnosticEvent. Removed anonymous types. --- src/server/client.ts | 92 +++++++++++++++-------------- src/server/protocol.d.ts | 124 +++++++++++++++++++-------------------- src/server/session.ts | 95 +++++++++++++++--------------- 3 files changed, 153 insertions(+), 158 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index e12a9fecd3ebc..d51777c2e99de 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -47,11 +47,11 @@ module ts.server { return lineMap; } - private lineColToPosition(fileName: string, lineCol: ts.server.protocol.LineCol): number { + private lineColToPosition(fileName: string, lineCol: protocol.Location): number { return ts.computePositionFromLineAndCharacter(this.getLineMap(fileName), lineCol.line, lineCol.col); } - private positionToOneBasedLineCol(fileName: string, position: number): ts.server.protocol.LineCol { + private positionToOneBasedLineCol(fileName: string, position: number): protocol.Location { var lineCol = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); return { line: lineCol.line, @@ -59,7 +59,7 @@ module ts.server { }; } - private convertCodeEditsToTextChange(fileName: string, codeEdit: ts.server.protocol.CodeEdit): ts.TextChange { + private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): ts.TextChange { var start = this.lineColToPosition(fileName, codeEdit.start); var end = this.lineColToPosition(fileName, codeEdit.end); @@ -69,8 +69,8 @@ module ts.server { }; } - private processRequest(command: string, arguments?: any): T { - var request: ts.server.protocol.Request = { + private processRequest(command: string, arguments?: any): T { + var request: protocol.Request = { seq: this.sequence++, type: "request", command: command, @@ -82,7 +82,7 @@ module ts.server { return request; } - private processResponse(request: ts.server.protocol.Request): T { + private processResponse(request: protocol.Request): T { var lastMessage = this.messages.shift(); Debug.assert(!!lastMessage, "Did not recieve any responses."); @@ -122,12 +122,12 @@ module ts.server { } openFile(fileName: string): void { - var args: ts.server.protocol.FileRequestArgs = { file: fileName }; + var args: protocol.FileRequestArgs = { file: fileName }; this.processRequest(CommandNames.Open, args); } closeFile(fileName: string): void { - var args: ts.server.protocol.FileRequestArgs = { file: fileName }; + var args: protocol.FileRequestArgs = { file: fileName }; this.processRequest(CommandNames.Close, args); } @@ -136,12 +136,14 @@ module ts.server { this.lineMaps[fileName] = undefined; var lineCol = this.positionToOneBasedLineCol(fileName, start); - var args: ts.server.protocol.ChangeRequestArgs = { + var endLineCol = this.positionToOneBasedLineCol(fileName, end); + + var args: protocol.ChangeRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, - insertLen: end - start, - deleteLen: end - start, + endLine: endLineCol.line, + endCol: endLineCol.col, insertString: newText }; @@ -150,14 +152,14 @@ module ts.server { getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.CodeLocationRequestArgs = { + var args: protocol.FileLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col }; - var request = this.processRequest(CommandNames.Quickinfo, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Quickinfo, args); + var response = this.processResponse(request); var start = this.lineColToPosition(fileName, response.body.start); var end = this.lineColToPosition(fileName, response.body.end); @@ -173,15 +175,15 @@ module ts.server { getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.CompletionsRequestArgs = { + var args: protocol.CompletionsRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, prefix: undefined }; - var request = this.processRequest(CommandNames.Completions, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Completions, args); + var response = this.processResponse(request); return this.lastCompletionEntry = { isMemberCompletion: false, @@ -207,13 +209,13 @@ module ts.server { } getNavigateToItems(searchTerm: string): NavigateToItem[] { - var args: ts.server.protocol.NavtoRequestArgs = { + var args: protocol.NavtoRequestArgs = { searchTerm, file: this.host.getScriptFileNames()[0] }; - var request = this.processRequest(CommandNames.Navto, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Navto, args); + var response = this.processResponse(request); return response.body.map(entry => { var fileName = entry.file; @@ -236,7 +238,7 @@ module ts.server { getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { var startLineCol = this.positionToOneBasedLineCol(fileName, start); var endLineCol = this.positionToOneBasedLineCol(fileName, end); - var args: ts.server.protocol.FormatRequestArgs = { + var args: protocol.FormatRequestArgs = { file: fileName, line: startLineCol.line, col: startLineCol.col, @@ -245,8 +247,8 @@ module ts.server { }; // TODO: handle FormatCodeOptions - var request = this.processRequest(CommandNames.Format, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Format, args); + var response = this.processResponse(request); return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); } @@ -257,7 +259,7 @@ module ts.server { getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): ts.TextChange[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.FormatOnKeyRequestArgs = { + var args: protocol.FormatOnKeyRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, @@ -265,22 +267,22 @@ module ts.server { }; // TODO: handle FormatCodeOptions - var request = this.processRequest(CommandNames.Formatonkey, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Formatonkey, args); + var response = this.processResponse(request); return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry)); } getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.CodeLocationRequestArgs = { + var args: protocol.FileLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.Definition, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Definition, args); + var response = this.processResponse(request); return response.body.map(entry => { var fileName = entry.file; @@ -299,14 +301,14 @@ module ts.server { getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.CodeLocationRequestArgs = { + var args: protocol.FileLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.References, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.References, args); + var response = this.processResponse(request); return response.body.refs.map(entry => { var fileName = entry.file; @@ -338,7 +340,7 @@ module ts.server { getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.RenameRequestArgs = { + var args: protocol.RenameRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, @@ -346,12 +348,12 @@ module ts.server { findInComments }; - var request = this.processRequest(CommandNames.Rename, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Rename, args); + var response = this.processResponse(request); var locations: RenameLocation[] = []; - response.body.locs.map((entry: ts.server.protocol.SpanGroup) => { + response.body.locs.map((entry: protocol.SpanGroup) => { var fileName = entry.file; - entry.locs.map((loc: ts.server.protocol.TextSpan) => { + entry.locs.map((loc: protocol.TextSpan) => { var start = this.lineColToPosition(fileName, loc.start); var end = this.lineColToPosition(fileName, loc.end); locations.push({ @@ -388,7 +390,7 @@ module ts.server { return this.lastRenameEntry.locations; } - decodeNavigationBarItems(items: ts.server.protocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { + decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { if (!items) { return []; } @@ -406,12 +408,12 @@ module ts.server { } getNavigationBarItems(fileName: string): NavigationBarItem[] { - var args: ts.server.protocol.FileRequestArgs = { + var args: protocol.FileRequestArgs = { file: fileName }; - var request = this.processRequest(CommandNames.NavBar, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.NavBar, args); + var response = this.processResponse(request); return this.decodeNavigationBarItems(response.body, fileName); } @@ -437,19 +439,19 @@ module ts.server { } getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { - throw new Error("Not Implemented Yet."); + throw new Error("Not Implemented Yet."); } getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { var lineCol = this.positionToOneBasedLineCol(fileName, position); - var args: ts.server.protocol.CodeLocationRequestArgs = { + var args: protocol.FileLocationRequestArgs = { file: fileName, line: lineCol.line, col: lineCol.col, }; - var request = this.processRequest(CommandNames.Brace, args); - var response = this.processResponse(request); + var request = this.processRequest(CommandNames.Brace, args); + var response = this.processResponse(request); return response.body.map(entry => { var start = this.lineColToPosition(fileName, entry.start); diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 3fdded3aa6a81..a446f0176aeab 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -95,10 +95,10 @@ declare module ts.server.protocol { } /** - * Instances of this interface specify a code location: + * Instances of this interface specify a location in a source file: * (file, line, col), where line and column are 1-based. */ - export interface CodeLocationRequestArgs extends FileRequestArgs { + export interface FileLocationRequestArgs extends FileRequestArgs { /** * The line number for the request (1-based). */ @@ -111,24 +111,24 @@ declare module ts.server.protocol { } /** - * A request whose arguments specify a code location (file, line, col). + * A request whose arguments specify a file location (file, line, col). */ - export interface CodeLocationRequest extends FileRequest { - arguments: CodeLocationRequestArgs; + export interface FileLocationRequest extends FileRequest { + arguments: FileLocationRequestArgs; } /** * Go to definition request; value of command field is - * "definition". Return response giving the code locations that + * "definition". Return response giving the file locations that * define the symbol found in file at location line, col. */ - export interface DefinitionRequest extends CodeLocationRequest { + export interface DefinitionRequest extends FileLocationRequest { } /** - * Object containing line and column (one-based) of code location. + * Location in source code expressed as (one-based) line and column. */ - export interface LineCol { + export interface Location { line: number; col: number; } @@ -140,18 +140,18 @@ declare module ts.server.protocol { /** * First character of the definition. */ - start: LineCol; + start: Location; /** * One character past last character of the definition. */ - end: LineCol; + end: Location; } /** * Object found in response messages defining a span of text in a specific source file. */ - export interface CodeSpan extends TextSpan { + export interface FileSpan extends TextSpan { /** * File containing text span. */ @@ -162,18 +162,18 @@ declare module ts.server.protocol { * Definition response message. Gives text range for definition. */ export interface DefinitionResponse extends Response { - body?: CodeSpan[]; + body?: FileSpan[]; } /** * Find references request; value of command field is - * "references". Return response giving the code locations that + * "references". Return response giving the file locations that * reference the symbol found in file at location line, col. */ - export interface ReferencesRequest extends CodeLocationRequest { + export interface ReferencesRequest extends FileLocationRequest { } - export interface ReferencesResponseItem extends CodeSpan { + export interface ReferencesResponseItem extends FileSpan { /** Text of line containing the reference. Including this * with the response avoids latency of editor loading files * to show text of reference line (the server already has @@ -192,7 +192,7 @@ declare module ts.server.protocol { */ export interface ReferencesResponseBody { /** - * The code locations referencing the symbol. + * The file locations referencing the symbol. */ refs: ReferencesResponseItem[]; @@ -219,7 +219,7 @@ declare module ts.server.protocol { body?: ReferencesResponseBody; } - export interface RenameRequestArgs extends CodeLocationRequestArgs { + export interface RenameRequestArgs extends FileLocationRequestArgs { findInComments?: boolean; findInStrings?: boolean; } @@ -227,11 +227,11 @@ declare module ts.server.protocol { /** * Rename request; value of command field is "rename". Return - * response giving the code locations that reference the symbol + * response giving the file locations that reference the symbol * found in file at location line, col. Also return full display * name of the symbol so that client can print it unambiguously. */ - export interface RenameRequest extends CodeLocationRequest { + export interface RenameRequest extends FileLocationRequest { arguments: RenameRequestArgs; } @@ -326,7 +326,7 @@ declare module ts.server.protocol { * documentation string for the symbol found in file at location * line, col. */ - export interface QuickInfoRequest extends CodeLocationRequest { + export interface QuickInfoRequest extends FileLocationRequest { } /** @@ -344,14 +344,14 @@ declare module ts.server.protocol { kindModifiers: string; /** - * Starting code location of symbol. + * Starting file location of symbol. */ - start: LineCol; + start: Location; /** * One past last character of symbol. */ - end: LineCol; + end: Location; /** * Type and kind of symbol. @@ -374,7 +374,7 @@ declare module ts.server.protocol { /** * Arguments for format messages. */ - export interface FormatRequestArgs extends CodeLocationRequestArgs { + export interface FormatRequestArgs extends FileLocationRequestArgs { /** * Last line of range for which to format text in file. */ @@ -393,7 +393,7 @@ declare module ts.server.protocol { * instructions in reverse to file will result in correctly * reformatted text. */ - export interface FormatRequest extends CodeLocationRequest { + export interface FormatRequest extends FileLocationRequest { arguments: FormatRequestArgs; } @@ -408,12 +408,12 @@ declare module ts.server.protocol { /** * First character of the text span to edit. */ - start: LineCol; + start: Location; /** * One character past last character of the text span to edit. */ - end: LineCol; + end: Location; /** * Replace the span defined above with this string (may be @@ -432,7 +432,7 @@ declare module ts.server.protocol { /** * Arguments for format on key messages. */ - export interface FormatOnKeyRequestArgs extends CodeLocationRequestArgs { + export interface FormatOnKeyRequestArgs extends FileLocationRequestArgs { /** * Key pressed (';', '\n', or '}'). */ @@ -447,14 +447,14 @@ declare module ts.server.protocol { * edit instructions in reverse to file will result in correctly * reformatted text. */ - export interface FormatOnKeyRequest extends CodeLocationRequest { + export interface FormatOnKeyRequest extends FileLocationRequest { arguments: FormatOnKeyRequestArgs; } /** * Arguments for completions messages. */ - export interface CompletionsRequestArgs extends CodeLocationRequestArgs { + export interface CompletionsRequestArgs extends FileLocationRequestArgs { /** * Optional prefix to apply to possible completions. */ @@ -467,7 +467,7 @@ declare module ts.server.protocol { * be the empty string), return the possible completions that * begin with prefix. */ - export interface CompletionsRequest extends CodeLocationRequest { + export interface CompletionsRequest extends FileLocationRequest { arguments: CompletionsRequestArgs; } @@ -552,18 +552,18 @@ declare module ts.server.protocol { } /** - * Item of diagnostic information found in a DiagEvent message. + * Item of diagnostic information found in a DiagnosticEvent message. */ export interface Diagnostic { /** - * Starting code location at which text appies. + * Starting file location at which text appies. */ - start: LineCol; + start: Location; /** - * Length of code location at which text applies. + * The last file location at which the text applies. */ - len: number; + end: Location; /** * Text of diagnostic message. @@ -571,24 +571,26 @@ declare module ts.server.protocol { text: string; } + export interface DiagnosticEventBody { + /** + * The file for which diagnostic information is reported. + */ + file: string; + + /** + * An array of diagnostic information items. + */ + diagnostics: Diagnostic[]; + } + /** * Event message for "syntaxDiag" and "semanticDiag" event types. * These events provide syntactic and semantic errors for a file. */ - export interface DiagEvent extends Event { - body?: { - /** - * The file for which diagnostic information is reported. - */ - file: string; - - /** - * An array of diagnostic information items. - */ - diagnostics: Diagnostic[]; - }; + export interface DiagnosticEvent extends Event { + body?: DiagnosticEventBody; } - + /** * Arguments for reload request. */ @@ -652,7 +654,7 @@ declare module ts.server.protocol { /** * Navto request message; value of command field is "navto". - * Return list of objects giving code locations and symbols that + * Return list of objects giving file locations and symbols that * match the search term given in argument 'searchTerm'. The * context for the search is given by the named file. */ @@ -692,12 +694,12 @@ declare module ts.server.protocol { /** * The location within file at which the symbol is found. */ - start: LineCol; + start: Location; /** * One past the last character of the symbol. */ - end: LineCol; + end: Location; /** * Name of symbol's container symbol (if any); for example, @@ -722,15 +724,9 @@ declare module ts.server.protocol { /** * Arguments for change request message. */ - export interface ChangeRequestArgs extends CodeLocationRequestArgs { - /** - * Length of span deleted at location (file, line, col); nothing deleted - * if this field is zero or undefined. - */ - deleteLen?: number; - + export interface ChangeRequestArgs extends FormatRequestArgs { /** - * Optional string to insert at location (file, line col). + * Optional string to insert at location (file, line, col). */ insertString?: string; } @@ -740,7 +736,7 @@ declare module ts.server.protocol { * Update the server's view of the file named by argument 'file'. * Server does not currently send a response to a change request. */ - export interface ChangeRequest extends CodeLocationRequest { + export interface ChangeRequest extends FileLocationRequest { arguments: ChangeRequestArgs; } @@ -753,10 +749,10 @@ declare module ts.server.protocol { /** * Brace matching request; value of command field is "brace". - * Return response giving the code locations of matching braces + * Return response giving the file locations of matching braces * found in file at location line, col. */ - export interface BraceRequest extends CodeLocationRequest { + export interface BraceRequest extends FileLocationRequest { } /** diff --git a/src/server/session.ts b/src/server/session.ts index 67811c76412a6..1a0a8d98a90e1 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -81,8 +81,8 @@ module ts.server { function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) { return { start: project.compilerService.host.positionToLineCol(fileName, diag.start), - len: diag.length, - text: diag.messageText, + end: project.compilerService.host.positionToLineCol(fileName, diag.start+diag.length), + text: ts.flattenDiagnosticMessageText(diag.messageText, "\n") }; } @@ -174,7 +174,7 @@ module ts.server { } response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) { - var res: ts.server.protocol.Response = { + var res: protocol.Response = { seq: 0, type: "response", command: cmdName, @@ -251,7 +251,7 @@ module ts.server { } } - getDefinition(line: number, col: number, fileName: string): ts.server.protocol.CodeSpan[] { + getDefinition(line: number, col: number, fileName: string): protocol.FileSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -273,7 +273,7 @@ module ts.server { })); } - getRenameLocations(line: number, col: number, fileName: string,findInComments: boolean, findInStrings: boolean): ts.server.protocol.RenameResponseBody { + getRenameLocations(line: number, col: number, fileName: string,findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -299,7 +299,7 @@ module ts.server { throw Errors.NoContent; } - var bakedRenameLocs = renameLocations.map(location => ({ + var bakedRenameLocs = renameLocations.map(location => ({ file: location.fileName, start: compilerService.host.positionToLineCol(location.fileName, location.textSpan.start), end: compilerService.host.positionToLineCol(location.fileName, ts.textSpanEnd(location.textSpan)), @@ -322,8 +322,8 @@ module ts.server { return b.start.col - a.start.col; } } - }).reduce((accum: ts.server.protocol.SpanGroup[], cur: ts.server.protocol.CodeSpan) => { - var curFileAccum: ts.server.protocol.SpanGroup; + }).reduce((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { + var curFileAccum: protocol.SpanGroup; if (accum.length > 0) { curFileAccum = accum[accum.length - 1]; if (curFileAccum.file != cur.file) { @@ -341,7 +341,7 @@ module ts.server { return { info: renameInfo, locs: bakedRenameLocs }; } - getReferences(line: number, col: number, fileName: string): ts.server.protocol.ReferencesResponseBody { + getReferences(line: number, col: number, fileName: string): protocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects var file = ts.normalizePath(fileName); @@ -367,7 +367,7 @@ module ts.server { var nameSpan = nameInfo.textSpan; var nameColStart = compilerService.host.positionToLineCol(file, nameSpan.start).col; var nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - var bakedRefs: ts.server.protocol.ReferencesResponseItem[] = references.map((ref) => { + var bakedRefs: protocol.ReferencesResponseItem[] = references.map((ref) => { var start = compilerService.host.positionToLineCol(ref.fileName, ref.textSpan.start); var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); var snap = compilerService.host.getScriptSnapshot(ref.fileName); @@ -393,7 +393,7 @@ module ts.server { this.projectService.openClientFile(file); } - getQuickInfo(line: number, col: number, fileName: string): ts.server.protocol.QuickInfoResponseBody { + getQuickInfo(line: number, col: number, fileName: string): protocol.QuickInfoResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -419,7 +419,7 @@ module ts.server { }; } - getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): ts.server.protocol.CodeEdit[] { + getFormattingEditsForRange(line: number, col: number, endLine: number, endCol: number, fileName: string): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -445,7 +445,7 @@ module ts.server { }); } - getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): ts.server.protocol.CodeEdit[] { + getFormattingEditsAfterKeystroke(line: number, col: number, key: string, fileName: string): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -456,7 +456,7 @@ module ts.server { var compilerService = project.compilerService; var position = compilerService.host.lineColToPosition(file, line, col); var edits = compilerService.languageService.getFormattingEditsAfterKeystroke(file, position, key, - compilerService.formatCodeOptions); + compilerService.formatCodeOptions); if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, position))) { // TODO: get these options from host var editorOptions: ts.EditorOptions = { @@ -487,7 +487,7 @@ module ts.server { }); } - getCompletions(line: number, col: number, prefix: string, fileName: string): ts.server.protocol.CompletionItem[] { + getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionItem[] { if (!prefix) { prefix = ""; } @@ -541,18 +541,15 @@ module ts.server { } } - change(line: number, col: number, deleteLen: number, insertString: string, fileName: string) { + change(line: number, col: number, endLine: number, endCol: number, insertString: string, fileName: string) { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (project) { var compilerService = project.compilerService; - var pos = compilerService.host.lineColToPosition(file, line, col); - if (pos >= 0) { - var end = pos; - if (deleteLen) { - end += deleteLen; - } - compilerService.host.editScript(file, pos, end, insertString); + var start = compilerService.host.lineColToPosition(file, line, col); + var end = compilerService.host.lineColToPosition(file, endLine, endCol); + if (start >= 0) { + compilerService.host.editScript(file, start, end, insertString); this.changeSeq++; } } @@ -586,7 +583,7 @@ module ts.server { this.projectService.closeClientFile(file); } - decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): ts.server.protocol.NavigationBarItem[] { + decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] { if (!items) { return undefined; } @@ -605,7 +602,7 @@ module ts.server { })); } - getNavigationBarItems(fileName: string): ts.server.protocol.NavigationBarItem[] { + getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -621,7 +618,7 @@ module ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - getNavigateToItems(searchTerm: string, fileName: string): ts.server.protocol.NavtoItem[] { + getNavigateToItems(searchTerm: string, fileName: string): protocol.NavtoItem[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -637,7 +634,7 @@ module ts.server { return navItems.map((navItem) => { var start = compilerService.host.positionToLineCol(navItem.fileName, navItem.textSpan.start); var end = compilerService.host.positionToLineCol(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); - var bakedItem: ts.server.protocol.NavtoItem = { + var bakedItem: protocol.NavtoItem = { name: navItem.name, kind: navItem.kind, file: navItem.fileName, @@ -660,7 +657,7 @@ module ts.server { }); } - getBraceMatching(line: number, col: number, fileName: string): ts.server.protocol.TextSpan[] { + getBraceMatching(line: number, col: number, fileName: string): protocol.TextSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -684,87 +681,87 @@ module ts.server { onMessage(message: string) { try { - var request = JSON.parse(message); + var request = JSON.parse(message); var response: any; switch (request.command) { - case CommandNames.Definition: { - var defArgs = request.arguments; + case CommandNames.Definition: { + var defArgs = request.arguments; response = this.getDefinition(defArgs.line, defArgs.col, defArgs.file); break; } - case CommandNames.References: { - var refArgs = request.arguments; + case CommandNames.References: { + var refArgs = request.arguments; response = this.getReferences(refArgs.line, refArgs.col, refArgs.file); break; } case CommandNames.Rename: { - var renameArgs = request.arguments; + var renameArgs = request.arguments; response = this.getRenameLocations(renameArgs.line, renameArgs.col, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); break; } case CommandNames.Open: { - var openArgs = request.arguments; + var openArgs = request.arguments; this.openClientFile(openArgs.file); break; } case CommandNames.Quickinfo: { - var quickinfoArgs = request.arguments; + var quickinfoArgs = request.arguments; response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.col, quickinfoArgs.file); break; } case CommandNames.Format: { - var formatArgs = request.arguments; + var formatArgs = request.arguments; response = this.getFormattingEditsForRange(formatArgs.line, formatArgs.col, formatArgs.endLine, formatArgs.endCol, formatArgs.file); break; } case CommandNames.Formatonkey: { - var formatOnKeyArgs = request.arguments; + var formatOnKeyArgs = request.arguments; response = this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.col, formatOnKeyArgs.key, formatOnKeyArgs.file); break; } case CommandNames.Completions: { - var completionsArgs = request.arguments; + var completionsArgs = request.arguments; response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); break; } case CommandNames.Geterr: { - var geterrArgs = request.arguments; + var geterrArgs = request.arguments; response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files); break; } case CommandNames.Change: { - var changeArgs = request.arguments; - this.change(changeArgs.line, changeArgs.col, changeArgs.deleteLen, changeArgs.insertString, - changeArgs.file); + var changeArgs = request.arguments; + this.change(changeArgs.line, changeArgs.col, changeArgs.endLine, changeArgs.endCol, + changeArgs.insertString, changeArgs.file); break; } case CommandNames.Reload: { - var reloadArgs = request.arguments; + var reloadArgs = request.arguments; this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); break; } case CommandNames.Saveto: { - var savetoArgs = request.arguments; + var savetoArgs = request.arguments; this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); break; } case CommandNames.Close: { - var closeArgs = request.arguments; + var closeArgs = request.arguments; this.closeClientFile(closeArgs.file); break; } case CommandNames.Navto: { - var navtoArgs = request.arguments; + var navtoArgs = request.arguments; response = this.getNavigateToItems(navtoArgs.searchTerm, navtoArgs.file); break; } case CommandNames.Brace: { - var braceArguments = request.arguments; + var braceArguments = request.arguments; response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file); break; } case CommandNames.NavBar: { - var navBarArgs = request.arguments; + var navBarArgs = request.arguments; response = this.getNavigationBarItems(navBarArgs.file); break; } From 4b590836e7e16a2bb65f9c9e18d64956c1883904 Mon Sep 17 00:00:00 2001 From: steveluc Date: Wed, 18 Feb 2015 15:12:35 -0800 Subject: [PATCH 44/45] Split completions req/response pair into two messages "completions" and "completionEntryDetails". This mirrors the function of the LS API and increases performance of completion in large projects. --- src/server/client.ts | 31 +++++++++++++------------ src/server/protocol.d.ts | 43 +++++++++++++++++++++++++++------- src/server/session.ts | 50 +++++++++++++++++++++++++--------------- 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index d51777c2e99de..a0b68de840e44 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -1,5 +1,5 @@ /// - + module ts.server { export interface SessionClientHost extends LanguageServiceHost { @@ -172,7 +172,7 @@ module ts.server { documentation: [{ kind: "text", text: response.body.documentation }] }; } - + getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { var lineCol = this.positionToOneBasedLineCol(fileName, position); var args: protocol.CompletionsRequestArgs = { @@ -185,27 +185,28 @@ module ts.server { var request = this.processRequest(CommandNames.Completions, args); var response = this.processResponse(request); - return this.lastCompletionEntry = { + return { isMemberCompletion: false, isNewIdentifierLocation: false, - entries: response.body.map(entry => ({ - kind: entry.kind, - kindModifiers: entry.kindModifiers, - name: entry.name, - displayParts: entry.displayParts, - documentation: entry.documentation - })), + entries: response.body, fileName: fileName, position: position }; } - + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { - if (!this.lastCompletionEntry || this.lastCompletionEntry.fileName !== fileName || this.lastCompletionEntry.position !== position) { - this.getCompletionsAtPosition(fileName, position); - } + var lineCol = this.positionToOneBasedLineCol(fileName, position); + var args: protocol.CompletionDetailsRequestArgs = { + file: fileName, + line: lineCol.line, + col: lineCol.col, + entryNames: [entryName] + }; - return this.lastCompletionEntry.entries.filter(entry => entry.name === entryName)[0]; + var request = this.processRequest(CommandNames.CompletionDetails, args); + var response = this.processResponse(request); + Debug.assert(response.body.length == 1, "Unexpected length of completion details response body."); + return response.body[0]; } getNavigateToItems(searchTerm: string): NavigateToItem[] { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index a446f0176aeab..b07fb20d48d4d 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -471,6 +471,26 @@ declare module ts.server.protocol { arguments: CompletionsRequestArgs; } + /** + * Arguments for completion details request. + */ + export interface CompletionDetailsRequestArgs extends FileLocationRequestArgs { + /** + * Names of one or more entries for which to obtain details. + */ + entryNames: string[]; + } + + /** + * Completion entry details request; value of command field is + * "completionEntryDetails". Given a file location (file, line, + * col) and an array of completion entry names return more + * detailed information for each completion entry. + */ + export interface CompletionDetailsRequest extends FileLocationRequest { + arguments: CompletionDetailsRequestArgs; + } + /** * Part of a symbol description. */ @@ -489,35 +509,42 @@ declare module ts.server.protocol { /** * An item found in a completion response. */ - export interface CompletionItem { + export interface CompletionEntry { /** * The symbol's name. */ name: string; - /** * The symbol's kind (such as 'className' or 'parameterName'). */ kind: string; - /** * Optional modifiers for the kind (such as 'public'). */ - kindModifiers?: string; - + kindModifiers: string; + } + + /** + * Additional completion entry details, available on demand + */ + export interface CompletionEntryDetails extends CompletionEntry { /** * Display parts of the symbol (similar to quick info). */ - displayParts?: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; /** * Documentation strings for the symbol. */ - documentation?: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; } export interface CompletionsResponse extends Response { - body?: CompletionItem[]; + body?: CompletionEntry[]; + } + + export interface CompletionDetailsResponse extends Response { + body?: CompletionEntryDetails[]; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 1a0a8d98a90e1..bfcf82bce29ac 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -81,7 +81,7 @@ module ts.server { function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) { return { start: project.compilerService.host.positionToLineCol(fileName, diag.start), - end: project.compilerService.host.positionToLineCol(fileName, diag.start+diag.length), + end: project.compilerService.host.positionToLineCol(fileName, diag.start + diag.length), text: ts.flattenDiagnosticMessageText(diag.messageText, "\n") }; } @@ -104,6 +104,7 @@ module ts.server { export var Change = "change"; export var Close = "close"; export var Completions = "completions"; + export var CompletionDetails = "completionEntryDetails"; export var Definition = "definition"; export var Format = "format"; export var Formatonkey = "formatonkey"; @@ -486,8 +487,8 @@ module ts.server { }; }); } - - getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionItem[] { + + getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionEntry[] { if (!prefix) { prefix = ""; } @@ -505,27 +506,34 @@ module ts.server { throw Errors.NoContent; } - return completions.entries.reduce((result: ts.CompletionEntryDetails[], entry: ts.CompletionEntry) => { + return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { if (completions.isMemberCompletion || entry.name.indexOf(prefix) == 0) { - var protoEntry = {}; - protoEntry.name = entry.name; - protoEntry.kind = entry.kind; - if (entry.kindModifiers && (entry.kindModifiers.length > 0)) { - protoEntry.kindModifiers = entry.kindModifiers; - } - var details = compilerService.languageService.getCompletionEntryDetails(file, position, entry.name); - if (details && (details.documentation) && (details.documentation.length > 0)) { - protoEntry.documentation = details.documentation; - } - if (details && (details.displayParts) && (details.displayParts.length > 0)) { - protoEntry.displayParts = details.displayParts; - } - result.push(protoEntry); + result.push(entry); } return result; }, []); } + getCompletionEntryDetails(line: number, col: number, + entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] { + var file = ts.normalizePath(fileName); + var project = this.projectService.getProjectForFile(file); + if (!project) { + throw Errors.NoProject; + } + + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); + + return entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => { + var details = compilerService.languageService.getCompletionEntryDetails(file, position, entryName); + if (details) { + accum.push(details); + } + return accum; + }, []); + } + getDiagnostics(delay: number, fileNames: string[]) { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); @@ -724,6 +732,12 @@ module ts.server { response = this.getCompletions(request.arguments.line, request.arguments.col, completionsArgs.prefix, request.arguments.file); break; } + case CommandNames.CompletionDetails: { + var completionDetailsArgs = request.arguments; + response = this.getCompletionEntryDetails(request.arguments.line, request.arguments.col, completionDetailsArgs.entryNames, + request.arguments.file); + break; + } case CommandNames.Geterr: { var geterrArgs = request.arguments; response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files); From d364f6110e2f43393377c97b474e2dd69d8ae2c5 Mon Sep 17 00:00:00 2001 From: steveluc Date: Thu, 19 Feb 2015 12:56:53 -0800 Subject: [PATCH 45/45] Style fixes. --- src/server/client.ts | 1 - src/server/session.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index a0b68de840e44..6185834a022df 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -24,7 +24,6 @@ module ts.server { private fileMapping: ts.Map = {}; private lineMaps: ts.Map = {}; private messages: string[] = []; - private lastCompletionEntry: CompletionEntry; private lastRenameEntry: RenameEntry; constructor(private host: SessionClientHost) { diff --git a/src/server/session.ts b/src/server/session.ts index bfcf82bce29ac..19f6cd3645ea0 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -54,7 +54,6 @@ module ts.server { } function sortNavItems(items: ts.NavigateToItem[]) { - return items.sort((a, b) => { if (a.matchKind < b.matchKind) { return -1;