Skip to content

Commit 78974ef

Browse files
committed
defer updates in project structure after file is edited
1 parent bcdb06c commit 78974ef

8 files changed

Lines changed: 119 additions & 35 deletions

File tree

src/harness/harnessLanguageService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ namespace Harness.LanguageService {
646646
return true;
647647
}
648648

649-
isVerbose() {
649+
hasLevel() {
650650
return false;
651651
}
652652

src/server/editorServices.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ namespace ts.server {
145145

146146
private readonly hostConfiguration: HostConfiguration;
147147

148+
private changedFiles: ScriptInfo[];
149+
148150
constructor(public readonly host: ServerHost,
149151
public readonly logger: Logger,
150152
public readonly cancellationToken: HostCancellationToken,
@@ -163,6 +165,14 @@ namespace ts.server {
163165
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
164166
}
165167

168+
getChangedFiles_TestOnly() {
169+
return this.changedFiles;
170+
}
171+
172+
ensureInferredProjectsUpToDate_TestOnly() {
173+
this.ensureInferredProjectsUpToDate();
174+
}
175+
166176
stopWatchingDirectory(directory: string) {
167177
this.directoryWatchers.stopWatchingDirectory(directory);
168178
}
@@ -187,7 +197,21 @@ namespace ts.server {
187197
}
188198

189199
private ensureInferredProjectsUpToDate() {
190-
200+
if (this.changedFiles) {
201+
let projectsToUpdate: Project[];
202+
if (this.changedFiles.length === 1) {
203+
// simpliest case - no allocations
204+
projectsToUpdate = this.changedFiles[0].containingProjects;
205+
}
206+
else {
207+
projectsToUpdate = [];
208+
for (const f of this.changedFiles) {
209+
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
210+
}
211+
}
212+
this.updateProjectGraphs(projectsToUpdate);
213+
this.changedFiles = undefined;
214+
}
191215
}
192216

193217
private findContainingConfiguredProject(info: ScriptInfo): ConfiguredProject {
@@ -532,7 +556,7 @@ namespace ts.server {
532556
}
533557

534558
private printProjects() {
535-
if (!this.logger.isVerbose()) {
559+
if (!this.logger.hasLevel(LogLevel.verbose)) {
536560
return;
537561
}
538562

@@ -970,11 +994,13 @@ namespace ts.server {
970994
}
971995

972996
applyChangesInOpenFiles(openFiles: protocol.NewOpenFile[], changedFiles: protocol.ChangedOpenFile[], closedFiles: string[]): void {
997+
const recordChangedFiles = changedFiles && !openFiles && !closedFiles;
973998
if (openFiles) {
974999
for (const file of openFiles) {
9751000
const scriptInfo = this.getScriptInfo(file.fileName);
9761001
Debug.assert(!scriptInfo || !scriptInfo.isOpen);
977-
this.openClientFileWithNormalizedPath(toNormalizedPath(file.fileName), file.content);
1002+
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
1003+
this.openClientFileWithNormalizedPath(normalizedPath, file.content);
9781004
}
9791005
}
9801006

@@ -987,6 +1013,14 @@ namespace ts.server {
9871013
const change = file.changes[i];
9881014
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
9891015
}
1016+
if (recordChangedFiles) {
1017+
if (!this.changedFiles) {
1018+
this.changedFiles = [scriptInfo];
1019+
}
1020+
else if (this.changedFiles.indexOf(scriptInfo) < 0) {
1021+
this.changedFiles.push(scriptInfo);
1022+
}
1023+
}
9901024
}
9911025
}
9921026

@@ -996,7 +1030,9 @@ namespace ts.server {
9961030
}
9971031
}
9981032

999-
if (openFiles || changedFiles || closedFiles) {
1033+
// if files were open or closed then explicitly refresh list of inferred projects
1034+
// otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update
1035+
if (openFiles || closedFiles) {
10001036
this.refreshInferredProjects();
10011037
}
10021038
}

src/server/server.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace ts.server {
3030

3131
constructor(private readonly logFilename: string,
3232
private readonly traceToConsole: boolean,
33-
private readonly level: string) {
33+
private readonly level: LogLevel) {
3434
}
3535

3636
static padStringRight(str: string, padding: string) {
@@ -66,11 +66,10 @@ namespace ts.server {
6666
return !!this.logFilename || this.traceToConsole;
6767
}
6868

69-
isVerbose() {
70-
return this.loggingEnabled() && (this.level == "verbose");
69+
hasLevel(level: LogLevel) {
70+
return this.loggingEnabled() && this.level >= level;
7171
}
7272

73-
7473
msg(s: string, type: Msg.Types = Msg.Err) {
7574
if (this.fd < 0) {
7675
if (this.logFilename) {
@@ -88,8 +87,8 @@ namespace ts.server {
8887
this.seq++;
8988
this.firstInGroup = true;
9089
}
91-
const buf = new Buffer(s);
9290
if (this.fd >= 0) {
91+
const buf = new Buffer(s);
9392
fs.writeSync(this.fd, buf, 0, buf.length, null);
9493
}
9594
if (this.traceToConsole) {
@@ -124,12 +123,13 @@ namespace ts.server {
124123

125124
interface LogOptions {
126125
file?: string;
127-
detailLevel?: string;
126+
detailLevel?: LogLevel;
128127
traceToConsole?: boolean;
128+
logToFile?: boolean;
129129
}
130130

131131
function parseLoggingEnvironmentString(logEnvStr: string): LogOptions {
132-
const logEnv: LogOptions = {};
132+
const logEnv: LogOptions = { logToFile: true };
133133
const args = logEnvStr.split(" ");
134134
for (let i = 0, len = args.length; i < (len - 1); i += 2) {
135135
const option = args[i];
@@ -140,11 +140,15 @@ namespace ts.server {
140140
logEnv.file = value;
141141
break;
142142
case "-level":
143-
logEnv.detailLevel = value;
143+
const level: LogLevel = (<any>LogLevel)[value];
144+
logEnv.detailLevel = typeof level === "number" ? level : LogLevel.normal;
144145
break;
145146
case "-traceToConsole":
146147
logEnv.traceToConsole = value.toLowerCase() === "true";
147148
break;
149+
case "-logToFile":
150+
logEnv.logToFile = value.toLowerCase() === "true";
151+
break;
148152
}
149153
}
150154
}
@@ -154,16 +158,18 @@ namespace ts.server {
154158
// TSS_LOG "{ level: "normal | verbose | terse", file?: string}"
155159
function createLoggerFromEnv() {
156160
let fileName: string = undefined;
157-
let detailLevel = "normal";
161+
let detailLevel = LogLevel.normal;
158162
let traceToConsole = false;
159163
const logEnvStr = process.env["TSS_LOG"];
160164
if (logEnvStr) {
161165
const logEnv = parseLoggingEnvironmentString(logEnvStr);
162-
if (logEnv.file) {
163-
fileName = logEnv.file;
164-
}
165-
else {
166-
fileName = __dirname + "/.log" + process.pid.toString();
166+
if (logEnv.logToFile) {
167+
if (logEnv.file) {
168+
fileName = logEnv.file;
169+
}
170+
else {
171+
fileName = __dirname + "/.log" + process.pid.toString();
172+
}
167173
}
168174
if (logEnv.detailLevel) {
169175
detailLevel = logEnv.detailLevel;

src/server/session.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,10 @@ namespace ts.server {
191191
}
192192

193193
public send(msg: protocol.Message, canCompressResponse: boolean) {
194+
const verboseLogging = this.logger.hasLevel(LogLevel.verbose);
195+
194196
const json = JSON.stringify(msg);
195-
if (this.logger.isVerbose()) {
197+
if (verboseLogging) {
196198
this.logger.info(msg.type + ": " + json);
197199
}
198200

@@ -201,9 +203,9 @@ namespace ts.server {
201203
this.host.write(`Content-Length: ${1 + this.byteLength(json, "utf8")}\r\n\r\n${json}${this.host.newLine}`);
202204
}
203205
else {
204-
const start = this.logger.isVerbose() && this.hrtime();
206+
const start = verboseLogging && this.hrtime();
205207
const compressed = this.compress(json);
206-
if (this.logger.isVerbose()) {
208+
if (verboseLogging) {
207209
const elapsed = this.hrtime(start);
208210
this.logger.info(`compressed message ${json.length} to ${compressed.length} in ${hrTimeToMilliseconds(elapsed)} ms using ${compressed.compressionKind}`);
209211
}
@@ -1460,24 +1462,28 @@ namespace ts.server {
14601462

14611463
public onMessage(message: string) {
14621464
let start: number[];
1463-
if (this.logger.isVerbose()) {
1464-
this.logger.info("request: " + message);
1465+
if (this.logger.hasLevel(LogLevel.requestTime)) {
14651466
start = this.hrtime();
1467+
if (this.logger.hasLevel(LogLevel.verbose)) {
1468+
this.logger.info(`request: ${message}`);
1469+
}
14661470
}
1471+
14671472
let request: protocol.Request;
14681473
try {
14691474
request = <protocol.Request>JSON.parse(message);
14701475
const {response, responseRequired} = this.executeCommand(request);
14711476

1472-
if (this.logger.isVerbose()) {
1473-
const elapsed = this.hrtime(start);
1474-
const elapsedMs = hrTimeToMilliseconds(elapsed);
1475-
let leader = "Elapsed time (in milliseconds)";
1476-
if (!responseRequired) {
1477-
leader = "Async elapsed time (in milliseconds)";
1477+
if (this.logger.hasLevel(LogLevel.requestTime)) {
1478+
const elapsedTime = hrTimeToMilliseconds(this.hrtime(start)).toFixed(4);
1479+
if (responseRequired) {
1480+
this.logger.perftrc(`${request.seq}::${request.command}: elapsed time (in milliseconds) ${elapsedTime}`);
1481+
}
1482+
else {
1483+
this.logger.perftrc(`${request.seq}::${request.command}: async elapsed time (in milliseconds) ${elapsedTime}`);
14781484
}
1479-
this.logger.msg(leader + ": " + elapsedMs.toFixed(4).toString(), "Perf");
14801485
}
1486+
14811487
if (response) {
14821488
this.output(response, request.command, request.canCompressResponse, request.seq);
14831489
}

src/server/utilities.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
/// <reference path="..\services\services.ts" />
22

33
namespace ts.server {
4+
export enum LogLevel {
5+
terse,
6+
normal,
7+
requestTime,
8+
verbose
9+
}
10+
411
export interface Logger {
512
close(): void;
6-
isVerbose(): boolean;
13+
hasLevel(level: LogLevel): boolean;
714
loggingEnabled(): boolean;
815
perftrc(s: string): void;
916
info(s: string): void;

tests/cases/unittests/cachingInServerLSHost.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ namespace ts {
7575
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
7676
const logger: server.Logger = {
7777
close() { },
78-
isVerbose: () => false,
78+
hasLevel: () => false,
7979
loggingEnabled: () => false,
8080
perftrc: (s: string) => { },
8181
info: (s: string) => { },

tests/cases/unittests/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace ts.server {
2929
const nullCancellationToken: HostCancellationToken = { isCancellationRequested: () => false };
3030
const mockLogger: Logger = {
3131
close(): void {},
32-
isVerbose(): boolean { return false; },
32+
hasLevel(): boolean { return false; },
3333
loggingEnabled(): boolean { return false; },
3434
perftrc(s: string): void {},
3535
info(s: string): void {},

tests/cases/unittests/tsserverProjectSystem.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace ts {
77

88
const nullLogger: server.Logger = {
99
close: () => void 0,
10-
isVerbose: () => void 0,
10+
hasLevel: () => void 0,
1111
loggingEnabled: () => false,
1212
perftrc: () => void 0,
1313
info: () => void 0,
@@ -1254,5 +1254,34 @@ namespace ts {
12541254
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
12551255
checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]);
12561256
});
1257+
1258+
it("project structure update is deferred if files are not added\removed", () => {
1259+
const file1 = {
1260+
path: "/a/b/f1.ts",
1261+
content: `import {x} from "./f2"`
1262+
};
1263+
const file2 = {
1264+
path: "/a/b/f2.ts",
1265+
content: "export let x = 1"
1266+
};
1267+
const host = createServerHost([file1, file2]);
1268+
const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useSingleInferredProject*/ false);
1269+
1270+
projectService.openClientFile(file1.path);
1271+
projectService.openClientFile(file2.path);
1272+
1273+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1274+
projectService.applyChangesInOpenFiles(
1275+
/*openFiles*/ undefined,
1276+
/*changedFiles*/ [{ fileName: file1.path, changes: [ { span: createTextSpan(0, file1.path.length), newText: "let y = 1" } ] }],
1277+
/*closedFiles*/ undefined);
1278+
1279+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1280+
const changedFiles = projectService.getChangedFiles_TestOnly();
1281+
assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`);
1282+
1283+
projectService.ensureInferredProjectsUpToDate_TestOnly();
1284+
checkNumberOfProjects(projectService, { inferredProjects: 2 });
1285+
});
12571286
});
12581287
}

0 commit comments

Comments
 (0)