Skip to content

Commit 8ae7e38

Browse files
Merge branch 'master' into fuchsia-lungfish
2 parents e46b8bb + 72d0a76 commit 8ae7e38

File tree

13 files changed

+643
-365
lines changed

13 files changed

+643
-365
lines changed

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@
287287
"command": "vscode-objectscript.newFile.kpi",
288288
"when": "false"
289289
},
290+
{
291+
"command": "vscode-objectscript.newFile.message",
292+
"when": "false"
293+
},
290294
{
291295
"command": "vscode-objectscript.importLocalFilesServerSide",
292296
"when": "false"
@@ -334,6 +338,10 @@
334338
{
335339
"command": "vscode-objectscript.openISCDocument",
336340
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
341+
},
342+
{
343+
"command": "vscode-objectscript.connectFolderToServerNamespace",
344+
"when": "!vscode-objectscript.connectActive && vscode-objectscript.explorerRootCount == 0 && workspaceFolderCount != 0"
337345
}
338346
],
339347
"view/title": [
@@ -650,6 +658,11 @@
650658
"command": "vscode-objectscript.newFile.dtl",
651659
"when": "workspaceFolderCount != 0",
652660
"group": "file"
661+
},
662+
{
663+
"command": "vscode-objectscript.newFile.message",
664+
"when": "workspaceFolderCount != 0",
665+
"group": "file"
653666
}
654667
]
655668
},
@@ -1116,6 +1129,11 @@
11161129
"command": "vscode-objectscript.newFile.dtl",
11171130
"title": "Data Transformation"
11181131
},
1132+
{
1133+
"category": "ObjectScript",
1134+
"command": "vscode-objectscript.newFile.message",
1135+
"title": "Interoperability Message"
1136+
},
11191137
{
11201138
"category": "ObjectScript",
11211139
"command": "vscode-objectscript.importLocalFilesServerSide",

src/api/index.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class AtelierAPI {
111111
if (
112112
parts.length === 2 &&
113113
(config("intersystems.servers").has(parts[0].toLowerCase()) ||
114-
vscode.workspace.workspaceFolders.find(
114+
vscode.workspace.workspaceFolders?.find(
115115
(ws) => ws.uri.scheme === "file" && ws.name.toLowerCase() === parts[0].toLowerCase()
116116
))
117117
) {
@@ -140,6 +140,29 @@ export class AtelierAPI {
140140
this.namespace = namespace;
141141
}
142142

143+
/**
144+
* Manually set the connection spec for this object,
145+
* where `connSpec` is the return value of `getResolvedConnectionSpec()`.
146+
*/
147+
public setConnSpec(serverName: string, connSpec: any): void {
148+
const {
149+
webServer: { scheme, host, port, pathPrefix = "" },
150+
username,
151+
password,
152+
} = connSpec;
153+
this._config.username = username;
154+
this._config.password = password;
155+
this._config.https = scheme == "https";
156+
this._config.host = host;
157+
this._config.port = port;
158+
this._config.pathPrefix = pathPrefix;
159+
this._config.apiVersion = this._config.apiVersion || DEFAULT_API_VERSION;
160+
this._config.serverVersion = this._config.serverVersion || DEFAULT_SERVER_VERSION;
161+
this._config.docker = false;
162+
this._config.active = true;
163+
this._config.serverName = serverName;
164+
}
165+
143166
public get active(): boolean {
144167
const { host = "", port = 0 } = this.config;
145168
return !!this._config.active && host.length > 0 && port > 0;
@@ -185,7 +208,7 @@ export class AtelierAPI {
185208
this.configName = workspaceFolderName;
186209
const conn = config("conn", workspaceFolderName);
187210
let serverName = workspaceFolderName.toLowerCase();
188-
if (config("intersystems.servers").has(serverName)) {
211+
if (config("intersystems.servers", workspaceFolderName).has(serverName)) {
189212
this.externalServer = true;
190213
} else if (
191214
!(conn["docker-compose"] && extensionContext.extension.extensionKind !== vscode.ExtensionKind.Workspace) &&

src/commands/addServerNamespaceToWorkspace.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
FILESYSTEM_SCHEMA,
88
FILESYSTEM_READONLY_SCHEMA,
99
filesystemSchemas,
10-
smExtensionId,
10+
serverManagerApi,
1111
} from "../extension";
1212
import { cspAppsForUri, getWsFolder, handleError, notIsfs } from "../utils";
1313
import { pickProject } from "./project";
@@ -18,7 +18,6 @@ import { isfsConfig, IsfsUriParam } from "../utils/FileProviderUtil";
1818
* @returns An object containing `serverName` and `namespace`, or `undefined`.
1919
*/
2020
export async function pickServerAndNamespace(message?: string): Promise<{ serverName: string; namespace: string }> {
21-
const serverManagerApi = await getServerManagerApi();
2221
if (!serverManagerApi) {
2322
vscode.window.showErrorMessage(
2423
`${
@@ -168,22 +167,6 @@ export async function addServerNamespaceToWorkspace(resource?: vscode.Uri): Prom
168167
}
169168
}
170169

171-
export async function getServerManagerApi(): Promise<any> {
172-
const targetExtension = vscode.extensions.getExtension(smExtensionId);
173-
if (!targetExtension) {
174-
return undefined;
175-
}
176-
if (!targetExtension.isActive) {
177-
await targetExtension.activate();
178-
}
179-
const api = targetExtension.exports;
180-
181-
if (!api) {
182-
return undefined;
183-
}
184-
return api;
185-
}
186-
187170
/** Prompt the user to fill in the `path` and `query` of `uri`. */
188171
async function modifyWsFolderUri(uri: vscode.Uri): Promise<vscode.Uri | undefined> {
189172
if (notIsfs(uri)) return;

src/commands/compile.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export async function importFile(
9292
ignoreConflict?: boolean,
9393
skipDeplCheck = false
9494
): Promise<any> {
95+
if (!file) return;
9596
const api = new AtelierAPI(file.uri);
9697
if (!api.active) return;
9798
if (file.name.split(".").pop().toLowerCase() === "cls" && !skipDeplCheck) {
@@ -261,6 +262,8 @@ export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]
261262
}
262263

263264
export async function compile(docs: (CurrentTextFile | CurrentBinaryFile)[], flags?: string): Promise<any> {
265+
docs = docs.filter(notNull);
266+
if (!docs.length) return;
264267
const wsFolder = vscode.workspace.getWorkspaceFolder(docs[0].uri);
265268
const conf = vscode.workspace.getConfiguration("objectscript", wsFolder || docs[0].uri);
266269
flags = flags || conf.get("compileFlags");
@@ -379,9 +382,7 @@ export async function compileOnly(askFlags = false, document?: vscode.TextDocume
379382
export async function namespaceCompile(askFlags = false): Promise<any> {
380383
const api = new AtelierAPI();
381384
const fileTypes = ["*.CLS", "*.MAC", "*.INC", "*.BAS"];
382-
if (!config("conn").active) {
383-
throw new Error(`No Active Connection`);
384-
}
385+
if (!api.active) return;
385386
const confirm = await vscode.window.showWarningMessage(
386387
`Compiling all files in namespace ${api.ns} might be expensive. Are you sure you want to proceed?`,
387388
"Cancel",
@@ -437,18 +438,20 @@ async function importFiles(files: vscode.Uri[], noCompile = false) {
437438
rateLimiter.call(async () => {
438439
return vscode.workspace.fs
439440
.readFile(uri)
440-
.then((contentBytes) => {
441-
if (isText(uri.path.split("/").pop(), Buffer.from(contentBytes))) {
442-
const textFile = currentFileFromContent(uri, new TextDecoder().decode(contentBytes));
443-
toCompile.push(textFile);
444-
return textFile;
445-
} else {
446-
return currentFileFromContent(uri, Buffer.from(contentBytes));
441+
.then((contentBytes) =>
442+
currentFileFromContent(
443+
uri,
444+
isText(uri.path.split("/").pop(), Buffer.from(contentBytes))
445+
? new TextDecoder().decode(contentBytes)
446+
: Buffer.from(contentBytes)
447+
)
448+
)
449+
.then((curFile) => {
450+
if (curFile) {
451+
if (typeof curFile.content == "string") toCompile.push(curFile); // Only compile text files
452+
return importFile(curFile).then(() => outputChannel.appendLine("Imported file: " + curFile.fileName));
447453
}
448-
})
449-
.then((curFile) =>
450-
importFile(curFile).then(() => outputChannel.appendLine("Imported file: " + curFile.fileName))
451-
);
454+
});
452455
})
453456
)
454457
);
@@ -460,6 +463,7 @@ async function importFiles(files: vscode.Uri[], noCompile = false) {
460463
}
461464

462465
export async function importFolder(uri: vscode.Uri, noCompile = false): Promise<any> {
466+
if (!(uri instanceof vscode.Uri)) return;
463467
if (filesystemSchemas.includes(uri.scheme)) return; // Not for server-side URIs
464468
if ((await vscode.workspace.fs.stat(uri)).type != vscode.FileType.Directory) {
465469
return importFiles([uri], noCompile);
Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as vscode from "vscode";
22
import { AtelierAPI } from "../api";
3-
import { panel, resolveConnectionSpec, getResolvedConnectionSpec, smExtensionId } from "../extension";
4-
import { notIsfs } from "../utils";
3+
import {
4+
panel,
5+
resolveConnectionSpec,
6+
getResolvedConnectionSpec,
7+
serverManagerApi,
8+
resolveUsernameAndPassword,
9+
} from "../extension";
10+
import { handleError, isUnauthenticated, notIsfs } from "../utils";
511

612
interface ConnSettings {
713
server: string;
@@ -14,7 +20,6 @@ export async function connectFolderToServerNamespace(): Promise<void> {
1420
vscode.window.showErrorMessage("No folders in the workspace.", "Dismiss");
1521
return;
1622
}
17-
const serverManagerApi = await getServerManagerApi();
1823
if (!serverManagerApi) {
1924
vscode.window.showErrorMessage(
2025
"Connecting a folder to a server namespace requires the [InterSystems Server Manager extension](https://marketplace.visualstudio.com/items?itemName=intersystems-community.servermanager) to be installed and enabled.",
@@ -31,42 +36,72 @@ export async function connectFolderToServerNamespace(): Promise<void> {
3136
return {
3237
label: folder.name,
3338
description: folder.uri.fsPath,
34-
detail: !conn.server ? undefined : `Currently connected to ${conn.ns} on ${conn.server}`,
39+
detail:
40+
!conn.server || !conn.active
41+
? "No active server connection"
42+
: `Currently connected to ${conn.ns} on ${conn.server}`,
3543
};
3644
});
3745
if (!items.length) {
3846
vscode.window.showErrorMessage("No local folders in the workspace.", "Dismiss");
3947
return;
4048
}
4149
const pick =
42-
items.length === 1 && !items[0].detail
50+
items.length == 1 && !items[0].detail.startsWith("Currently")
4351
? items[0]
4452
: await vscode.window.showQuickPick(items, { title: "Pick a folder" });
53+
if (!pick) return;
4554
const folder = vscode.workspace.workspaceFolders.find((el) => el.name === pick.label);
4655
// Get user's choice of server
4756
const options: vscode.QuickPickOptions = {};
48-
const serverName: string = await serverManagerApi.pickServer(undefined, options);
57+
const serverName: string = await serverManagerApi.pickServer(folder, options);
4958
if (!serverName) {
5059
return;
5160
}
52-
// Get its namespace list
53-
const uri = vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`);
54-
await resolveConnectionSpec(serverName);
61+
await resolveConnectionSpec(serverName, undefined, folder);
5562
// Prepare a displayable form of its connection spec as a hint to the user
5663
// This will never return the default value (second parameter) because we only just resolved the connection spec.
5764
const connSpec = getResolvedConnectionSpec(serverName, undefined);
5865
const connDisplayString = `${connSpec.webServer.scheme}://${connSpec.webServer.host}:${connSpec.webServer.port}/${connSpec.webServer.pathPrefix}`;
5966
// Connect and fetch namespaces
60-
const api = new AtelierAPI(uri);
61-
const allNamespaces: string[] | undefined = await api
67+
const api = new AtelierAPI(vscode.Uri.parse(`isfs://${serverName}/?ns=%SYS`));
68+
const serverConf = vscode.workspace
69+
.getConfiguration("intersystems", folder)
70+
.inspect<{ [key: string]: any }>("servers");
71+
if (
72+
serverConf.workspaceFolderValue &&
73+
typeof serverConf.workspaceFolderValue[serverName] == "object" &&
74+
!(serverConf.workspaceValue && typeof serverConf.workspaceValue[serverName] == "object")
75+
) {
76+
// Need to manually set connection info if the server is defined at the workspace folder level
77+
api.setConnSpec(serverName, connSpec);
78+
}
79+
const allNamespaces: string[] = await api
6280
.serverInfo(false)
6381
.then((data) => data.result.content.namespaces)
64-
.catch((reason) => {
65-
// Notify user about serverInfo failure
66-
vscode.window.showErrorMessage(
67-
reason.message || `Failed to fetch namespace list from server at ${connDisplayString}`,
68-
"Dismiss"
69-
);
82+
.catch(async (error) => {
83+
if (error?.statusCode == 401 && isUnauthenticated(api.config.username)) {
84+
// Attempt to resolve username and password and try again
85+
const newSpec = await resolveUsernameAndPassword(api.config.serverName, connSpec);
86+
if (newSpec) {
87+
// We were able to resolve credentials, so try again
88+
api.setConnSpec(api.config.serverName, newSpec);
89+
return api
90+
.serverInfo(false)
91+
.then((data) => data.result.content.namespaces)
92+
.catch(async (err) => {
93+
handleError(err, `Failed to fetch namespace list from server at ${connDisplayString}.`);
94+
return undefined;
95+
});
96+
} else {
97+
handleError(
98+
`Unauthenticated access rejected by '${api.serverId}'.`,
99+
`Failed to fetch namespace list from server at ${connDisplayString}.`
100+
);
101+
return undefined;
102+
}
103+
}
104+
handleError(error, `Failed to fetch namespace list from server at ${connDisplayString}.`);
70105
return undefined;
71106
});
72107
// Clear the panel entry created by the connection
@@ -90,22 +125,34 @@ export async function connectFolderToServerNamespace(): Promise<void> {
90125
}
91126
// Update folder's config object
92127
const config = vscode.workspace.getConfiguration("objectscript", folder);
93-
const conn: any = config.inspect("conn").workspaceFolderValue;
94-
await config.update("conn", { ...conn, server: serverName, ns: namespace, active: true });
95-
}
96-
97-
async function getServerManagerApi(): Promise<any> {
98-
const targetExtension = vscode.extensions.getExtension(smExtensionId);
99-
if (!targetExtension) {
100-
return undefined;
101-
}
102-
if (!targetExtension.isActive) {
103-
await targetExtension.activate();
104-
}
105-
const api = targetExtension.exports;
106-
107-
if (!api) {
108-
return undefined;
128+
if (vscode.workspace.workspaceFile && items.length == 1) {
129+
// Ask the user if they want to enable the connection at the workspace or folder level.
130+
// Only allow this when there is a single client-side folder in the workspace because
131+
// the server may be configured at the workspace folder level.
132+
const answer = await vscode.window.showQuickPick(
133+
[
134+
{ label: `Workspace Folder ${folder.name}`, detail: folder.uri.toString(true) },
135+
{ label: "Workspace File", detail: vscode.workspace.workspaceFile.toString(true) },
136+
],
137+
{ title: "Store the server connection at the workspace or folder level?" }
138+
);
139+
if (!answer) return;
140+
if (answer.label == "Workspace File") {
141+
// Enable the connection at the workspace level
142+
const conn: any = config.inspect("conn").workspaceValue;
143+
await config.update(
144+
"conn",
145+
{ ...conn, server: serverName, ns: namespace, active: true },
146+
vscode.ConfigurationTarget.Workspace
147+
);
148+
return;
149+
}
109150
}
110-
return api;
151+
// Enable the connection at the workspace folder level
152+
const conn: any = config.inspect("conn").workspaceFolderValue;
153+
await config.update(
154+
"conn",
155+
{ ...conn, server: serverName, ns: namespace, active: true },
156+
vscode.ConfigurationTarget.WorkspaceFolder
157+
);
111158
}

0 commit comments

Comments
 (0)