Skip to content

Commit f4eecde

Browse files
committed
Check if /_vscode web app exists before redirecting .vscode files
1 parent b5078fc commit f4eecde

File tree

3 files changed

+126
-84
lines changed

3 files changed

+126
-84
lines changed

src/extension.ts

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -446,21 +446,6 @@ export async function checkConnection(
446446
serverVersion: info.result.content.version,
447447
healthshare: hasHS ? "yes" : "no",
448448
});
449-
450-
// Update CSP web app cache if required
451-
const key = `${api.serverId}:${api.config.ns}`.toLowerCase();
452-
if (!cspApps.has(key)) {
453-
cspApps.set(key, await api.getCSPApps().then((data) => data.result.content || []));
454-
}
455-
if (!otherDocExts.has(key)) {
456-
otherDocExts.set(
457-
key,
458-
await api
459-
.actionQuery("SELECT Extention FROM %Library.RoutineMgr_DocumentTypes()", [])
460-
.then((data) => data.result?.content?.map((e) => e.Extention) ?? [])
461-
.catch(() => [])
462-
);
463-
}
464449
if (!api.externalServer) {
465450
await setConnectionState(configName, true);
466451
}
@@ -756,6 +741,44 @@ function setExplorerContextKeys(): void {
756741
);
757742
}
758743

744+
/** Cache the lists of web apps and abstract document types for all server-namespaces in `wsFolders` */
745+
async function updateWebAndAbstractDocsCaches(wsFolders: readonly vscode.WorkspaceFolder[]): Promise<any> {
746+
if (!wsFolders?.length) return;
747+
const keys: Set<string> = new Set();
748+
const connections: { key: string; api: AtelierAPI }[] = [];
749+
// Filter out any duplicate connections
750+
for (const wsFolder of wsFolders) {
751+
const api = new AtelierAPI(wsFolder.uri);
752+
if (!api.active) continue;
753+
const key = `${api.serverId}:${api.config.ns}`.toLowerCase();
754+
if (keys.has(key)) continue;
755+
keys.add(key);
756+
connections.push({ key, api });
757+
}
758+
return Promise.allSettled(
759+
connections.map(async (connection) => {
760+
if (!cspApps.has(connection.key)) {
761+
cspApps.set(
762+
connection.key,
763+
await connection.api
764+
.getCSPApps()
765+
.then((data) => data.result.content ?? [])
766+
.catch(() => [])
767+
);
768+
}
769+
if (!otherDocExts.has(connection.key)) {
770+
otherDocExts.set(
771+
connection.key,
772+
await connection.api
773+
.actionQuery("SELECT Extention FROM %Library.RoutineMgr_DocumentTypes()", [])
774+
.then((data) => data.result?.content?.map((e) => e.Extention) ?? [])
775+
.catch(() => [])
776+
);
777+
}
778+
})
779+
);
780+
}
781+
759782
/** The URIs of all classes that have been opened. Used when `objectscript.openClassContracted` is true */
760783
let openedClasses: string[];
761784

@@ -847,9 +870,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
847870
continue;
848871
}
849872
}
850-
await Promise.allSettled(
851-
vscode.workspace.workspaceFolders?.map((wsFolder) => addWsServerRootFolderData(wsFolder.uri)) || []
852-
);
873+
874+
await updateWebAndAbstractDocsCaches(vscode.workspace.workspaceFolders);
875+
await addWsServerRootFolderData(vscode.workspace.workspaceFolders);
853876

854877
xmlContentProvider = new XmlContentProvider();
855878

