From a0b557e1e2d3c56f8c2ece643b7d77b2410a699e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sat, 14 Feb 2015 15:12:06 -0800 Subject: [PATCH] Recover from git corruption --- src/harness/harnessLanguageService.ts | 149 +++- src/server/client.ts | 294 ++---- src/server/editorServices.ts | 3 +- src/server/protocol.ts | 1190 +++++++------------------ src/server/protodef.d.ts | 56 +- src/server/server.ts | 76 +- 6 files changed, 601 insertions(+), 1167 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 39f59948cb..2e50ca59b9 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 = new ServerLanguageServiceHost(cancellationToken, options); - this.client = new ts.server.SessionClient(this.host, /*abbreviate*/ true); - this.host.setClient(this.client); + // 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); + + // 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 a93177adb3..1aa7547f54 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; - } +module ts.server { - 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 interface SessionClientHost extends LanguageServiceHost { + writeMessage(message: string): 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); + export class SessionClient implements LanguageService { + private sequence: number = 0; + private fileMapping: ts.Map = {}; + private lineMaps: ts.Map = {}; + private messages: string[] = []; + + constructor(private host: SessionClientHost) { } - private positionToOneBasedLineCol(fileName: string, position: number): ServerProtocol.LineCol { - var lineCol = this.host.positionToZeroBasedLineCol(fileName, position); + public onMessage(message: string): void { + this.messages.push(message); + } + + private writeMessage(message: string): void { + this.host.writeMessage(message); + } + + 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; + } + + private lineColToPosition(fileName: string, lineCol: ServerProtocol.LineCol): number { + return ts.computePositionFromLineAndCharacter(this.getLineMap(fileName), lineCol.line, lineCol.col); + } + + 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 933435f93a..7007704a16 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 6187d8e614..16abbf029d 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 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 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"); - } + if (!project) { + throw Errors.NoProject; } - } - // 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; + var compilerService = project.compilerService; + var position = compilerService.host.lineColToPosition(file, line, col); - 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"); - } + var references = compilerService.languageService.getReferencesAtPosition(file, position); + if (!references) { + throw Errors.NoContent; } - else { - this.output(undefined, CommandNames.Type, reqSeq, "no project for " + file); + + 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); - } - 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 (!project) { + throw Errors.NoProject; } - 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 }); + 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; + } + 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.documentation; + } + accum.push(protoEntry); } return accum; }, []); + } + + 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, 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; - }); + if (!project) { + throw Errors.NoProject; + } - this.output(bakedNavItems, CommandNames.Navto, reqSeq); - } - else { - this.output(undefined, CommandNames.Navto, reqSeq, "no nav items"); - } - } - 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; } + + 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; + } + 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; + } + return bakedItem; + }); } - getMatchingBrace(line: number, col: number, rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + getBraceMatching(line: number, col: number, fileName: string): ServerProtocol.TextSpan[] { + 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 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"); - } + 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; } } + + if (response) { + this.output(response, request.command, request.seq); + } + } 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()); + if (err instanceof OperationCanceledException) { + // Handle cancellation exceptions } - 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); + 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 73c3940415..95e671f561 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 ec0ee293ce..9f06e9b027 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