Skip to content

Commit

Permalink
Add Compile command to server-side file explorer (#1389)
Browse files Browse the repository at this point in the history
  • Loading branch information
isc-bsaviano authored Jun 28, 2024
1 parent 5cfc9ec commit 3799c68
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 56 deletions.
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,6 @@
"command": "vscode-objectscript.explorer.project.refresh",
"when": "vscode-objectscript.connectActive"
},
{
"command": "vscode-objectscript.compileFolder",
"when": "false"
},
{
"command": "vscode-objectscript.serverCommands.sourceControl",
"when": "vscode-objectscript.connectActive && resourceScheme == isfs || (vscode-objectscript.connectActive && !editorIsOpen)"
Expand Down Expand Up @@ -338,6 +334,10 @@
{
"command": "vscode-objectscript.extractXMLFileContents",
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
},
{
"command": "vscode-objectscript.compileIsfs",
"when": "false"
}
],
"view/title": [
Expand Down Expand Up @@ -622,6 +622,11 @@
"command": "vscode-objectscript.extractXMLFileContents",
"when": "vscode-objectscript.connectActive && resourceExtname =~ /^\\.xml$/i && !(resourceScheme =~ /^isfs(-readonly)?$/)",
"group": "objectscript_modify@4"
},
{
"command": "vscode-objectscript.compileIsfs",
"when": "vscode-objectscript.connectActive && resourceScheme == isfs && resourcePath && !(resourcePath =~ /^\\/?$/) && !listMultiSelection",
"group": "objectscript_modify@1"
}
],
"file/newFile": [
Expand Down Expand Up @@ -1174,6 +1179,11 @@
"category": "ObjectScript",
"command": "vscode-objectscript.extractXMLFileContents",
"title": "Extract Documents from XML File..."
},
{
"category": "ObjectScript",
"command": "vscode-objectscript.compileIsfs",
"title": "Compile"
}
],
"keybindings": [
Expand Down
69 changes: 25 additions & 44 deletions src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
config,
documentContentProvider,
FILESYSTEM_SCHEMA,
FILESYSTEM_READONLY_SCHEMA,
OBJECTSCRIPT_FILE_SCHEMA,
fileSystemProvider,
workspaceState,
Expand Down Expand Up @@ -213,24 +212,14 @@ What do you want to do?`,

function updateOthers(others: string[], baseUri: vscode.Uri) {
let workspaceFolder = vscode.workspace.getWorkspaceFolder(baseUri);
if (!workspaceFolder && (baseUri.scheme === FILESYSTEM_SCHEMA || baseUri.scheme === FILESYSTEM_READONLY_SCHEMA)) {
if (!workspaceFolder && filesystemSchemas.includes(baseUri.scheme)) {
// hack to deal with problem seen with isfs* schemes
workspaceFolder = vscode.workspace.getWorkspaceFolder(baseUri.with({ path: "" }));
}
const workspaceFolderName = workspaceFolder ? workspaceFolder.name : "";
others.forEach((item) => {
const uri = DocumentContentProvider.getUri(item, workspaceFolderName);
if (uri.scheme === FILESYSTEM_SCHEMA || uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
// Massage uri.path to change the first N-1 dots to slashes, where N is the number of slashes in baseUri.path
// For example, when baseUri.path is /Foo/Bar.cls and uri.path is /Foo.Bar.1.int
const partsToConvert = baseUri.path.split("/").length - 1;
const dotParts = uri.path.split(".");
const correctPath =
dotParts.length <= partsToConvert
? uri.path
: dotParts.slice(0, partsToConvert).join("/") + "." + dotParts.slice(partsToConvert).join(".");
//console.log(`updateOthers: uri.path=${uri.path} baseUri.path=${baseUri.path} correctPath=${correctPath}`);
fileSystemProvider.fireFileChanged(uri.with({ path: correctPath }));
const uri = DocumentContentProvider.getUri(item, undefined, undefined, undefined, workspaceFolder?.uri);
if (filesystemSchemas.includes(uri.scheme)) {
fileSystemProvider.fireFileChanged(uri);
} else {
documentContentProvider.update(uri);
}
Expand All @@ -242,33 +231,25 @@ export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]
return;
}
const api = new AtelierAPI(files[0].uri);
return Promise.all(
files.map((file) =>
api
.getDoc(file.name)
.then(async (data) => {
const mtime = Number(new Date(data.result.ts + "Z"));
workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined);
if (file.uri.scheme === "file") {
if (Buffer.isBuffer(data.result.content)) {
// This is a binary file
await vscode.workspace.fs.writeFile(file.uri, data.result.content);
} else {
// This is a text file
const content = (data.result.content || []).join(
(file as CurrentTextFile).eol === vscode.EndOfLine.LF ? "\n" : "\r\n"
);
await vscode.workspace.fs.writeFile(file.uri, new TextEncoder().encode(content));
}
} else if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
fileSystemProvider.fireFileChanged(file.uri);
}
})
.then(() => api.actionIndex([file.name]))
.then((data) => data.result.content[0].others)
.then((others) => {
updateOthers(others, file.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 (file.uri.scheme === "file") {
const content = await api.getDoc(file.name).then((data) => data.result.content);
await vscode.workspace.fs.writeFile(
file.uri,
Buffer.isBuffer(content) ? content : new TextEncoder().encode(content.join("\n"))
);
} else if (filesystemSchemas.includes(file.uri.scheme)) {
fileSystemProvider.fireFileChanged(file.uri);
}
updateOthers(doc.others, file.uri);
})
)
);
}
Expand Down Expand Up @@ -347,13 +328,13 @@ export async function importAndCompile(
if (compileFile) {
compile([file], flags);
} else {
if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
if (filesystemSchemas.includes(file.uri.scheme)) {
// Fire the file changed event to avoid VSCode alerting the user on the next save that
// "The content of the file is newer."
fileSystemProvider.fireFileChanged(file.uri);
}
}
} else if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
} else if (filesystemSchemas.includes(file.uri.scheme)) {
// Fire the file changed event to avoid VSCode alerting the user on the next folder-specific save (e.g. of settings.json) that
// "The content of the file is newer."
fileSystemProvider.fireFileChanged(file.unredirectedUri ?? file.uri);
Expand Down
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
vscode.commands.registerCommand("vscode-objectscript.compileWithFlags", () => importAndCompile(true)),
vscode.commands.registerCommand("vscode-objectscript.compileAll", () => namespaceCompile(false)),
vscode.commands.registerCommand("vscode-objectscript.compileAllWithFlags", () => namespaceCompile(true)),
vscode.commands.registerCommand("vscode-objectscript.refreshLocalFile", async (_file, files) => {
vscode.commands.registerCommand("vscode-objectscript.refreshLocalFile", async () => {
const file = currentFile();
if (!file) {
return;
Expand Down Expand Up @@ -1404,6 +1404,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
}
}
),
vscode.commands.registerCommand("vscode-objectscript.compileIsfs", (uri) => fileSystemProvider.compile(uri)),
...setUpTestController(),

/* Anything we use from the VS Code proposed API */
Expand Down
112 changes: 109 additions & 3 deletions src/providers/FileSystemProvider/FileSystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
redirectDotvscodeRoot,
workspaceFolderOfUri,
} from "../../utils/index";
import { config, intLangId, macLangId, workspaceState } from "../../extension";
import { config, FILESYSTEM_SCHEMA, intLangId, macLangId, workspaceState } from "../../extension";
import { addIsfsFileToProject, modifyProject } from "../../commands/project";
import { DocumentContentProvider } from "../DocumentContentProvider";
import { Document, UserAction } from "../../api/atelier";
Expand Down Expand Up @@ -512,10 +512,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
// Ignore the recursive flag for project folders
toDeletePromise = projectContentsFromUri(uri, true);
} else {
toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined);
toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined).then(
(data) => data.result.content
);
}
const toDelete: string[] = await toDeletePromise.then((data) =>
data.result.content
data
.map((entry) => {
if (options.recursive || project) {
return entry.Name;
Expand Down Expand Up @@ -658,6 +660,110 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
await vscode.workspace.fs.delete(oldUri);
}

/**
* If `uri` is a file, compile it.
* If `uri` is a directory, compile its contents.
*/
public async compile(uri: vscode.Uri): Promise<void> {
if (!uri || uri.scheme != FILESYSTEM_SCHEMA) return;
uri = redirectDotvscodeRoot(uri);
const compileList: string[] = [];
try {
const entry = await this._lookup(uri, true);
if (!entry) return;
if (entry instanceof Directory) {
// Get the list of files to compile
let compileListPromise: Promise<any>;
if (new URLSearchParams(uri.query).get("project")?.length) {
compileListPromise = projectContentsFromUri(uri, true);
} else {
compileListPromise = studioOpenDialogFromURI(uri, { flat: true }).then((data) => data.result.content);
}
compileList.push(...(await compileListPromise.then((data) => data.map((e) => e.Name))));
} else {
// Compile this file
compileList.push(isCSPFile(uri) ? uri.path : uri.path.slice(1).replace(/\//g, "."));
}
} catch (error) {
console.log(error);
let errorMsg = "Error determining documents to compile.";
if (error && error.errorText && error.errorText !== "") {
outputChannel.appendLine("\n" + error.errorText);
outputChannel.show(true);
errorMsg += " Check 'ObjectScript' output channel for details.";
}
vscode.window.showErrorMessage(errorMsg, "Dismiss");
return;
}
if (!compileList.length) return;
const api = new AtelierAPI(uri);
const conf = vscode.workspace.getConfiguration("objectscript");
// Compile the files
await vscode.window.withProgress(
{
cancellable: true,
location: vscode.ProgressLocation.Notification,
title: `Compiling: ${compileList.length == 1 ? compileList[0] : compileList.length + " files"}`,
},
(progress, token: vscode.CancellationToken) =>
api
.asyncCompile(compileList, token, conf.get("compileFlags"))
.then((data) => {
const info = compileList.length > 1 ? "" : `${compileList}: `;
if (data.status && data.status.errors && data.status.errors.length) {
throw new Error(`${info}Compile error`);
} else if (!conf.get("suppressCompileMessages")) {
vscode.window.showInformationMessage(`${info}Compilation succeeded.`, "Dismiss");
}
})
.catch(() => {
if (!conf.get("suppressCompileErrorMessages")) {
vscode.window
.showErrorMessage(
"Compilation failed. Check 'ObjectScript' output channel for details.",
"Show",
"Dismiss"
)
.then((action) => {
if (action === "Show") {
outputChannel.show(true);
}
});
}
})
);
// Tell the client to update all "other" files affected by compilation
const workspaceFolder = workspaceFolderOfUri(uri);
const otherList: string[] = await api
.actionIndex(compileList)
.then((data) =>
data.result.content.flatMap((idx) => {
if (!idx.status.length) {
// Update the timestamp for this file
const mtime = Number(new Date(idx.ts + "Z"));
workspaceState.update(`${workspaceFolder}:${idx.name}:mtime`, mtime > 0 ? mtime : undefined);
// Tell the client that it changed
this.fireFileChanged(DocumentContentProvider.getUri(idx.name, undefined, undefined, undefined, uri));
// Return the list of "other" documents
return idx.others;
} else {
// The server failed to index the document. This should never happen.
return [];
}
})
)
.catch(() => {
// Index API returned an error. This should never happen.
return [];
});
// Only fire the event for files that weren't in the compile list
otherList.forEach(
(f) =>
!compileList.includes(f) &&
this.fireFileChanged(DocumentContentProvider.getUri(f, undefined, undefined, undefined, uri))
);
}

public watch(uri: vscode.Uri): vscode.Disposable {
return new vscode.Disposable(() => {
return;
Expand Down
8 changes: 4 additions & 4 deletions src/utils/FileProviderUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ export async function projectContentsFromUri(uri: vscode.Uri, overrideFlat?: boo
"SELECT CASE " +
"WHEN Type = 'CLS' THEN Name||'.cls' " +
"ELSE Name END Name, Type FROM %Studio.Project_ProjectItemsList(?) " +
"WHERE (Name %STARTSWITH ? OR Name %STARTSWITH ?) AND (" +
"WHERE (Name %STARTSWITH ? OR Name %STARTSWITH ?) AND ((" +
"(Type = 'MAC' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.mac,*.int,*.inc,*.bas,*.mvi',1,1,1,1,0,1) AS sod WHERE Name = sod.Name)) OR " +
"(Type = 'CSP' AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1) AS sod WHERE '/'||Name = sod.Name)) OR " +
"(Type NOT IN ('CLS','PKG','MAC','CSP','DIR','GBL') AND EXISTS (SELECT sod.Size FROM %Library.RoutineMgr_StudioOpenDialog('*.other',1,1,1,1,0,1) AS sod WHERE Name = sod.Name))) OR " +
"(Type = 'CLS' AND (Package IS NOT NULL OR (Package IS NULL AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = Name)))) " +
"(Type = 'CLS' AND (Package IS NOT NULL OR (Package IS NULL AND EXISTS (SELECT dcd.ID FROM %Dictionary.ClassDefinition AS dcd WHERE dcd.ID = Name))))) " +
"UNION " +
"SELECT SUBSTR(sod.Name,2) AS Name, pil.Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,0,1) AS sod " +
"SELECT SUBSTR(sod.Name,2) AS Name, pil.Type FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1,?) AS sod " +
"JOIN %Studio.Project_ProjectItemsList(?,1) AS pil ON " +
"pil.Type = 'DIR' AND SUBSTR(sod.Name,2) %STARTSWITH ? AND SUBSTR(sod.Name,2) %STARTSWITH pil.Name||'/'";
parameters = [project, folderDots, folder, folder + "*.cspall", project, folder];
parameters = [project, folderDots, folder, `Name %STARTSWITH '/${folder}'`, project, folder];
} else {
if (folder.length) {
const l = String(folder.length + 1); // Need the + 1 because SUBSTR is 1 indexed
Expand Down

0 comments on commit 3799c68

Please sign in to comment.