diff --git a/README.md b/README.md index 80069d8d..55660676 100644 --- a/README.md +++ b/README.md @@ -592,6 +592,7 @@ The `$/typescriptVersion` notification params include two properties: - [x] textDocument/formatting - [x] textDocument/hover - [x] textDocument/inlayHint (no support for `inlayHint/resolve` or `workspace/inlayHint/refresh`) +- [x] textDocument/linkedEditingRange - [x] textDocument/prepareCallHierarchy - [x] callHierarchy/incomingCalls - [x] callHierarchy/outgoingCalls diff --git a/package.json b/package.json index 763125fc..53435f21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-language-server", - "version": "3.3.2", + "version": "3.3.3-0", "description": "Language Server Protocol (LSP) implementation for TypeScript using tsserver", "author": "TypeFox and others", "license": "Apache-2.0", diff --git a/src/cli.ts b/src/cli.ts index f8bbe907..d4c11dff 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,6 +21,7 @@ const program = new Command('typescript-language-server') .option('--tsserver-log-verbosity ', '[deprecated] Specify a tsserver log verbosity (terse, normal, verbose). Defaults to `normal`.' + ' example: --tsserver-log-verbosity verbose') .option('--tsserver-path ', '[deprecated] Specify path to tsserver.js or the lib directory. example: --tsserver-path=/Users/me/typescript/lib/tsserver.js') + .option('--log-directory ', 'Directory for LSP logs') .parse(process.argv); const options = program.opts(); @@ -37,5 +38,6 @@ if (options.logLevel) { createLspConnection({ cmdLineTsserverPath: options.tsserverPath as string, cmdLineTsserverLogVerbosity: TsServerLogLevel.fromString(options.tsserverLogVerbosity), + logDirectory: options.logDirectory, showMessageLevel: logLevel as lsp.MessageType, }).listen(); diff --git a/src/lsp-connection.ts b/src/lsp-connection.ts index 319d605a..67096709 100644 --- a/src/lsp-connection.ts +++ b/src/lsp-connection.ts @@ -6,7 +6,7 @@ */ import lsp from 'vscode-languageserver/node.js'; -import { LspClientLogger } from './utils/logger.js'; +import { FileLogger, LspClientLogger } from './utils/logger.js'; import { LspServer } from './lsp-server.js'; import { LspClientImpl } from './lsp-client.js'; import type { TsServerLogLevel } from './utils/configuration.js'; @@ -14,13 +14,16 @@ import type { TsServerLogLevel } from './utils/configuration.js'; export interface LspConnectionOptions { cmdLineTsserverPath: string; cmdLineTsserverLogVerbosity: TsServerLogLevel; + logDirectory?: string; showMessageLevel: lsp.MessageType; } export function createLspConnection(options: LspConnectionOptions): lsp.Connection { const connection = lsp.createConnection(lsp.ProposedFeatures.all); const lspClient = new LspClientImpl(connection); - const logger = new LspClientLogger(lspClient, options.showMessageLevel); + const logger = options.logDirectory + ? new FileLogger(options.logDirectory, options.showMessageLevel) + : new LspClientLogger(lspClient, options.showMessageLevel); const server: LspServer = new LspServer({ logger, lspClient, diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 75858f2f..756cd5d1 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -961,6 +961,7 @@ export class LspServer { } } + this.logger.log(`codeAction returned with ${actions.length} actions`); return actions; } protected async getCodeFixes(fileRangeArgs: ts.server.protocol.FileRangeRequestArgs, context: lsp.CodeActionContext, token?: lsp.CancellationToken): Promise { diff --git a/src/tsServer/tracer.ts b/src/tsServer/tracer.ts index 23eff8a2..6dfbf4db 100644 --- a/src/tsServer/tracer.ts +++ b/src/tsServer/tracer.ts @@ -68,7 +68,7 @@ export default class Tracer { if (this.trace === Trace.Verbose && response.body) { data = `Result: ${JSON.stringify(response.body, null, 4)}`; } - this.logTrace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success} ${!response.success ? `. Message: ${response.message}` : ''}`, data); + this.logTrace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms. Success: ${response.success}${!response.success ? `. Message: ${response.message}` : ''}`, data); } public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index f177f515..2d562e8e 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -11,6 +11,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-qualifier */ +import path from 'path'; +import fs from 'fs-extra'; import lsp from 'vscode-languageserver'; import type { LspClient } from '../lsp-client.js'; @@ -180,6 +182,82 @@ export class ConsoleLogger implements Logger { } } +export class FileLogger implements Logger { + private logFile: fs.WriteStream; + + constructor( + private logDirectory: string, + private level: lsp.MessageType, + ) { + console.error('Resolved logDirectory', path.resolve(this.logDirectory)); + if (!fs.pathExistsSync(this.logDirectory)) { + fs.mkdirSync(this.logDirectory); + } + this.logFile = fs.createWriteStream(path.join(this.logDirectory, 'debug.log'), { flags : 'w' }); + } + + private sendMessage(severity: lsp.MessageType, args: any[], options?: { overrideLevel?: boolean; }): void { + if (this.level >= severity || options?.overrideLevel) { + const [prefix, firstArg, ...rest] = this.toStrings(...args); + this.logFile.write(`${prefix} ${firstArg}`); + if (rest !== undefined) { + this.logFile.write(`\n${rest.join('\n')}`); + } + this.logFile.write('\n\n'); + } + } + + private toStrings(...args: any[]): string[] { + return args.map(a => { + const out = typeof a === 'string' ? a : JSON.stringify(a, null, 2); + if (out && out.length > 1000) { + return ` ${out.slice(0, 1000)}...`; + } + return out; + }); + } + + error(...args: any[]): void { + this.sendMessage(lsp.MessageType.Error, args); + } + + warn(...args: any[]): void { + this.sendMessage(lsp.MessageType.Warning, args); + } + + info(...args: any[]): void { + this.sendMessage(lsp.MessageType.Info, args); + } + + log(...args: any[]): void { + this.sendMessage(lsp.MessageType.Log, args); + } + + logIgnoringVerbosity(level: LogLevel, ...args: any[]): void { + this.sendMessage(this.logLevelToLspMessageType(level), args, { overrideLevel: true }); + } + + trace(level: TraceLevel, message: string, data?: any): void { + this.logIgnoringVerbosity(LogLevel.Log, `[${level} - ${now()}] ${message}`); + if (data) { + this.logIgnoringVerbosity(LogLevel.Log, data2String(data)); + } + } + + private logLevelToLspMessageType(level: LogLevel): lsp.MessageType { + switch (level) { + case LogLevel.Log: + return lsp.MessageType.Log; + case LogLevel.Info: + return lsp.MessageType.Info; + case LogLevel.Warning: + return lsp.MessageType.Warning; + case LogLevel.Error: + return lsp.MessageType.Error; + } + } +} + export class PrefixingLogger implements Logger { constructor( private logger: Logger, @@ -207,10 +285,7 @@ export class PrefixingLogger implements Logger { } trace(level: TraceLevel, message: string, data?: any): void { - this.logIgnoringVerbosity(LogLevel.Log, this.prefix, `[${level} - ${now()}] ${message}`); - if (data) { - this.logIgnoringVerbosity(LogLevel.Log, this.prefix, data2String(data)); - } + this.logIgnoringVerbosity(LogLevel.Log, `[${level} - ${now()}] ${message}`, data ? data2String(data) : undefined); } }