Skip to content

Only reload contents of client-side file after compilation if compilation changed the file #1575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 82 additions & 62 deletions src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {
classNameRegex,
compileErrorMsg,
cspAppsForUri,
CurrentBinaryFile,
currentFile,
currentFileFromContent,
CurrentTextFile,
EitherCurrentFile,
exportedUris,
getWsFolder,
handleError,
Expand Down Expand Up @@ -57,7 +57,7 @@ async function compileFlags(): Promise<string> {
* @param force If passed true, use server mtime.
* @return mtime timestamp or -1.
*/
export async function checkChangedOnServer(file: CurrentTextFile | CurrentBinaryFile, force = false): Promise<number> {
export async function checkChangedOnServer(file: EitherCurrentFile, force = false): Promise<number> {
if (!file || !file.uri || schemas.includes(file.uri.scheme)) {
return -1;
}
Expand Down Expand Up @@ -88,7 +88,7 @@ export async function checkChangedOnServer(file: CurrentTextFile | CurrentBinary
}

export async function importFile(
file: CurrentTextFile | CurrentBinaryFile,
file: EitherCurrentFile,
ignoreConflict?: boolean,
skipDeplCheck = false
): Promise<any> {
Expand Down Expand Up @@ -213,61 +213,79 @@ function updateOthers(others: string[], baseUri: vscode.Uri) {
});
}

export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]): Promise<any> {
if (!files.length) {
return;
}
/**
* Reload the contents of `files` from the server. This will also a trigger a
* refresh of in-memory copies of "other" documents related to `files`. Files
* in the `onlyUpdateOthersFiles` array will not have their contents reloaded.
* Only their "other" documents will be refreshed.
*/
export async function loadChanges(
files: EitherCurrentFile[],
onlyUpdateOthersFiles: EitherCurrentFile[] = []
): Promise<void> {
if (!files?.length) return;
const api = new AtelierAPI(files[0].uri);
// Use allSettled so we attempt to load changes for all files, even if some fail
return api.actionIndex(files.map((f) => f.name)).then((data) =>
Promise.allSettled(
data.result.content.map(async (doc) => {
if (doc.status.length) return;
const file = files.find((f) => f.name == doc.name);
const mtime = Number(new Date(doc.ts + "Z"));
workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined);
if (notIsfs(file.uri)) {
const content = await api.getDoc(file.name, file.uri).then((data) => data.result.content);
exportedUris.add(file.uri.toString()); // Set optimistically
await vscode.workspace.fs
.writeFile(
file.uri,
Buffer.isBuffer(content)
? content
: new TextEncoder().encode(
content.join(
((<CurrentTextFile>file)?.eol ?? vscode.EndOfLine.LF) == vscode.EndOfLine.CRLF ? "\r\n" : "\n"
)
)
)
.then(undefined, (e) => {
// Save failed, so remove this URI from the set
exportedUris.delete(file.uri.toString());
// Re-throw the error
throw e;
});
if (isClassOrRtn(file.uri)) {
// Update the document index
updateIndexForDocument(file.uri, undefined, undefined, content);
await api
.actionIndex(Array.from(new Set(files.map((f) => f.name).concat(onlyUpdateOthersFiles.map((f) => f.name)))))
.then((data) =>
Promise.allSettled(
data.result.content.map(async (doc) => {
if (doc.status.length) return;
let file = files.find((f) => f.name == doc.name);
if (file) {
// This is a file that requires a content reload
if (notIsfs(file.uri)) {
const mtime = Number(new Date(doc.ts + "Z"));
workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined);
const content = await api.getDoc(file.name, file.uri).then((data) => data.result.content);
exportedUris.add(file.uri.toString()); // Set optimistically
await vscode.workspace.fs
.writeFile(
file.uri,
Buffer.isBuffer(content)
? content
: new TextEncoder().encode(
content.join(
((<CurrentTextFile>file)?.eol ?? vscode.EndOfLine.LF) == vscode.EndOfLine.CRLF ? "\r\n" : "\n"
)
)
)
.then(undefined, (e) => {
// Save failed, so remove this URI from the set
exportedUris.delete(file.uri.toString());
// Re-throw the error
throw e;
});
if (isClassOrRtn(file.uri)) {
// Update the document index
updateIndexForDocument(file.uri, undefined, undefined, content);
}
} else {
fileSystemProvider.fireFileChanged(file.uri);
}
} else {
// The contents of this file did not change, but its "other" documents still need to be updated
file = onlyUpdateOthersFiles.find((f) => f.name == doc.name);
if (!file) return;
}
} else if (filesystemSchemas.includes(file.uri.scheme)) {
fileSystemProvider.fireFileChanged(file.uri);
}
updateOthers(doc.others, file.uri);
})
)
);
updateOthers(doc.others, file.uri);
})
)
);
}

