Skip to content

Commit 3799c68

Browse files
authored
Add Compile command to server-side file explorer (#1389)
1 parent 5cfc9ec commit 3799c68

File tree

5 files changed

+154
-56
lines changed

5 files changed

+154
-56
lines changed

package.json

+14-4
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,6 @@
195195
"command": "vscode-objectscript.explorer.project.refresh",
196196
"when": "vscode-objectscript.connectActive"
197197
},
198-
{
199-
"command": "vscode-objectscript.compileFolder",
200-
"when": "false"
201-
},
202198
{
203199
"command": "vscode-objectscript.serverCommands.sourceControl",
204200
"when": "vscode-objectscript.connectActive && resourceScheme == isfs || (vscode-objectscript.connectActive && !editorIsOpen)"
@@ -338,6 +334,10 @@
338334
{
339335
"command": "vscode-objectscript.extractXMLFileContents",
340336
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
337+
},
338+
{
339+
"command": "vscode-objectscript.compileIsfs",
340+
"when": "false"
341341
}
342342
],
343343
"view/title": [
@@ -622,6 +622,11 @@
622622
"command": "vscode-objectscript.extractXMLFileContents",
623623
"when": "vscode-objectscript.connectActive && resourceExtname =~ /^\\.xml$/i && !(resourceScheme =~ /^isfs(-readonly)?$/)",
624624
"group": "objectscript_modify@4"
625+
},
626+
{
627+
"command": "vscode-objectscript.compileIsfs",
628+
"when": "vscode-objectscript.connectActive && resourceScheme == isfs && resourcePath && !(resourcePath =~ /^\\/?$/) && !listMultiSelection",
629+
"group": "objectscript_modify@1"
625630
}
626631
],
627632
"file/newFile": [
@@ -1174,6 +1179,11 @@
11741179
"category": "ObjectScript",
11751180
"command": "vscode-objectscript.extractXMLFileContents",
11761181
"title": "Extract Documents from XML File..."
1182+
},
1183+
{
1184+
"category": "ObjectScript",
1185+
"command": "vscode-objectscript.compileIsfs",
1186+
"title": "Compile"
11771187
}
11781188
],
11791189
"keybindings": [

src/commands/compile.ts

+25-44
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
config,
66
documentContentProvider,
77
FILESYSTEM_SCHEMA,
8-
FILESYSTEM_READONLY_SCHEMA,
98
OBJECTSCRIPT_FILE_SCHEMA,
109
fileSystemProvider,
1110
workspaceState,
@@ -213,24 +212,14 @@ What do you want to do?`,
213212

214213
function updateOthers(others: string[], baseUri: vscode.Uri) {
215214
let workspaceFolder = vscode.workspace.getWorkspaceFolder(baseUri);
216-
if (!workspaceFolder && (baseUri.scheme === FILESYSTEM_SCHEMA || baseUri.scheme === FILESYSTEM_READONLY_SCHEMA)) {
215+
if (!workspaceFolder && filesystemSchemas.includes(baseUri.scheme)) {
217216
// hack to deal with problem seen with isfs* schemes
218217
workspaceFolder = vscode.workspace.getWorkspaceFolder(baseUri.with({ path: "" }));
219218
}
220-
const workspaceFolderName = workspaceFolder ? workspaceFolder.name : "";
221219
others.forEach((item) => {
222-
const uri = DocumentContentProvider.getUri(item, workspaceFolderName);
223-
if (uri.scheme === FILESYSTEM_SCHEMA || uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
224-
// Massage uri.path to change the first N-1 dots to slashes, where N is the number of slashes in baseUri.path
225-
// For example, when baseUri.path is /Foo/Bar.cls and uri.path is /Foo.Bar.1.int
226-
const partsToConvert = baseUri.path.split("/").length - 1;
227-
const dotParts = uri.path.split(".");
228-
const correctPath =
229-
dotParts.length <= partsToConvert
230-
? uri.path
231-
: dotParts.slice(0, partsToConvert).join("/") + "." + dotParts.slice(partsToConvert).join(".");
232-
//console.log(`updateOthers: uri.path=${uri.path} baseUri.path=${baseUri.path} correctPath=${correctPath}`);
233-
fileSystemProvider.fireFileChanged(uri.with({ path: correctPath }));
220+
const uri = DocumentContentProvider.getUri(item, undefined, undefined, undefined, workspaceFolder?.uri);
221+
if (filesystemSchemas.includes(uri.scheme)) {
222+
fileSystemProvider.fireFileChanged(uri);
234223
} else {
235224
documentContentProvider.update(uri);
236225
}
@@ -242,33 +231,25 @@ export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]
242231
return;
243232
}
244233
const api = new AtelierAPI(files[0].uri);
245-
return Promise.all(
246-
files.map((file) =>
247-
api
248-
.getDoc(file.name)
249-
.then(async (data) => {
250-
const mtime = Number(new Date(data.result.ts + "Z"));
251-
workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined);
252-
if (file.uri.scheme === "file") {
253-
if (Buffer.isBuffer(data.result.content)) {
254-
// This is a binary file
255-
await vscode.workspace.fs.writeFile(file.uri, data.result.content);
256-
} else {
257-
// This is a text file
258-
const content = (data.result.content || []).join(
259-
(file as CurrentTextFile).eol === vscode.EndOfLine.LF ? "\n" : "\r\n"
260-
);
261-
await vscode.workspace.fs.writeFile(file.uri, new TextEncoder().encode(content));
262-
}
263-
} else if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
264-
fileSystemProvider.fireFileChanged(file.uri);
265-
}
266-
})
267-
.then(() => api.actionIndex([file.name]))
268-
.then((data) => data.result.content[0].others)
269-
.then((others) => {
270-
updateOthers(others, file.uri);
271-
})
234+
// Use allSettled so we attempt to load changes for all files, even if some fail
235+
return api.actionIndex(files.map((f) => f.name)).then((data) =>
236+
Promise.allSettled(
237+
data.result.content.map(async (doc) => {
238+
if (doc.status.length) return;
239+
const file = files.find((f) => f.name == doc.name);
240+
const mtime = Number(new Date(doc.ts + "Z"));
241+
workspaceState.update(`${file.uniqueId}:mtime`, mtime > 0 ? mtime : undefined);
242+
if (file.uri.scheme === "file") {
243+
const content = await api.getDoc(file.name).then((data) => data.result.content);
244+
await vscode.workspace.fs.writeFile(
245+
file.uri,
246+
Buffer.isBuffer(content) ? content : new TextEncoder().encode(content.join("\n"))
247+
);
248+
} else if (filesystemSchemas.includes(file.uri.scheme)) {
249+
fileSystemProvider.fireFileChanged(file.uri);
250+
}
251+
updateOthers(doc.others, file.uri);
252+
})
272253
)
273254
);
274255
}
@@ -347,13 +328,13 @@ export async function importAndCompile(
347328
if (compileFile) {
348329
compile([file], flags);
349330
} else {
350-
if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
331+
if (filesystemSchemas.includes(file.uri.scheme)) {
351332
// Fire the file changed event to avoid VSCode alerting the user on the next save that
352333
// "The content of the file is newer."
353334
fileSystemProvider.fireFileChanged(file.uri);
354335
}
355336
}
356-
} else if (file.uri.scheme === FILESYSTEM_SCHEMA || file.uri.scheme === FILESYSTEM_READONLY_SCHEMA) {
337+
} else if (filesystemSchemas.includes(file.uri.scheme)) {
357338
// Fire the file changed event to avoid VSCode alerting the user on the next folder-specific save (e.g. of settings.json) that
358339
// "The content of the file is newer."
359340
fileSystemProvider.fireFileChanged(file.unredirectedUri ?? file.uri);

src/extension.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
877877
vscode.commands.registerCommand("vscode-objectscript.compileWithFlags", () => importAndCompile(true)),
878878
vscode.commands.registerCommand("vscode-objectscript.compileAll", () => namespaceCompile(false)),
879879
vscode.commands.registerCommand("vscode-objectscript.compileAllWithFlags", () => namespaceCompile(true)),
880-
vscode.commands.registerCommand("vscode-objectscript.refreshLocalFile", async (_file, files) => {
880+
vscode.commands.registerCommand("vscode-objectscript.refreshLocalFile", async () => {
881881
const file = currentFile();
882882
if (!file) {
883883
return;
@@ -1404,6 +1404,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
14041404
}
14051405
}
14061406
),
1407+
vscode.commands.registerCommand("vscode-objectscript.compileIsfs", (uri) => fileSystemProvider.compile(uri)),
14071408
...setUpTestController(),
14081409

14091410
/* Anything we use from the VS Code proposed API */

src/providers/FileSystemProvider/FileSystemProvider.ts

+109-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
redirectDotvscodeRoot,
1414
workspaceFolderOfUri,
1515
} from "../../utils/index";
16-
import { config, intLangId, macLangId, workspaceState } from "../../extension";
16+
import { config, FILESYSTEM_SCHEMA, intLangId, macLangId, workspaceState } from "../../extension";
1717
import { addIsfsFileToProject, modifyProject } from "../../commands/project";
1818
import { DocumentContentProvider } from "../DocumentContentProvider";
1919
import { Document, UserAction } from "../../api/atelier";
@@ -512,10 +512,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
512512
// Ignore the recursive flag for project folders
513513
toDeletePromise = projectContentsFromUri(uri, true);
514514
} else {
515-
toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined);
515+
toDeletePromise = studioOpenDialogFromURI(uri, options.recursive ? { flat: true } : undefined).then(
516+
(data) => data.result.content
517+
);
516518
}
517519
const toDelete: string[] = await toDeletePromise.then((data) =>
518-
data.result.content
520+
data
519521
.map((entry) => {
520522
if (options.recursive || project) {
521523
return entry.Name;
@@ -658,6 +660,110 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
658660
await vscode.workspace.fs.delete(oldUri);
659661
}
660662

663+
/**
664+
* If `uri` is a file, compile it.
665+
* If `uri` is a directory, compile its contents.
666+
*/
667+
public async compile(uri: vscode.Uri): Promise<void> {
668+
if (!uri || uri.scheme != FILESYSTEM_SCHEMA) return;
669+
uri = redirectDotvscodeRoot(uri);
670+
const compileList: string[] = [];
671+
try {
672+
const entry = await this._lookup(uri, true);
673+
if (!entry) return;
674+
if (entry instanceof Directory) {
675+
// Get the list of files to compile
676+
let compileListPromise: Promise<any>;
677+
if (new URLSearchParams(uri.query).get("project")?.length) {
678+
compileListPromise = projectContentsFromUri(uri, true);
679+
} else {
680+
compileListPromise = studioOpenDialogFromURI(uri, { flat: true }).then((data) => data.result.content);
681+
}
682+
compileList.push(...(await compileListPromise.then((data) => data.map((e) => e.Name))));
683+
} else {
684+
// Compile this file
685+
compileList.push(isCSPFile(uri) ? uri.path : uri.path.slice(1).replace(/\//g, "."));
686+
}
687+
} catch (error) {
688+
console.log(error);
689+
let errorMsg = "Error determining documents to compile.";
690+
if (error && error.errorText && error.errorText !== "") {
691+
outputChannel.appendLine("\n" + error.errorText);
692+
outputChannel.show(true);
693+
errorMsg += " Check 'ObjectScript' output channel for details.";
694+
}
695+
vscode.window.showErrorMessage(errorMsg, "Dismiss");
696+
return;
697+
}
698+
if (!compileList.length) return;
699+
const api = new AtelierAPI(uri);
700+
const conf = vscode.workspace.getConfiguration("objectscript");
701+
// Compile the files
702+
await vscode.window.withProgress(
703+
{
704+
cancellable: true,
705+
location: vscode.ProgressLocation.Notification,
706+
title: `Compiling: ${compileList.length == 1 ? compileList[0] : compileList.length + " files"}`,
707+
},
708+
(progress, token: vscode.CancellationToken) =>
709+
api
710+
.asyncCompile(compileList, token, conf.get("compileFlags"))
711+
.then((data) => {
712+
const info = compileList.length > 1 ? "" : `${compileList}: `;
713+
if (data.status && data.status.errors && data.status.errors.length) {
714+
throw new Error(`${info}Compile error`);
715+
} else if (!conf.get("suppressCompileMessages")) {
716+
vscode.window.showInformationMessage(`${info}Compilation succeeded.`, "Dismiss");
717+
}
718+
})
719+
.catch(() => {
720+
if (!conf.get("suppressCompileErrorMessages")) {
721+
vscode.window
722+
.showErrorMessage(
723+
"Compilation failed. Check 'ObjectScript' output channel for details.",
724+
"Show",
725+
"Dismiss"
726+
)
727+
.then((action) => {
728+
if (action === "Show") {
729+
outputChannel.show(true);
730+
}
731+
});
732+
}
733+
})
734+
);
735+
// Tell the client to update all "other" files affected by compilation
736+
const workspaceFolder = workspaceFolderOfUri(uri);
737+
const otherList: string[] = await api
738+
.actionIndex(compileList)
739+
.then((data) =>
740+
data.result.content.flatMap((idx) => {
741+
if (!idx.status.length) {
742+
// Update the timestamp for this file
743+
const mtime = Number(new Date(idx.ts + "Z"));
744+
workspaceState.update(`${workspaceFolder}:${idx.name}:mtime`, mtime > 0 ? mtime : undefined);
745+
// Tell the client that it changed
746+
this.fireFileChanged(DocumentContentProvider.getUri(idx.name, undefined, undefined, undefined, uri));
747+
// Return the list of "other" documents
748+
return idx.others;
749+
} else {
750+
// The server failed to index the document. This should never happen.
751+
return [];
752+
}
753+
})
754+
)
755+
.catch(() => {
756+
// Index API returned an error. This should never happen.
757+
return [];
758+
});
759+
// Only fire the event for files that weren't in the compile list
760+
otherList.forEach(
761+
(f) =>
762+
!compileList.includes(f) &&
763+
this.fireFileChanged(DocumentContentProvider.getUri(f, undefined, undefined, undefined, uri))
764+
);
765+
}
766+
661767
public watch(uri: vscode.Uri): vscode.Disposable {
662768
return new vscode.Disposable(() => {
663769
return;

src/utils/FileProviderUtil.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ export async function projectContentsFromUri(uri: vscode.Uri, overrideFlat?: boo
2525
"SELECT CASE " +
2626
"WHEN Type = 'CLS' THEN Name||'.cls' " +
2727
"ELSE Name END Name, Type FROM %Studio.Project_ProjectItemsList(?) " +
28-
"WHERE (Name %STARTSWITH ? OR Name %STARTSWITH ?) AND (" +
28+
"WHERE (Name %STARTSWITH ? OR Name %STARTSWITH ?) AND ((" +
2929
"(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 " +
3030
"(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 " +
3131
"(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 " +
32-
"(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)))) " +
32+
"(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))))) " +
3333
"UNION " +
34-
"SELECT SUBSTR(sod.Name,2) AS Name, pil.Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,0,1) AS sod " +
34+
"SELECT SUBSTR(sod.Name,2) AS Name, pil.Type FROM %Library.RoutineMgr_StudioOpenDialog('*.cspall',1,1,1,1,0,1,?) AS sod " +
3535
"JOIN %Studio.Project_ProjectItemsList(?,1) AS pil ON " +
3636
"pil.Type = 'DIR' AND SUBSTR(sod.Name,2) %STARTSWITH ? AND SUBSTR(sod.Name,2) %STARTSWITH pil.Name||'/'";
37-
parameters = [project, folderDots, folder, folder + "*.cspall", project, folder];
37+
parameters = [project, folderDots, folder, `Name %STARTSWITH '/${folder}'`, project, folder];
3838
} else {
3939
if (folder.length) {
4040
const l = String(folder.length + 1); // Need the + 1 because SUBSTR is 1 indexed

0 commit comments

Comments
 (0)