Skip to content
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 @@ -58,7 +58,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 @@ -89,7 +89,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 @@ -212,63 +212,81 @@ 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> {
docs = docs.filter(notNull);
if (!docs.length) return;
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 @@ -286,13 +304,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 @@ -303,17 +326,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 @@ -428,7 +448,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 Down Expand Up @@ -30,7 +29,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 @@ -52,7 +51,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 @@ -79,11 +78,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 @@ -129,6 +129,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 @@ -219,7 +221,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