export async function compile(docs: (CurrentTextFile | CurrentBinaryFile)[], flags?: string): Promise<any> {
export async function compile(docs: EitherCurrentFile[], flags?: string): Promise<any> {
const wsFolder = vscode.workspace.getWorkspaceFolder(docs[0].uri);
const conf = vscode.workspace.getConfiguration("objectscript", wsFolder || docs[0].uri);
flags = flags || conf.get("compileFlags");
const api = new AtelierAPI(docs[0].uri);
const docNames = docs.map((d) => d.name);
// Determine the line ending to use for other documents affected
// Determine the line ending to use for documents affected
// by compilation so we don't need to read their contents
const eol = (<CurrentTextFile>docs.find((d) => (<CurrentTextFile>d)?.eol))?.eol ?? vscode.EndOfLine.LF;
const docsToReload: EitherCurrentFile[] = [];
const docsToRefreshOthers: EitherCurrentFile[] = [...docs];
return vscode.window
.withProgress(
{
Expand All @@ -285,13 +303,18 @@ export async function compile(docs: (CurrentTextFile | CurrentBinaryFile)[], fla
} else if (!conf.get("suppressCompileMessages")) {
vscode.window.showInformationMessage(`${info}Compilation succeeded.`, "Dismiss");
}
if (wsFolder) {
// Make sure that we update the content for any
// other documents affected by this compilation
data.result.content.forEach((f) => {
if (docNames.includes(f.name)) return;
data.result.content.forEach((f) => {
// Reload the contents of files that were changed by compilation
if (docNames.includes(f.name)) {
docsToReload.push(
...docsToRefreshOthers.splice(
docsToRefreshOthers.findIndex((d) => d.name == f.name),
1
)
);
} else if (wsFolder) {
getUrisForDocument(f.name, wsFolder).forEach((u) => {
docs.push({
docsToReload.push({
name: f.name,
uri: u,
uniqueId: `${wsFolder.name}:${f.name}`,
Expand All @@ -302,17 +325,14 @@ export async function compile(docs: (CurrentTextFile | CurrentBinaryFile)[], fla
content: "",
});
});
});
}
return docs;
})
.catch(() => {
compileErrorMsg(conf);
// Always fetch server changes, even when compile failed or got cancelled
return docs;
}
});
})
.catch(() => compileErrorMsg(conf))
)
.then(loadChanges);
.then(() => {
return loadChanges(docsToReload, docsToRefreshOthers);
});
}

export async function importAndCompile(
Expand Down Expand Up @@ -429,7 +449,7 @@ export async function namespaceCompile(askFlags = false): Promise<any> {
}

async function importFiles(files: vscode.Uri[], noCompile = false) {
const toCompile: (CurrentTextFile | CurrentBinaryFile)[] = [];
const toCompile: EitherCurrentFile[] = [];
const rateLimiter = new RateLimiter(50);
await Promise.allSettled<void>(
files.map((uri) =>
Expand Down
13 changes: 6 additions & 7 deletions src/utils/documentIndex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as vscode from "vscode";
import {
CurrentBinaryFile,
CurrentTextFile,
EitherCurrentFile,
RateLimiter,
currentFileFromContent,
exportedUris,
Expand All @@ -27,7 +26,7 @@ interface WSFolderIndex {

interface WSFolderIndexChange {
/** InterSystems document added to the index or changed on disk, if any */
addedOrChanged?: CurrentTextFile | CurrentBinaryFile;
addedOrChanged?: EitherCurrentFile;
/** InterSystems document removed from the index, if any */
removed?: string;
}
Expand All @@ -49,7 +48,7 @@ async function getCurrentFile(
uri: vscode.Uri,
forceText = false,
content?: string[] | Buffer
): Promise<CurrentTextFile | CurrentBinaryFile | undefined> {
): Promise<EitherCurrentFile | undefined> {
if (content) {
// forceText is always true when content is passed
return currentFileFromContent(uri, Buffer.isBuffer(content) ? textDecoder.decode(content) : content.join("\n"));
Expand All @@ -76,11 +75,11 @@ async function getCurrentFile(
}

/** Generate a debounced compile function */
function generateCompileFn(): (doc: CurrentTextFile | CurrentBinaryFile) => void {
function generateCompileFn(): (doc: EitherCurrentFile) => void {
let timeout: NodeJS.Timeout;
const docs: (CurrentTextFile | CurrentBinaryFile)[] = [];
const docs: EitherCurrentFile[] = [];

return (doc: CurrentTextFile | CurrentBinaryFile): void => {
return (doc: EitherCurrentFile): void => {
docs.push(doc);

// Clear the previous timeout to reset the debounce timer
Expand Down
4 changes: 3 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export interface CurrentBinaryFile extends CurrentFile {
content: Buffer;
}

export type EitherCurrentFile = CurrentTextFile | CurrentBinaryFile;

/**
* For workspace roots in the local filesystem, configName is the root's name
* which defaults to the folder name, and apiTarget is the same.
Expand Down Expand Up @@ -216,7 +218,7 @@ export const classNameRegex = /^[ \t]*Class[ \t]+(%?[\p{L}\d\u{100}-\u{ffff}]+(?
/** A regex for extracting the name and type of a routine from its content */
export const routineNameTypeRegex = /^ROUTINE ([^\s]+)(?:\s*\[\s*Type\s*=\s*\b([a-z]{3})\b)?/i;

export function currentFileFromContent(uri: vscode.Uri, content: string | Buffer): CurrentTextFile | CurrentBinaryFile {
export function currentFileFromContent(uri: vscode.Uri, content: string | Buffer): EitherCurrentFile {
const fileName = uri.fsPath;
const workspaceFolder = workspaceFolderOfUri(uri);
if (!workspaceFolder) {
Expand Down