From 8bef6d7ebc6a92aeabfe7b438f7a302ab3e89263 Mon Sep 17 00:00:00 2001 From: steveluc Date: Sat, 7 Mar 2015 15:44:21 -0800 Subject: [PATCH 1/7] Added additional cases for format on enter. These cases fix bugs in the orginal format on enter (which wasn't distinguishing whether there was existing whitespace to start some types of lines). --- src/server/session.ts | 48 ++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 7e9c6972a1f..5a91bcc08a1 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -463,17 +463,41 @@ module ts.server { 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 }); + var scriptInfo = compilerService.host.getScriptInfo(file); + if (scriptInfo) { + var lineInfo = scriptInfo.getLineInfo(line); + if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) { + var lineText = lineInfo.leaf.text; + if (lineText.search("\\S") < 0) { + // 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); + for (var i = 0, len = lineText.length; i < len; i++) { + if (lineText.charAt(i) == " ") { + indentPosition--; + } + else { + break; + } + } + if (indentPosition > 0) { + var spaces = generateSpaces(indentPosition); + edits.push({ span: ts.createTextSpanFromBounds(position, position), newText: spaces }); + } + else if (indentPosition < 0) { + edits.push({ + span: ts.createTextSpanFromBounds(position, position - indentPosition), + newText: "" + }); + } + } + } } } @@ -491,7 +515,7 @@ module ts.server { }; }); } - + getCompletions(line: number, col: number, prefix: string, fileName: string): protocol.CompletionEntry[] { if (!prefix) { prefix = ""; From 9a45160ab64da5d1121aa806a8fb49fe01e3eb05 Mon Sep 17 00:00:00 2001 From: steveluc Date: Sat, 7 Mar 2015 17:13:52 -0800 Subject: [PATCH 2/7] Changed TypeScript server logging to use an environment variable TSS_LOG as follows. If TSS_LOG is not set, no logging will occur. If TSS_LOG is set to any value, logging will occur as before this change (log file will be in directory of tsserver.js with name .logPID where PID is the process id of the server process; log will contain the pre-change messages). If TSS_LOG is set to a string that has the form "-file fileName", the log file will be in the tsserver.js directory with name 'fileName'. If TSS_LOG is set to a string that has the form "-level levelName", then the level of logging will be set to 'levelName'. Currently the two levels are 'normal' and 'verbose'. The TSS_LOG string can contain zero, one or both of its options as in "-file LOG -level verbose". At the verbose level, the server will log every request, response and event, and will also give elapsed time for message processing. --- src/server/editorServices.ts | 2 ++ src/server/server.ts | 64 +++++++++++++++++++++++++++++++++--- src/server/session.ts | 17 +++++++++- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5ca4cb4d00f..3cb66fed297 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -5,6 +5,8 @@ module ts.server { export interface Logger { close(): void; + verbose(): boolean; + enabled(): boolean; perftrc(s: string): void; info(s: string): void; startGroup(): void; diff --git a/src/server/server.ts b/src/server/server.ts index c48ba951347..e5cc0fd398d 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -19,7 +19,7 @@ module ts.server { inGroup = false; firstInGroup = true; - constructor(public logFilename: string) { + constructor(public logFilename: string, public level: string) { } static padStringRight(str: string, padding: string) { @@ -51,9 +51,20 @@ module ts.server { this.firstInGroup = true; } + enabled() { + return !!this.logFilename; + } + + verbose() { + return this.enabled() && (this.level == "verbose"); + } + + msg(s: string, type = "Err") { if (this.fd < 0) { - this.fd = fs.openSync(this.logFilename, "w"); + if (this.logFilename) { + this.fd = fs.openSync(this.logFilename, "w"); + } } if (this.fd >= 0) { s = s + "\n"; @@ -173,17 +184,62 @@ module ts.server { }); rl.on('close',() => { - this.projectService.closeLog(); this.projectService.log("Exiting..."); + this.projectService.closeLog(); process.exit(0); }); } } + interface LogEnv { + file?: string; + level?: string; + } + + function parseLogEnv(logEnvStr: string): LogEnv { + var logEnv: LogEnv = {}; + var args = logEnvStr.split(' '); + for (var i = 0, len = args.length; i < len; i++) { + var option = args[i]; + var value = args[i + 1]; + if (option && value) { + switch (option) { + case "-file": + logEnv.file = value; + break; + case "-level": + logEnv.level = value; + break; + } + } + } + return logEnv; + } + + // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" + function createLoggerFromEnv() { + var fileName: string = undefined; + var level = "normal"; + var logEnvStr = process.env["TSS_LOG"]; + if (logEnvStr) { + var logEnv = parseLogEnv(logEnvStr); + if (logEnv.file) { + fileName = logEnv.file; + } + else { + fileName = __dirname + "/.log" + process.pid.toString(); + } + if (logEnv.level) { + level = logEnv.level; + } + } + var logger = new Logger(fileName, level); + return logger; + } // 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 logger = createLoggerFromEnv(); // REVIEW: for now this implementation uses polling. // The advantage of polling is that it works reliably diff --git a/src/server/session.ts b/src/server/session.ts index 5a91bcc08a1..59a049a18e9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -145,6 +145,9 @@ module ts.server { send(msg: NodeJS._debugger.Message) { var json = JSON.stringify(msg); + if (this.logger.verbose()) { + this.logger.info(msg.type+": " + json); + } this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) + '\r\n\r\n' + json); } @@ -717,6 +720,10 @@ module ts.server { } onMessage(message: string) { + if (this.logger.verbose()) { + this.logger.info("request: " + message); + var start = process.hrtime(); + } try { var request = JSON.parse(message); var response: any; @@ -822,13 +829,21 @@ module ts.server { } } + if (this.logger.verbose()) { + var elapsed = process.hrtime(start); + var elapsedms = ((1e9 * elapsed[0]) + elapsed[1])/1000000.0; + var leader = "Elapsed time (in milliseconds)"; + if (!responseRequired) { + leader = "Async elapsed time (in milliseconds)"; + } + this.logger.msg(leader+": " + elapsedms.toFixed(4).toString(), "Perf"); + } if (response) { this.output(response, request.command, request.seq); } else if (responseRequired) { this.output(undefined, request.command, request.seq, "No content available."); } - } catch (err) { if (err instanceof OperationCanceledException) { // Handle cancellation exceptions From 865802a63cf88bdea46938fc99fa4f13f2e0e61c Mon Sep 17 00:00:00 2001 From: steveluc Date: Sat, 7 Mar 2015 17:46:56 -0800 Subject: [PATCH 3/7] Added new logger methods to test harness. --- src/harness/harnessLanguageService.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4206f16bab3..202a167f089 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -469,6 +469,7 @@ module Harness.LanguageService { this.writeMessage(message); } + readFile(fileName: string): string { if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) { fileName = Harness.Compiler.defaultLibFileName; @@ -526,6 +527,15 @@ module Harness.LanguageService { msg(message: string) { return this.host.log(message); } + + enabled() { + return true; + } + + verbose() { + return false; + } + endGroup(): void { } From 360e47880ebc8c43060284aec99ddb87e20f9ca9 Mon Sep 17 00:00:00 2001 From: steveluc Date: Sat, 7 Mar 2015 23:19:58 -0800 Subject: [PATCH 4/7] Addressed comments on code style and organization. --- src/harness/harnessLanguageService.ts | 2 +- src/server/editorServices.ts | 2 +- src/server/server.ts | 23 +++++++++++------------ src/server/session.ts | 14 ++++++++------ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 202a167f089..55b3464a384 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -532,7 +532,7 @@ module Harness.LanguageService { return true; } - verbose() { + isVerbose() { return false; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3cb66fed297..970286f29fa 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -5,7 +5,7 @@ module ts.server { export interface Logger { close(): void; - verbose(): boolean; + isVerbose(): boolean; enabled(): boolean; perftrc(s: string): void; info(s: string): void; diff --git a/src/server/server.ts b/src/server/server.ts index e5cc0fd398d..1735a2dddec 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -55,7 +55,7 @@ module ts.server { return !!this.logFilename; } - verbose() { + isVerbose() { return this.enabled() && (this.level == "verbose"); } @@ -191,15 +191,15 @@ module ts.server { } } - interface LogEnv { + interface LogOptions { file?: string; - level?: string; + detailLevel?: string; } - function parseLogEnv(logEnvStr: string): LogEnv { - var logEnv: LogEnv = {}; + function parseLogEnv(logEnvStr: string): LogOptions { + var logEnv: LogOptions = {}; var args = logEnvStr.split(' '); - for (var i = 0, len = args.length; i < len; i++) { + for (var i = 0, len = args.length; i < (len - 1); i += 2) { var option = args[i]; var value = args[i + 1]; if (option && value) { @@ -208,7 +208,7 @@ module ts.server { logEnv.file = value; break; case "-level": - logEnv.level = value; + logEnv.detailLevel = value; break; } } @@ -219,7 +219,7 @@ module ts.server { // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" function createLoggerFromEnv() { var fileName: string = undefined; - var level = "normal"; + var detailLevel = "normal"; var logEnvStr = process.env["TSS_LOG"]; if (logEnvStr) { var logEnv = parseLogEnv(logEnvStr); @@ -229,12 +229,11 @@ module ts.server { else { fileName = __dirname + "/.log" + process.pid.toString(); } - if (logEnv.level) { - level = logEnv.level; + if (logEnv.detailLevel) { + detailLevel = logEnv.detailLevel; } } - var logger = new Logger(fileName, level); - return logger; + return new Logger(fileName, 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 59a049a18e9..ee828642830 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -145,8 +145,8 @@ module ts.server { send(msg: NodeJS._debugger.Message) { var json = JSON.stringify(msg); - if (this.logger.verbose()) { - this.logger.info(msg.type+": " + json); + if (this.logger.isVerbose()) { + this.logger.info(msg.type + ": " + json); } this.sendLineToClient('Content-Length: ' + (1 + Buffer.byteLength(json, 'utf8')) + '\r\n\r\n' + json); @@ -720,7 +720,7 @@ module ts.server { } onMessage(message: string) { - if (this.logger.verbose()) { + if (this.logger.isVerbose()) { this.logger.info("request: " + message); var start = process.hrtime(); } @@ -829,14 +829,16 @@ module ts.server { } } - if (this.logger.verbose()) { + if (this.logger.isVerbose()) { var elapsed = process.hrtime(start); - var elapsedms = ((1e9 * elapsed[0]) + elapsed[1])/1000000.0; + var seconds = elapsed[0] + var nanoseconds = elapsed[1]; + var elapsedMs = ((1e9 * seconds) + nanoseconds)/1000000.0; var leader = "Elapsed time (in milliseconds)"; if (!responseRequired) { leader = "Async elapsed time (in milliseconds)"; } - this.logger.msg(leader+": " + elapsedms.toFixed(4).toString(), "Perf"); + this.logger.msg(leader + ": " + elapsedMs.toFixed(4).toString(), "Perf"); } if (response) { this.output(response, request.command, request.seq); From 29c06f9d6afdf3b5f4d1bf396032de659d68fb3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Mar 2015 22:35:28 -0700 Subject: [PATCH 5/7] Move variable closer to where it is used. --- src/compiler/emitter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c7f132519ae..972e2fbfad4 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1531,7 +1531,6 @@ module ts { var sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap ? [] : undefined; var diagnostics: Diagnostic[] = []; var newLine = host.getNewLine(); - var preserveNewLines = compilerOptions.preserveNewLines || false; if (targetSourceFile === undefined) { forEach(host.getSourceFiles(), sourceFile => { @@ -1572,6 +1571,7 @@ module ts { var writeLine = writer.writeLine; var increaseIndent = writer.increaseIndent; var decreaseIndent = writer.decreaseIndent; + var preserveNewLines = compilerOptions.preserveNewLines || false; var currentSourceFile: SourceFile; @@ -3034,10 +3034,10 @@ module ts { } function indentIfOnDifferentLines(parent: Node, node1: Node, node2: Node) { - // Use a newline for existin code if the original had one, and we're preserving formatting. + // Use a newline for existing code if the original had one, and we're preserving formatting. var realNodesAreOnDifferentLines = preserveNewLines && !nodeIsSynthesized(parent) && !nodeEndIsOnSameLineAsNodeStart(node1, node2); - // Always use a newline for synthesized code if hte generator asked for it. + // Always use a newline for synthesized code if the synthesizer desires it. var synthesizedNodeIsOnDifferentLine = synthesizedNodeStartsOnNewLine(node2); if (realNodesAreOnDifferentLines || synthesizedNodeIsOnDifferentLine) { From 04320e415e68841f3a517bb0a3616c78908750f2 Mon Sep 17 00:00:00 2001 From: steveluc Date: Mon, 9 Mar 2015 01:23:03 -0700 Subject: [PATCH 6/7] Renames and comments as requested by feedback. --- src/harness/harnessLanguageService.ts | 2 +- src/server/editorServices.ts | 2 +- src/server/server.ts | 8 ++++---- src/server/session.ts | 8 +++++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 55b3464a384..68ac8d916fb 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -528,7 +528,7 @@ module Harness.LanguageService { return this.host.log(message); } - enabled() { + loggingEnabled() { return true; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 970286f29fa..dd5f74cfff2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -6,7 +6,7 @@ module ts.server { export interface Logger { close(): void; isVerbose(): boolean; - enabled(): boolean; + loggingEnabled(): boolean; perftrc(s: string): void; info(s: string): void; startGroup(): void; diff --git a/src/server/server.ts b/src/server/server.ts index 1735a2dddec..4c13be80c4c 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -51,12 +51,12 @@ module ts.server { this.firstInGroup = true; } - enabled() { + loggingEnabled() { return !!this.logFilename; } isVerbose() { - return this.enabled() && (this.level == "verbose"); + return this.loggingEnabled() && (this.level == "verbose"); } @@ -196,7 +196,7 @@ module ts.server { detailLevel?: string; } - function parseLogEnv(logEnvStr: string): LogOptions { + function parseLoggingEnvironmentString(logEnvStr: string): LogOptions { var logEnv: LogOptions = {}; var args = logEnvStr.split(' '); for (var i = 0, len = args.length; i < (len - 1); i += 2) { @@ -222,7 +222,7 @@ module ts.server { var detailLevel = "normal"; var logEnvStr = process.env["TSS_LOG"]; if (logEnvStr) { - var logEnv = parseLogEnv(logEnvStr); + var logEnv = parseLoggingEnvironmentString(logEnvStr); if (logEnv.file) { fileName = logEnv.file; } diff --git a/src/server/session.ts b/src/server/session.ts index ee828642830..cc705d64d5e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -464,7 +464,13 @@ 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); + // Check whether we should auto-indent. This will be when + // the position is on a line containing only whitespace. + // This should leave the edits returned from + // getFormattingEditsAfterKeytroke either empty or pertaining + // only to the previous line. If all this is true, then + // add edits necessary to properly indent the current line. if ((key == "\n") && ((!edits) || (edits.length == 0) || allEditsBeforePos(edits, position))) { var scriptInfo = compilerService.host.getScriptInfo(file); if (scriptInfo) { From 37c6f69f015c3da3c709e2bae0786aa55a10a045 Mon Sep 17 00:00:00 2001 From: steveluc Date: Mon, 9 Mar 2015 11:57:50 -0700 Subject: [PATCH 7/7] Hold at most 4 snapshots per file in the server. This is about 1K per file. --- src/server/editorServices.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index dd5f74cfff2..7c9ccfa7854 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1073,6 +1073,7 @@ module ts.server { static changeNumberThreshold = 8; static changeLengthThreshold = 256; + static maxVersions = 8; // REVIEW: can optimize by coalescing simple edits edit(pos: number, deleteLen: number, insertedText?: string) { @@ -1133,6 +1134,13 @@ module ts.server { this.currentVersion = snap.version; this.versions[snap.version] = snap; this.changes = []; + if ((this.currentVersion - this.minVersion) >= ScriptVersionCache.maxVersions) { + var oldMin = this.minVersion; + this.minVersion = (this.currentVersion - ScriptVersionCache.maxVersions) + 1; + for (var j = oldMin; j < this.minVersion; j++) { + this.versions[j] = undefined; + } + } } return snap; }