defer updates in project structure after file is edited

This commit is contained in:
Vladimir Matveev
2016-07-12 18:02:54 -07:00
parent bcdb06c4f7
commit 78974efe18
8 changed files with 119 additions and 35 deletions
+1 -1
View File
@@ -646,7 +646,7 @@ namespace Harness.LanguageService {
return true;
}
isVerbose() {
hasLevel() {
return false;
}
+40 -4
View File
@@ -145,6 +145,8 @@ namespace ts.server {
private readonly hostConfiguration: HostConfiguration;
private changedFiles: ScriptInfo[];
constructor(public readonly host: ServerHost,
public readonly logger: Logger,
public readonly cancellationToken: HostCancellationToken,
@@ -163,6 +165,14 @@ namespace ts.server {
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
}
getChangedFiles_TestOnly() {
return this.changedFiles;
}
ensureInferredProjectsUpToDate_TestOnly() {
this.ensureInferredProjectsUpToDate();
}
stopWatchingDirectory(directory: string) {
this.directoryWatchers.stopWatchingDirectory(directory);
}
@@ -187,7 +197,21 @@ namespace ts.server {
}
private ensureInferredProjectsUpToDate() {
if (this.changedFiles) {
let projectsToUpdate: Project[];
if (this.changedFiles.length === 1) {
// simpliest case - no allocations
projectsToUpdate = this.changedFiles[0].containingProjects;
}
else {
projectsToUpdate = [];
for (const f of this.changedFiles) {
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
}
}
this.updateProjectGraphs(projectsToUpdate);
this.changedFiles = undefined;
}
}
private findContainingConfiguredProject(info: ScriptInfo): ConfiguredProject {
@@ -532,7 +556,7 @@ namespace ts.server {
}
private printProjects() {
if (!this.logger.isVerbose()) {
if (!this.logger.hasLevel(LogLevel.verbose)) {
return;
}
@@ -970,11 +994,13 @@ namespace ts.server {
}
applyChangesInOpenFiles(openFiles: protocol.NewOpenFile[], changedFiles: protocol.ChangedOpenFile[], closedFiles: string[]): void {
const recordChangedFiles = changedFiles && !openFiles && !closedFiles;
if (openFiles) {
for (const file of openFiles) {
const scriptInfo = this.getScriptInfo(file.fileName);
Debug.assert(!scriptInfo || !scriptInfo.isOpen);
this.openClientFileWithNormalizedPath(toNormalizedPath(file.fileName), file.content);
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
this.openClientFileWithNormalizedPath(normalizedPath, file.content);
}
}
@@ -987,6 +1013,14 @@ namespace ts.server {
const change = file.changes[i];
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
}
if (recordChangedFiles) {
if (!this.changedFiles) {
this.changedFiles = [scriptInfo];
}
else if (this.changedFiles.indexOf(scriptInfo) < 0) {
this.changedFiles.push(scriptInfo);
}
}
}
}
@@ -996,7 +1030,9 @@ namespace ts.server {
}
}
if (openFiles || changedFiles || closedFiles) {
// if files were open or closed then explicitly refresh list of inferred projects
// otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update
if (openFiles || closedFiles) {
this.refreshInferredProjects();
}
}
+20 -14
View File
@@ -30,7 +30,7 @@ namespace ts.server {
constructor(private readonly logFilename: string,
private readonly traceToConsole: boolean,
private readonly level: string) {
private readonly level: LogLevel) {
}
static padStringRight(str: string, padding: string) {
@@ -66,11 +66,10 @@ namespace ts.server {
return !!this.logFilename || this.traceToConsole;
}
isVerbose() {
return this.loggingEnabled() && (this.level == "verbose");
hasLevel(level: LogLevel) {
return this.loggingEnabled() && this.level >= level;
}
msg(s: string, type: Msg.Types = Msg.Err) {
if (this.fd < 0) {
if (this.logFilename) {
@@ -88,8 +87,8 @@ namespace ts.server {
this.seq++;
this.firstInGroup = true;
}
const buf = new Buffer(s);
if (this.fd >= 0) {
const buf = new Buffer(s);
fs.writeSync(this.fd, buf, 0, buf.length, null);
}
if (this.traceToConsole) {
@@ -124,12 +123,13 @@ namespace ts.server {
interface LogOptions {
file?: string;
detailLevel?: string;
detailLevel?: LogLevel;
traceToConsole?: boolean;
logToFile?: boolean;
}
function parseLoggingEnvironmentString(logEnvStr: string): LogOptions {
const logEnv: LogOptions = {};
const logEnv: LogOptions = { logToFile: true };
const args = logEnvStr.split(" ");
for (let i = 0, len = args.length; i < (len - 1); i += 2) {
const option = args[i];
@@ -140,11 +140,15 @@ namespace ts.server {
logEnv.file = value;
break;
case "-level":
logEnv.detailLevel = value;
const level: LogLevel = (<any>LogLevel)[value];
logEnv.detailLevel = typeof level === "number" ? level : LogLevel.normal;
break;
case "-traceToConsole":
logEnv.traceToConsole = value.toLowerCase() === "true";
break;
case "-logToFile":
logEnv.logToFile = value.toLowerCase() === "true";
break;
}
}
}
@@ -154,16 +158,18 @@ namespace ts.server {
// TSS_LOG "{ level: "normal | verbose | terse", file?: string}"
function createLoggerFromEnv() {
let fileName: string = undefined;
let detailLevel = "normal";
let detailLevel = LogLevel.normal;
let traceToConsole = false;
const logEnvStr = process.env["TSS_LOG"];
if (logEnvStr) {
const logEnv = parseLoggingEnvironmentString(logEnvStr);
if (logEnv.file) {
fileName = logEnv.file;
}
else {
fileName = __dirname + "/.log" + process.pid.toString();
if (logEnv.logToFile) {
if (logEnv.file) {
fileName = logEnv.file;
}
else {
fileName = __dirname + "/.log" + process.pid.toString();
}
}
if (logEnv.detailLevel) {
detailLevel = logEnv.detailLevel;
+18 -12
View File
@@ -191,8 +191,10 @@ namespace ts.server {
}
public send(msg: protocol.Message, canCompressResponse: boolean) {
const verboseLogging = this.logger.hasLevel(LogLevel.verbose);
const json = JSON.stringify(msg);
if (this.logger.isVerbose()) {
if (verboseLogging) {
this.logger.info(msg.type + ": " + json);
}
@@ -201,9 +203,9 @@ namespace ts.server {
this.host.write(`Content-Length: ${1 + this.byteLength(json, "utf8")}\r\n\r\n${json}${this.host.newLine}`);
}
else {
const start = this.logger.isVerbose() && this.hrtime();
const start = verboseLogging && this.hrtime();
const compressed = this.compress(json);
if (this.logger.isVerbose()) {
if (verboseLogging) {
const elapsed = this.hrtime(start);
this.logger.info(`compressed message ${json.length} to ${compressed.length} in ${hrTimeToMilliseconds(elapsed)} ms using ${compressed.compressionKind}`);
}
@@ -1460,24 +1462,28 @@ namespace ts.server {
public onMessage(message: string) {
let start: number[];
if (this.logger.isVerbose()) {
this.logger.info("request: " + message);
if (this.logger.hasLevel(LogLevel.requestTime)) {
start = this.hrtime();
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`request: ${message}`);
}
}
let request: protocol.Request;
try {
request = <protocol.Request>JSON.parse(message);
const {response, responseRequired} = this.executeCommand(request);
if (this.logger.isVerbose()) {
const elapsed = this.hrtime(start);
const elapsedMs = hrTimeToMilliseconds(elapsed);
let leader = "Elapsed time (in milliseconds)";
if (!responseRequired) {
leader = "Async elapsed time (in milliseconds)";
if (this.logger.hasLevel(LogLevel.requestTime)) {
const elapsedTime = hrTimeToMilliseconds(this.hrtime(start)).toFixed(4);
if (responseRequired) {
this.logger.perftrc(`${request.seq}::${request.command}: elapsed time (in milliseconds) ${elapsedTime}`);
}
else {
this.logger.perftrc(`${request.seq}::${request.command}: async elapsed time (in milliseconds) ${elapsedTime}`);
}
this.logger.msg(leader + ": " + elapsedMs.toFixed(4).toString(), "Perf");
}
if (response) {
this.output(response, request.command, request.canCompressResponse, request.seq);
}
+8 -1
View File
@@ -1,9 +1,16 @@
/// <reference path="..\services\services.ts" />
namespace ts.server {
export enum LogLevel {
terse,
normal,
requestTime,
verbose
}
export interface Logger {
close(): void;
isVerbose(): boolean;
hasLevel(level: LogLevel): boolean;
loggingEnabled(): boolean;
perftrc(s: string): void;
info(s: string): void;
@@ -75,7 +75,7 @@ namespace ts {
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
const logger: server.Logger = {
close() { },
isVerbose: () => false,
hasLevel: () => false,
loggingEnabled: () => false,
perftrc: (s: string) => { },
info: (s: string) => { },
+1 -1
View File
@@ -29,7 +29,7 @@ namespace ts.server {
const nullCancellationToken: HostCancellationToken = { isCancellationRequested: () => false };
const mockLogger: Logger = {
close(): void {},
isVerbose(): boolean { return false; },
hasLevel(): boolean { return false; },
loggingEnabled(): boolean { return false; },
perftrc(s: string): void {},
info(s: string): void {},
+30 -1
View File
@@ -7,7 +7,7 @@ namespace ts {
const nullLogger: server.Logger = {
close: () => void 0,
isVerbose: () => void 0,
hasLevel: () => void 0,
loggingEnabled: () => false,
perftrc: () => void 0,
info: () => void 0,
@@ -1254,5 +1254,34 @@ namespace ts {
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]);
});
it("project structure update is deferred if files are not added\removed", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `import {x} from "./f2"`
};
const file2 = {
path: "/a/b/f2.ts",
content: "export let x = 1"
};
const host = createServerHost([file1, file2]);
const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useSingleInferredProject*/ false);
projectService.openClientFile(file1.path);
projectService.openClientFile(file2.path);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
projectService.applyChangesInOpenFiles(
/*openFiles*/ undefined,
/*changedFiles*/ [{ fileName: file1.path, changes: [ { span: createTextSpan(0, file1.path.length), newText: "let y = 1" } ] }],
/*closedFiles*/ undefined);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
const changedFiles = projectService.getChangedFiles_TestOnly();
assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`);
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkNumberOfProjects(projectService, { inferredProjects: 2 });
});
});
}