diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 923203c7f55..833b0064a1a 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -1187,6 +1187,8 @@ declare namespace ts.server.protocol { * Optional limit on the number of items to return. */ maxResultCount?: number; + + projectFileName?: string; } /** diff --git a/src/server/server.ts b/src/server/server.ts index 767793024c2..efd01741f86 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -14,12 +14,14 @@ namespace ts.server { }); class Logger implements ts.server.Logger { - fd = -1; - seq = 0; - inGroup = false; - firstInGroup = true; + private fd = -1; + private seq = 0; + private inGroup = false; + private firstInGroup = true; - constructor(public logFilename: string, public level: string) { + constructor(private readonly logFilename: string, + private readonly traceToConsole: boolean, + private readonly level: string) { } static padStringRight(str: string, padding: string) { @@ -52,7 +54,7 @@ namespace ts.server { } loggingEnabled() { - return !!this.logFilename; + return !!this.logFilename || this.traceToConsole; } isVerbose() { @@ -66,7 +68,7 @@ namespace ts.server { this.fd = fs.openSync(this.logFilename, "w"); } } - if (this.fd >= 0) { + if (this.fd >= 0 || this.traceToConsole) { s = s + "\n"; const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); if (this.firstInGroup) { @@ -78,7 +80,13 @@ namespace ts.server { this.firstInGroup = true; } const buf = new Buffer(s); - fs.writeSync(this.fd, buf, 0, buf.length, null); + if (this.fd >= 0) { + fs.writeSync(this.fd, buf, 0, buf.length, null); + } + if (this.traceToConsole) + { + console.warn(s); + } } } } @@ -109,6 +117,7 @@ namespace ts.server { interface LogOptions { file?: string; detailLevel?: string; + traceToConsole?: boolean; } function parseLoggingEnvironmentString(logEnvStr: string): LogOptions { @@ -125,6 +134,9 @@ namespace ts.server { case "-level": logEnv.detailLevel = value; break; + case "-traceToConsole": + logEnv.traceToConsole = value.toLowerCase() === "true"; + break; } } } @@ -135,6 +147,7 @@ namespace ts.server { function createLoggerFromEnv() { let fileName: string = undefined; let detailLevel = "normal"; + let traceToConsole = false; const logEnvStr = process.env["TSS_LOG"]; if (logEnvStr) { const logEnv = parseLoggingEnvironmentString(logEnvStr); @@ -147,8 +160,9 @@ namespace ts.server { if (logEnv.detailLevel) { detailLevel = logEnv.detailLevel; } + traceToConsole = logEnv.traceToConsole; } - return new Logger(fileName, detailLevel); + return new Logger(fileName, traceToConsole, detailLevel); } // This places log file in the directory containing editorServices.js // TODO: check that this location is writable diff --git a/src/server/session.ts b/src/server/session.ts index 5119e536f72..b5616ae2bf9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -104,9 +104,11 @@ namespace ts.server { export const Change = "change"; export const Close = "close"; export const Completions = "completions"; + export const CompletionsFull = "completions-full"; export const CompletionDetails = "completionEntryDetails"; export const Configure = "configure"; export const Definition = "definition"; + export const DefinitionFull = "definition-full"; export const Exit = "exit"; export const Format = "format"; export const Formatonkey = "formatonkey"; @@ -116,13 +118,16 @@ namespace ts.server { export const Navto = "navto"; export const Occurrences = "occurrences"; export const DocumentHighlights = "documentHighlights"; + export const DocumentHighlightsFull = "documentHighlights-full"; export const Open = "open"; export const Quickinfo = "quickinfo"; + export const QuickinfoFull = "quickinfo-full"; export const References = "references"; export const Reload = "reload"; export const Rename = "rename"; export const Saveto = "saveto"; export const SignatureHelp = "signatureHelp"; + export const SignatureHelpFull = "signatureHelp-full"; export const TypeDefinition = "typeDefinition"; export const ProjectInfo = "projectInfo"; export const ReloadProjects = "reloadProjects"; @@ -316,29 +321,34 @@ namespace ts.server { } } - private getDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { - const file = ts.normalizePath(fileName); + private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.FileSpan[] | DefinitionInfo[] { + const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); if (!project) { throw Errors.NoProject; } const scriptInfo = project.getScriptInfo(file); - const position = scriptInfo.lineOffsetToPosition(line, offset); + const position = this.getPosition(args, scriptInfo);; const definitions = project.languageService.getDefinitionAtPosition(file, position); if (!definitions) { return undefined; } - return definitions.map(def => { - const defScriptInfo = project.getScriptInfo(def.fileName); - return { - file: def.fileName, - start: defScriptInfo.positionToLineOffset(def.textSpan.start), - end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) - }; - }); + if (simplifiedResult) { + return definitions.map(def => { + const defScriptInfo = project.getScriptInfo(def.fileName); + return { + file: def.fileName, + start: defScriptInfo.positionToLineOffset(def.textSpan.start), + end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) + }; + }); + } + else { + return definitions; + } } private getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { @@ -397,8 +407,8 @@ namespace ts.server { }); } - private getDocumentHighlights(line: number, offset: number, fileName: string, filesToSearch: string[]): protocol.DocumentHighlightsItem[] { - fileName = ts.normalizePath(fileName); + private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): protocol.DocumentHighlightsItem[] | DocumentHighlights[] { + const fileName = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(fileName); if (!project) { @@ -406,15 +416,20 @@ namespace ts.server { } const scriptInfo = project.getScriptInfo(fileName); - const position = scriptInfo.lineOffsetToPosition(line, offset); + const position = this.getPosition(args, scriptInfo); - const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, filesToSearch); + const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, args.filesToSearch); if (!documentHighlights) { return undefined; } - return documentHighlights.map(convertToDocumentHighlightsItem); + if (simplifiedResult) { + return documentHighlights.map(convertToDocumentHighlightsItem); + } + else { + return documentHighlights; + } function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem { const { fileName, highlightSpans } = documentHighlights; @@ -608,7 +623,11 @@ namespace ts.server { } } - private getQuickInfo(args: protocol.FileLocationRequestArgs): protocol.QuickInfoResponseBody { + private getPosition(args: protocol.FileLocationRequestArgs, scriptInfo: ScriptInfo): number { + return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset); + } + + private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo { const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); if (!project) { @@ -616,26 +635,26 @@ namespace ts.server { } const scriptInfo = project.getScriptInfo(file); - const position = args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset); - const quickInfo = project.languageService.getQuickInfoAtPosition(file, position); + const quickInfo = project.languageService.getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo)); if (!quickInfo) { return undefined; } - if (args.position !== undefined) { - // TODO: fixme - return quickInfo; - } - const displayString = ts.displayPartsToString(quickInfo.displayParts); - const docString = ts.displayPartsToString(quickInfo.documentation); - return { - kind: quickInfo.kind, - kindModifiers: quickInfo.kindModifiers, - start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start), - end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)), - displayString: displayString, - documentation: docString, - }; + if (simplifiedResult) { + const displayString = ts.displayPartsToString(quickInfo.displayParts); + const docString = ts.displayPartsToString(quickInfo.documentation); + return { + kind: quickInfo.kind, + kindModifiers: quickInfo.kindModifiers, + start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start), + end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)), + displayString: displayString, + documentation: docString, + }; + } + else { + return quickInfo; + } } private getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] { @@ -736,44 +755,45 @@ namespace ts.server { }); } - private getCompletions(line: number, offset: number, prefix: string, fileName: string): protocol.CompletionEntry[] { - if (!prefix) { - prefix = ""; - } - const file = ts.normalizePath(fileName); + private getCompletions(args: protocol.CompletionsRequestArgs, simplifiedResult: boolean): protocol.CompletionEntry[] | CompletionInfo { + const prefix = args.prefix || ""; + const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); if (!project) { throw Errors.NoProject; } const scriptInfo = project.getScriptInfo(file); - const position = scriptInfo.lineOffsetToPosition(line, offset); + const position = this.getPosition(args, scriptInfo) const completions = project.languageService.getCompletionsAtPosition(file, position); if (!completions) { return undefined; } - - return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { - if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { - result.push(entry); - } - return result; - }, []).sort((a, b) => a.name.localeCompare(b.name)); + if (simplifiedResult) { + return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { + if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { + result.push(entry); + } + return result; + }, []).sort((a, b) => a.name.localeCompare(b.name)); + } + else { + return completions; + } } - private getCompletionEntryDetails(line: number, offset: number, - entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] { - const file = ts.normalizePath(fileName); + private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): protocol.CompletionEntryDetails[] { + const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); if (!project) { throw Errors.NoProject; } const scriptInfo = project.getScriptInfo(file); - const position = scriptInfo.lineOffsetToPosition(line, offset); + const position = this.getPosition(args, scriptInfo); - return entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => { + return args.entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => { const details = project.languageService.getCompletionEntryDetails(file, position, entryName); if (details) { accum.push(details); @@ -782,33 +802,36 @@ namespace ts.server { }, []); } - private getSignatureHelpItems(line: number, offset: number, fileName: string): protocol.SignatureHelpItems { - const file = ts.normalizePath(fileName); + private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems { + const file = ts.normalizePath(args.file); const project = this.projectService.getProjectForFile(file); if (!project) { throw Errors.NoProject; } const scriptInfo = project.getScriptInfo(file); - const position = scriptInfo.lineOffsetToPosition(line, offset); + const position = this.getPosition(args, scriptInfo); const helpItems = project.languageService.getSignatureHelpItems(file, position); if (!helpItems) { return undefined; } - const span = helpItems.applicableSpan; - const result: protocol.SignatureHelpItems = { - items: helpItems.items, - applicableSpan: { - start: scriptInfo.positionToLineOffset(span.start), - end: scriptInfo.positionToLineOffset(span.start + span.length) - }, - selectedItemIndex: helpItems.selectedItemIndex, - argumentIndex: helpItems.argumentIndex, - argumentCount: helpItems.argumentCount, - }; - - return result; + if (simplifiedResult) { + const span = helpItems.applicableSpan; + return { + items: helpItems.items, + applicableSpan: { + start: scriptInfo.positionToLineOffset(span.start), + end: scriptInfo.positionToLineOffset(span.start + span.length) + }, + selectedItemIndex: helpItems.selectedItemIndex, + argumentIndex: helpItems.argumentIndex, + argumentCount: helpItems.argumentCount, + }; + } + else { + return helpItems; + } } private getDiagnostics(delay: number, fileNames: string[]) { @@ -1075,9 +1098,11 @@ namespace ts.server { this.exit(); return this.notRequired(); }, - [CommandNames.Definition]: (request: protocol.Request) => { - const defArgs = request.arguments; - return this.requiredResponse(this.getDefinition(defArgs.line, defArgs.offset, defArgs.file)); + [CommandNames.Definition]: (request: protocol.DefinitionRequest) => { + return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => { + return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.TypeDefinition]: (request: protocol.Request) => { const defArgs = request.arguments; @@ -1112,7 +1137,10 @@ namespace ts.server { return this.notRequired(); }, [CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => { - return this.requiredResponse(this.getQuickInfo(request.arguments)); + return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.QuickinfoFull]: (request: protocol.QuickInfoRequest) => { + return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.Format]: (request: protocol.Request) => { const formatArgs = request.arguments; @@ -1122,20 +1150,20 @@ namespace ts.server { const formatOnKeyArgs = request.arguments; return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true }; }, - [CommandNames.Completions]: (request: protocol.Request) => { - const completionsArgs = request.arguments; - return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true }; + [CommandNames.Completions]: (request: protocol.CompletionDetailsRequest) => { + return this.requiredResponse(this.getCompletions(request.arguments, /*simplifiedResult*/ true)); }, - [CommandNames.CompletionDetails]: (request: protocol.Request) => { - const completionDetailsArgs = request.arguments; - return { - response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset, - completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true - }; + [CommandNames.CompletionsFull]: (request: protocol.CompletionDetailsRequest) => { + return this.requiredResponse(this.getCompletions(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.SignatureHelp]: (request: protocol.Request) => { - const signatureHelpArgs = request.arguments; - return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true }; + [CommandNames.CompletionDetails]: (request: protocol.CompletionDetailsRequest) => { + return this.requiredResponse(this.getCompletionEntryDetails(request.arguments)) + }, + [CommandNames.SignatureHelp]: (request: protocol.SignatureHelpRequest) => { + return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.SignatureHelpFull]: (request: protocol.SignatureHelpRequest) => { + return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.Geterr]: (request: protocol.Request) => { const geterrArgs = request.arguments; @@ -1188,9 +1216,11 @@ namespace ts.server { const { line, offset, file: fileName } = request.arguments; return { response: this.getOccurrences(line, offset, fileName), responseRequired: true }; }, - [CommandNames.DocumentHighlights]: (request: protocol.Request) => { - const { line, offset, file: fileName, filesToSearch } = request.arguments; - return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true }; + [CommandNames.DocumentHighlights]: (request: protocol.DocumentHighlightsRequest) => { + return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.DocumentHighlightsFull]: (request: protocol.DocumentHighlightsRequest) => { + return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.ProjectInfo]: (request: protocol.Request) => { const { file, needFileNameList } = request.arguments;