@@ -1373,22 +1396,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
13731396
},
13741397
supportsMultipleEditorsPerDocument: false,
13751398
}),
1376-
vscode.workspace.onDidChangeWorkspaceFolders(async ({ added }) => {
1377-
// Make sure we have a resolved connection spec for the targets of all added folders
1378-
const toCheck = new Map<string, vscode.Uri>();
1379-
added.map((workspaceFolder) => {
1380-
const uri = workspaceFolder.uri;
1381-
const { configName } = connectionTarget(uri);
1382-
toCheck.set(configName, uri);
1383-
});
1384-
for await (const oneToCheck of toCheck) {
1385-
const configName = oneToCheck[0];
1386-
const uri = oneToCheck[1];
1387-
const serverName = notIsfs(uri) ? config("conn", configName).server : configName;
1388-
await resolveConnectionSpec(serverName);
1389-
}
1390-
await Promise.allSettled(added.map((wsFolder) => addWsServerRootFolderData(wsFolder.uri)));
1391-
}),
13921399
vscode.workspace.onDidChangeConfiguration(async ({ affectsConfiguration }) => {
13931400
if (affectsConfiguration("objectscript.conn") || affectsConfiguration("intersystems.servers")) {
13941401
if (affectsConfiguration("intersystems.servers")) {
@@ -1545,7 +1552,23 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
15451552
"vscode-objectscript.webSocketTerminal",
15461553
new WebSocketTerminalProfileProvider()
15471554
),
1548-
vscode.workspace.onDidChangeWorkspaceFolders((e) => {
1555+
vscode.workspace.onDidChangeWorkspaceFolders(async (e) => {
1556+
// Make sure we have a resolved connection spec for the targets of all added folders
1557+
const toCheck = new Map<string, vscode.Uri>();
1558+
e.added.map((workspaceFolder) => {
1559+
const uri = workspaceFolder.uri;
1560+
const { configName } = connectionTarget(uri);
1561+
toCheck.set(configName, uri);
1562+
});
1563+
for await (const oneToCheck of toCheck) {
1564+
const configName = oneToCheck[0];
1565+
const uri = oneToCheck[1];
1566+
const serverName = notIsfs(uri) ? config("conn", configName).server : configName;
1567+
await resolveConnectionSpec(serverName);
1568+
}
1569+
// await this so the next step can take advantage of the caching
1570+
await updateWebAndAbstractDocsCaches(e.added);
1571+
addWsServerRootFolderData(e.added);
15491572
// Show the proposed API prompt if required
15501573
proposedApiPrompt(proposed.length > 0, e.added);
15511574
// Warn about SystemMode

src/providers/FileSystemProvider/FileSystemProvider.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
243243
if (!api.active) throw vscode.FileSystemError.Unavailable("Server connection is inactive");
244244
let entryPromise: Promise<Entry>;
245245
let result: Entry;
246-
const redirectedUri = redirectDotvscodeRoot(uri);
246+
const redirectedUri = redirectDotvscodeRoot(uri, vscode.FileSystemError.FileNotFound(uri));
247247
if (redirectedUri.path !== uri.path) {
248248
// When redirecting the /.vscode subtree we must fill in as-yet-unvisited folders to fix https://github.com/intersystems-community/vscode-objectscript/issues/1143
249249
entryPromise = this._lookup(redirectedUri, true);
@@ -290,8 +290,8 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
290290
}
291291

292292
public async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
293-
if (uri.path.includes(".vscode/")) {
294-
throw vscode.FileSystemError.NoPermissions("Cannot read the /.vscode directory");
293+
if (uri.path.includes(".vscode/") || uri.path.endsWith(".vscode")) {
294+
throw "Cannot read the /.vscode directory";
295295
}
296296
const parent = await this._lookupAsDirectory(uri);
297297
const api = new AtelierAPI(uri);
@@ -406,7 +406,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
406406
}
407407

408408
public createDirectory(uri: vscode.Uri): void | Thenable<void> {
409-
uri = redirectDotvscodeRoot(uri);
409+
uri = redirectDotvscodeRoot(uri, "Server does not have a /_vscode web application");
410410
const basename = path.posix.basename(uri.path);
411411
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
412412
return this._lookupAsDirectory(dirname).then((parent) => {
@@ -435,9 +435,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
435435
overwrite: boolean;
436436
}
437437
): void | Thenable<void> {
438-
uri = redirectDotvscodeRoot(uri);
438+
uri = redirectDotvscodeRoot(uri, "Server does not have a /_vscode web application");
439439
if (uri.path.startsWith("/.")) {
440-
throw vscode.FileSystemError.NoPermissions("dot-folders not supported by server");
440+
throw "dot-folders are not supported by server";
441441
}
442442
const csp = isCSP(uri);
443443
const fileName = isfsDocumentName(uri, csp);
@@ -639,7 +639,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
639639
}
640640

641641
public async delete(uri: vscode.Uri, options: { recursive: boolean }): Promise<void> {
642-
uri = redirectDotvscodeRoot(uri);
642+
uri = redirectDotvscodeRoot(uri, vscode.FileSystemError.FileNotFound(uri));
643643
const { project } = isfsConfig(uri);
644644
const csp = isCSP(uri);
645645
const api = new AtelierAPI(uri);
@@ -724,13 +724,13 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
724724

725725
public async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): Promise<void> {
726726
if (!oldUri.path.split("/").pop().includes(".")) {
727-
throw vscode.FileSystemError.NoPermissions("Cannot rename a package/folder");
727+
throw "Cannot rename a package/folder";
728728
}
729729
if (oldUri.path.split(".").pop().toLowerCase() != newUri.path.split(".").pop().toLowerCase()) {
730-
throw vscode.FileSystemError.NoPermissions("Cannot change a file's extension during rename");
730+
throw "Cannot change a file's extension during rename";
731731
}
732732
if (vscode.workspace.getWorkspaceFolder(oldUri) != vscode.workspace.getWorkspaceFolder(newUri)) {
733-
throw vscode.FileSystemError.NoPermissions("Cannot rename a file across workspace folders");
733+
throw "Cannot rename a file across workspace folders";
734734
}
735735
// Check if the destination exists
736736
let newFileStat: vscode.FileStat;
@@ -807,7 +807,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
807807
*/
808808
public async compile(uri: vscode.Uri, file?: File, update?: boolean): Promise<void> {
809809
if (!uri || uri.scheme != FILESYSTEM_SCHEMA) return;
810-
uri = redirectDotvscodeRoot(uri);
810+
uri = redirectDotvscodeRoot(uri, "Server does not have a /_vscode web application");
811811
const compileList: string[] = [];
812812
try {
813813
const entry = file || (await this._lookup(uri, true));
@@ -958,9 +958,9 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
958958

959959
// Fetch from server and cache it, optionally the passed cached copy if unchanged on server
960960
private async _lookupAsFile(uri: vscode.Uri, cachedFile?: File): Promise<File> {
961-
uri = redirectDotvscodeRoot(uri);
961+
uri = redirectDotvscodeRoot(uri, vscode.FileSystemError.FileNotFound(uri));
962962
if (uri.path.startsWith("/.")) {
963-
throw vscode.FileSystemError.NoPermissions("dot-folders not supported by server");
963+
throw "dot-folders are not supported by server";
964964
}
965965
const csp = isCSP(uri);
966966
const name = path.basename(uri.path);
@@ -998,7 +998,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
998998
}
999999

10001000
private async _lookupParentDirectory(uri: vscode.Uri): Promise<Directory> {
1001-
uri = redirectDotvscodeRoot(uri);
1001+
uri = redirectDotvscodeRoot(uri, "Server does not have a /_vscode web application");
10021002
return this._lookupAsDirectory(uri.with({ path: path.posix.dirname(uri.path) }));
10031003
}
10041004

src/utils/index.ts

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,7 @@ export function cspAppsForUri(uri: vscode.Uri): string[] {
140140

141141
/** Get a list of all CSP web apps in the server-namespace that `api` is connected to. */
142142
export function cspAppsForApi(api: AtelierAPI): string[] {
143-
return (
144-
cspApps.get(
145-
(api.config.serverName && api.config.serverName != ""
146-
? `${api.config.serverName}:${api.config.ns}`
147-
: `${api.config.host}:${api.config.port}${api.config.pathPrefix}:${api.config.ns}`
148-
).toLowerCase()
149-
) ?? []
150-
);
143+
return cspApps.get(`${api.serverId}:${api.config.ns}`.toLowerCase()) ?? [];
151144
}
152145

153146
/**
@@ -598,31 +591,50 @@ export async function shellWithDocker(): Promise<vscode.Terminal> {
598591

599592
interface WSServerRootFolderData {
600593
redirectDotvscode: boolean;
594+
canRedirectDotvscode: boolean;
601595
}
602596

603597
const wsServerRootFolders = new Map<string, WSServerRootFolderData>();
604598

605-
/**
606-
* Add uri to the wsServerRootFolders map if eligible
607-
*/
608-
export async function addWsServerRootFolderData(uri: vscode.Uri): Promise<void> {
609-
if (!schemas.includes(uri.scheme)) {
610-
return;
611-
}
612-
const value: WSServerRootFolderData = {
613-
redirectDotvscode: true,
614-
};
615-
if (isCSP(uri) && !["", "/"].includes(uri.path)) {
616-
// A CSP-type root folder for a specific webapp that already has a .vscode/settings.json file must not redirect .vscode/* references
617-
const api = new AtelierAPI(uri);
618-
api
619-
.headDoc(`${uri.path}${!uri.path.endsWith("/") ? "/" : ""}.vscode/settings.json`)
620-
.then(() => {
621-
value.redirectDotvscode = false;
622-
})
623-
.catch(() => {});
624-
}
625-
wsServerRootFolders.set(uri.toString(), value);
599+
/** Cache information about redirection of `.vscode` folder contents for server-side folders */
600+
export async function addWsServerRootFolderData(wsFolders: readonly vscode.WorkspaceFolder[]): Promise<any> {
601+
if (!wsFolders?.length) return;
602+
return Promise.allSettled(
603+
wsFolders.map(async (wsFolder) => {
604+
if (notIsfs(wsFolder.uri)) return;
605+
const api = new AtelierAPI(wsFolder.uri);
606+
if (!api.active) return;
607+
const value: WSServerRootFolderData = {
608+
redirectDotvscode: true,
609+
canRedirectDotvscode: true,
610+
};
611+
if (isCSP(wsFolder.uri) && !["", "/"].includes(wsFolder.uri.path)) {
612+
// A CSP-type root folder for a specific webapp that already has a
613+
// .vscode/settings.json file must not redirect .vscode/* references
614+
await api
615+
.headDoc(`${wsFolder.uri.path}${!wsFolder.uri.path.endsWith("/") ? "/" : ""}.vscode/settings.json`)
616+
.then(() => {
617+
value.redirectDotvscode = false;
618+
})
619+
.catch(() => {});
620+
}
621+
if (value.redirectDotvscode) {
622+
// We must redirect .vscode Uris for this folder, so see
623+
// if the web app to do so is configured on the server
624+
const key = `${api.serverId}:%SYS`.toLowerCase();
625+
let webApps = cspApps.get(key);
626+
if (!webApps) {
627+
webApps = await api
628+
.getCSPApps(false, "%SYS")
629+
.then((data) => data.result.content ?? [])
630+
.catch(() => []);
631+
cspApps.set(key, webApps);
632+
}
633+
value.canRedirectDotvscode = webApps.includes("/_vscode");
634+
}
635+
wsServerRootFolders.set(wsFolder.uri.toString(), value);
636+
})
637+
);
626638
}
627639

628640
/**
@@ -635,16 +647,23 @@ export async function addWsServerRootFolderData(uri: vscode.Uri): Promise<void>
635647
* For both syntaxes the namespace folder name is uppercased
636648
*
637649
* @returns uri, altered if necessary.
638-
* @throws if `ns` queryparam is missing but required.
650+
* @throws if `ns` queryparam is missing but required, or if redirection
651+
* is required but not supported by the server and `err` was passed.
639652
*/
640-
export function redirectDotvscodeRoot(uri: vscode.Uri): vscode.Uri {
641-
if (!schemas.includes(uri.scheme)) {
642-
return uri;
643-
}
653+
export function redirectDotvscodeRoot(uri: vscode.Uri, err?: any): vscode.Uri {
654+
if (notIsfs(uri)) return;
644655
const dotMatch = uri.path.match(/^(.*)\/\.vscode(\/.*)?$/);
645656
if (dotMatch) {
646657
const dotvscodeRoot = uri.with({ path: dotMatch[1] || "/" });
647-
if (!wsServerRootFolders.get(dotvscodeRoot.toString())?.redirectDotvscode) {
658+
const rootData = wsServerRootFolders.get(dotvscodeRoot.toString());
659+
if (!rootData?.redirectDotvscode) {
660+
// Don't redirect .vscode Uris
661+
return uri;
662+
}
663+
if (!rootData?.canRedirectDotvscode) {
664+
// Need to redirect .vscode Uris, but the server doesn't support it.
665+
// Throw if the caller gave us something to throw.
666+
if (err) throw err;
648667
return uri;
649668
}
650669
let namespace: string;

0 commit comments

Comments
 (0)