mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-07 09:20:57 -05:00
Merge pull request #30107 from Microsoft/applyChangesToOpenFiles
Add UpdateOpen to request
This commit is contained in:
@@ -1173,6 +1173,21 @@ namespace ts {
|
||||
}};
|
||||
}
|
||||
|
||||
export function arrayReverseIterator<T>(array: ReadonlyArray<T>): Iterator<T> {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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<TextChange>;
|
||||
}
|
||||
|
||||
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<Diagnostic> | 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<OpenFileArguments> | undefined, changedFiles?: Iterator<ChangeFileArguments>, 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<TextChange>) {
|
||||
while (true) {
|
||||
const { value: change, done } = changes.next();
|
||||
if (done) break;
|
||||
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
148
src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts
Normal file
148
src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts
Normal file
@@ -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<protocol.OpenRequest>({
|
||||
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<protocol.OpenRequest>({
|
||||
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<protocol.ApplyChangedToOpenFilesRequest>({
|
||||
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<protocol.UpdateOpenRequest>({
|
||||
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
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user