diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 4bd4801ce8f..112eca82926 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1173,6 +1173,21 @@ namespace ts { }}; } + export function arrayReverseIterator(array: ReadonlyArray): Iterator { + let i = array.length; + return { + next: () => { + if (i === 0) { + return { value: undefined as never, done: true }; + } + else { + i--; + return { value: array[i], done: false }; + } + } + }; + } + /** * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e9d97f6e03c..10939b68114 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -407,6 +407,21 @@ namespace ts.server { } } + /*@internal*/ + export interface OpenFileArguments { + fileName: string; + content?: string; + scriptKind?: protocol.ScriptKindName | ScriptKind; + hasMixedContent?: boolean; + projectRootPath?: string; + } + + /*@internal*/ + export interface ChangeFileArguments { + fileName: string; + changes: Iterator; + } + export class ProjectService { /*@internal*/ @@ -1128,11 +1143,22 @@ namespace ts.server { return project; } + private assignOrphanScriptInfosToInferredProject() { + // collect orphaned files and assign them to inferred project just like we treat open of a file + this.openFiles.forEach((projectRootPath, path) => { + const info = this.getScriptInfoForPath(path as Path)!; + // collect all orphaned script infos from open files + if (info.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); + } + }); + } + /** * Remove this file from the set of open, non-configured files. * @param info The file that has been closed or newly configured */ - private closeOpenFile(info: ScriptInfo): void { + private closeOpenFile(info: ScriptInfo, skipAssignOrphanScriptInfosToInferredProject?: true) { // Closing file should trigger re-reading the file content from disk. This is // because the user may chose to discard the buffer content before saving // to the disk, and the server's version of the file can be out of sync. @@ -1176,15 +1202,8 @@ namespace ts.server { this.openFiles.delete(info.path); - if (ensureProjectsForOpenFiles) { - // collect orphaned files and assign them to inferred project just like we treat open of a file - this.openFiles.forEach((projectRootPath, path) => { - const info = this.getScriptInfoForPath(path as Path)!; - // collect all orphaned script infos from open files - if (info.isOrphan()) { - this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); - } - }); + if (!skipAssignOrphanScriptInfosToInferredProject && ensureProjectsForOpenFiles) { + this.assignOrphanScriptInfosToInferredProject(); } // Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project) @@ -1199,6 +1218,8 @@ namespace ts.server { else { this.handleDeletedFile(info); } + + return ensureProjectsForOpenFiles; } private deleteScriptInfo(info: ScriptInfo) { @@ -2570,20 +2591,22 @@ namespace ts.server { }); } - openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { + private getOrCreateOpenScriptInfo(fileName: NormalizedPath, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, projectRootPath: NormalizedPath | undefined) { + const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217 + this.openFiles.set(info.path, projectRootPath); + return info; + } + + private assignProjectToOpenedScriptInfo(info: ScriptInfo): OpenConfiguredProjectResult { let configFileName: NormalizedPath | undefined; let configFileErrors: ReadonlyArray | undefined; - - const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217 - - this.openFiles.set(info.path, projectRootPath); let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info); if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization configFileName = this.getConfigFileNameForFile(info); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`); + project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${info.fileName} to open`); // Send the event only if the project got created as part of this open request and info is part of the project if (info.isOrphan()) { // Since the file isnt part of configured project, do not send config file info @@ -2591,7 +2614,7 @@ namespace ts.server { } else { configFileErrors = project.getAllProjectErrors(); - this.sendConfigFileDiagEvent(project, fileName); + this.sendConfigFileDiagEvent(project, info.fileName); } } else { @@ -2613,10 +2636,14 @@ namespace ts.server { // At this point if file is part of any any configured or external project, then it would be present in the containing projects // So if it still doesnt have any containing projects, it needs to be part of inferred project if (info.isOrphan()) { - this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); + Debug.assert(this.openFiles.has(info.path)); + this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path)); } Debug.assert(!info.isOrphan()); + return { configFileName, configFileErrors }; + } + private cleanupAfterOpeningFile() { // This was postponed from closeOpenFile to after opening next file, // so that we can reuse the project if we need to right away this.removeOrphanConfiguredProjects(); @@ -2636,9 +2663,14 @@ namespace ts.server { this.removeOrphanScriptInfos(); this.printProjects(); + } + openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { + const info = this.getOrCreateOpenScriptInfo(fileName, fileContent, scriptKind, hasMixedContent, projectRootPath); + const result = this.assignProjectToOpenedScriptInfo(info); + this.cleanupAfterOpeningFile(); this.telemetryOnOpenFile(info); - return { configFileName, configFileErrors }; + return result; } private removeOrphanConfiguredProjects() { @@ -2745,12 +2777,16 @@ namespace ts.server { * Close file whose contents is managed by the client * @param filename is absolute pathname */ - closeClientFile(uncheckedFileName: string) { + closeClientFile(uncheckedFileName: string): void; + /*@internal*/ + closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject: true): boolean; + closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject?: true) { const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName)); - if (info) { - this.closeOpenFile(info); + const result = info ? this.closeOpenFile(info, skipAssignOrphanScriptInfosToInferredProject) : false; + if (!skipAssignOrphanScriptInfosToInferredProject) { + this.printProjects(); } - this.printProjects(); + return result; } private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void { @@ -2770,36 +2806,68 @@ namespace ts.server { } /* @internal */ - applyChangesInOpenFiles(openFiles: protocol.ExternalFile[] | undefined, changedFiles: protocol.ChangedOpenFile[] | undefined, closedFiles: string[] | undefined): void { + applyChangesInOpenFiles(openFiles: Iterator | undefined, changedFiles?: Iterator, closedFiles?: string[]): void { + let openScriptInfos: ScriptInfo[] | undefined; + let assignOrphanScriptInfosToInferredProject = false; if (openFiles) { - for (const file of openFiles) { + while (true) { + const { value: file, done } = openFiles.next(); + if (done) break; const scriptInfo = this.getScriptInfo(file.fileName); Debug.assert(!scriptInfo || !scriptInfo.isScriptOpen(), "Script should not exist and not be open already"); - const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName); - this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind!), file.hasMixedContent); // TODO: GH#18217 + // Create script infos so we have the new content for all the open files before we do any updates to projects + const info = this.getOrCreateOpenScriptInfo( + scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName), + file.content, + tryConvertScriptKindName(file.scriptKind!), + file.hasMixedContent, + file.projectRootPath ? toNormalizedPath(file.projectRootPath) : undefined + ); + (openScriptInfos || (openScriptInfos = [])).push(info); } } if (changedFiles) { - for (const file of changedFiles) { + while (true) { + const { value: file, done } = changedFiles.next(); + if (done) break; const scriptInfo = this.getScriptInfo(file.fileName)!; Debug.assert(!!scriptInfo); + // Make edits to script infos and marks containing project as dirty this.applyChangesToFile(scriptInfo, file.changes); } } if (closedFiles) { for (const file of closedFiles) { - this.closeClientFile(file); + // Close files, but dont assign projects to orphan open script infos, that part comes later + assignOrphanScriptInfosToInferredProject = this.closeClientFile(file, /*skipAssignOrphanScriptInfosToInferredProject*/ true) || assignOrphanScriptInfosToInferredProject; } } + + // All the script infos now exist, so ok to go update projects for open files + if (openScriptInfos) { + openScriptInfos.forEach(info => this.assignProjectToOpenedScriptInfo(info)); + } + + // While closing files there could be open files that needed assigning new inferred projects, do it now + if (assignOrphanScriptInfosToInferredProject) { + this.assignOrphanScriptInfosToInferredProject(); + } + + // Cleanup projects + this.cleanupAfterOpeningFile(); + + // Telemetry + forEach(openScriptInfos, info => this.telemetryOnOpenFile(info)); + this.printProjects(); } /* @internal */ - applyChangesToFile(scriptInfo: ScriptInfo, changes: TextChange[]) { - // apply changes in reverse order - for (let i = changes.length - 1; i >= 0; i--) { - const change = changes[i]; + applyChangesToFile(scriptInfo: ScriptInfo, changes: Iterator) { + while (true) { + const { value: change, done } = changes.next(); + if (done) break; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1fd8d98b570..3b7690e2c60 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -92,6 +92,7 @@ namespace ts.server.protocol { SynchronizeProjectList = "synchronizeProjectList", /* @internal */ ApplyChangedToOpenFiles = "applyChangedToOpenFiles", + UpdateOpen = "updateOpen", /* @internal */ EncodedSemanticClassificationsFull = "encodedSemanticClassifications-full", /* @internal */ @@ -1543,6 +1544,32 @@ namespace ts.server.protocol { closedFiles?: string[]; } + /** + * Request to synchronize list of open files with the client + */ + export interface UpdateOpenRequest extends Request { + command: CommandTypes.UpdateOpen; + arguments: UpdateOpenRequestArgs; + } + + /** + * Arguments to UpdateOpenRequest + */ + export interface UpdateOpenRequestArgs { + /** + * List of newly open files + */ + openFiles?: OpenRequestArgs[]; + /** + * List of open files files that were changes + */ + changedFiles?: FileCodeEdits[]; + /** + * List of files that were closed + */ + closedFiles?: string[]; + } + /** * Request to set compiler options for inferred projects. * External projects are opened / closed explicitly. diff --git a/src/server/session.ts b/src/server/session.ts index b47dfe6b716..7cc3f595f82 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1654,10 +1654,10 @@ namespace ts.server { const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); if (start >= 0) { this.changeSeq++; - this.projectService.applyChangesToFile(scriptInfo, [{ + this.projectService.applyChangesToFile(scriptInfo, singleIterator({ span: { start, length: end - start }, newText: args.insertString! // TODO: GH#18217 - }]); + })); } } @@ -2096,9 +2096,39 @@ namespace ts.server { }); return this.requiredResponse(converted); }, + [CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => { + this.changeSeq++; + this.projectService.applyChangesInOpenFiles( + request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({ + fileName: file.file, + content: file.fileContent, + scriptKind: file.scriptKindName, + projectRootPath: file.projectRootPath + })), + request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({ + fileName: file.fileName, + changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => { + const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file.fileName)); + const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset); + const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset); + return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined; + }) + })), + request.arguments.closedFiles + ); + return this.requiredResponse(/*response*/ true); + }, [CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => { this.changeSeq++; - this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles!, request.arguments.closedFiles!); // TODO: GH#18217 + this.projectService.applyChangesInOpenFiles( + request.arguments.openFiles && arrayIterator(request.arguments.openFiles), + request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({ + fileName: file.fileName, + // apply changes in reverse order + changes: arrayReverseIterator(file.changes) + })), + request.arguments.closedFiles + ); // TODO: report errors return this.requiredResponse(/*response*/ true); }, diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 137e4d8af58..4e5c088025c 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -96,6 +96,7 @@ "unittests/tscWatch/resolutionCache.ts", "unittests/tscWatch/watchEnvironment.ts", "unittests/tscWatch/watchApi.ts", + "unittests/tsserver/applyChangesToOpenFiles.ts", "unittests/tsserver/cachingFileSystemInformation.ts", "unittests/tsserver/cancellationToken.ts", "unittests/tsserver/compileOnSave.ts", diff --git a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts new file mode 100644 index 00000000000..316ecc8b652 --- /dev/null +++ b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts @@ -0,0 +1,148 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: applyChangesToOpenFiles", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let xyz = 1;" + }; + const app: File = { + path: "/a/b/app.ts", + content: "let z = 1;" + }; + + function fileContentWithComment(file: File) { + return `// some copy right notice +${file.content}`; + } + + function verifyText(service: server.ProjectService, file: string, expected: string) { + const info = service.getScriptInfo(file)!; + const snap = info.getSnapshot(); + // Verified applied in reverse order + assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`); + } + + function verifyProjectVersion(project: server.Project, expected: number) { + assert.equal(Number(project.getProjectVersion()), expected); + } + + function verify(applyChangesToOpen: (session: TestSession) => void) { + const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]); + const session = createSession(host); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { file: app.path } + }); + const service = session.getProjectService(); + const project = service.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + verifyProjectVersion(project, 1); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: file3.path, + fileContent: fileContentWithComment(file3) + } + }); + verifyProjectVersion(project, 2); + + // Verify Texts + verifyText(service, commonFile1.path, commonFile1.content); + verifyText(service, commonFile2.path, commonFile2.content); + verifyText(service, app.path, app.content); + verifyText(service, file3.path, fileContentWithComment(file3)); + + // Apply changes + applyChangesToOpen(session); + + // Verify again + verifyProjectVersion(project, 3); + // Open file contents + verifyText(service, commonFile1.path, fileContentWithComment(commonFile1)); + verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); + verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); + verifyText(service, file3.path, file3.content); + } + + it("with applyChangedToOpenFiles request", () => { + verify(session => + session.executeCommandSeq({ + command: protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [ + { + fileName: commonFile1.path, + content: fileContentWithComment(commonFile1) + }, + { + fileName: commonFile2.path, + content: fileContentWithComment(commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + changes: [ + { + span: { start: 0, length: 0 }, + newText: "let zzz = 10;" + }, + { + span: { start: 0, length: 0 }, + newText: "let zz = 10;" + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }) + ); + }); + + it("with updateOpen request", () => { + verify(session => + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + openFiles: [ + { + file: commonFile1.path, + fileContent: fileContentWithComment(commonFile1) + }, + { + file: commonFile2.path, + fileContent: fileContentWithComment(commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zzz = 10;", + }, + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zz = 10;", + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }) + ); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 1761e413833..10723300cc0 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -41,13 +41,13 @@ namespace ts.projectSystem { function changeFileToNotImportModule(service: TestProjectService) { const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]); + service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); checkProject(service, /*moduleIsOrphan*/ true); } function changeFileToImportModule(service: TestProjectService) { const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]); + service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); checkProject(service, /*moduleIsOrphan*/ false); } diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 2055141538a..82c706500d3 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -161,7 +161,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 0); externalFiles[0].content = "let x =1;"; - projectService.applyChangesInOpenFiles(externalFiles, [], []); + projectService.applyChangesInOpenFiles(arrayIterator(externalFiles)); }); it("external project that included config files", () => { @@ -790,9 +790,7 @@ namespace ts.projectSystem { rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], options: { allowJs: false } }]); - service.applyChangesInOpenFiles([ - { fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" } - ], /*changedFiles*/ undefined, /*closedFiles*/ undefined); + service.applyChangesInOpenFiles(singleIterator({ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" })); checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); checkProjectActualFiles(configProject, [tsconfig.path]); const inferredProject = service.inferredProjects[0]; diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 264dbff285e..81a62c6832e 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -202,7 +202,7 @@ namespace ts.projectSystem { const host = createServerHost([file1, config1]); const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); - projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []); + projectService.applyChangesInOpenFiles(singleIterator({ fileName: file1.path, content: file1.content })); checkNumberOfProjects(projectService, { inferredProjects: 1 }); const proj = projectService.inferredProjects[0]; @@ -588,11 +588,11 @@ namespace ts.projectSystem { const host = createServerHost([]); const projectService = createProjectService(host); - projectService.applyChangesInOpenFiles([tsFile], [], []); + projectService.applyChangesInOpenFiles(singleIterator(tsFile)); const projs = projectService.synchronizeProjectList([]); projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); projectService.synchronizeProjectList([projs[0].info!]); - projectService.applyChangesInOpenFiles([jsFile], [], []); + projectService.applyChangesInOpenFiles(singleIterator(jsFile)); }); it("config file is deleted", () => { @@ -696,11 +696,12 @@ namespace ts.projectSystem { checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Open HTML file - projectService.applyChangesInOpenFiles( - /*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }], - /*changedFiles*/ undefined, - /*closedFiles*/ undefined); - + projectService.applyChangesInOpenFiles(singleIterator({ + fileName: file2.path, + hasMixedContent: true, + scriptKind: ScriptKind.JS, + content: `var hello = "hello";` + })); // Now HTML file is included in the project checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); @@ -853,7 +854,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 1 }); projectService.applyChangesInOpenFiles( /*openFiles*/ undefined, - /*changedFiles*/[{ fileName: file1.path, changes: [{ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }] }], + /*changedFiles*/singleIterator({ fileName: file1.path, changes: singleIterator({ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }) }), /*closedFiles*/ undefined); checkNumberOfProjects(projectService, { inferredProjects: 1 }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 6d04938e22b..fb4874dd757 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5720,6 +5720,7 @@ declare namespace ts.server.protocol { OpenExternalProject = "openExternalProject", OpenExternalProjects = "openExternalProjects", CloseExternalProject = "closeExternalProject", + UpdateOpen = "updateOpen", GetOutliningSpans = "getOutliningSpans", TodoComments = "todoComments", Indentation = "indentation", @@ -6788,6 +6789,30 @@ declare namespace ts.server.protocol { */ interface CloseExternalProjectResponse extends Response { } + /** + * Request to synchronize list of open files with the client + */ + interface UpdateOpenRequest extends Request { + command: CommandTypes.UpdateOpen; + arguments: UpdateOpenRequestArgs; + } + /** + * Arguments to UpdateOpenRequest + */ + interface UpdateOpenRequestArgs { + /** + * List of newly open files + */ + openFiles?: OpenRequestArgs[]; + /** + * List of open files files that were changes + */ + changedFiles?: FileCodeEdits[]; + /** + * List of files that were closed + */ + closedFiles?: string[]; + } /** * Request to set compiler options for inferred projects. * External projects are opened / closed explicitly. @@ -8627,6 +8652,7 @@ declare namespace ts.server { */ private onConfigFileChangeForOpenScriptInfo; private removeProject; + private assignOrphanScriptInfosToInferredProject; /** * Remove this file from the set of open, non-configured files. * @param info The file that has been closed or newly configured @@ -8745,6 +8771,9 @@ declare namespace ts.server { */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult; private findExternalProjectContainingOpenScriptInfo; + private getOrCreateOpenScriptInfo; + private assignProjectToOpenedScriptInfo; + private cleanupAfterOpeningFile; openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; private removeOrphanConfiguredProjects; private removeOrphanScriptInfos;