Skip to content

Add Compile command to server-side file explorer #1389

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

Merged
merged 1 commit into from
Jun 28, 2024
Merged
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
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