mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
defer updates in project structure after file is edited
This commit is contained in:
@@ -646,7 +646,7 @@ namespace Harness.LanguageService {
|
||||
return true;
|
||||
}
|
||||
|
||||
isVerbose() {
|
||||
hasLevel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) => { },
|
||||
|
||||
@@ -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 {},
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user