Skip to content

Commit 5631f6a

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fix-client-side
2 parents 9d60e10 + 75f6ea9 commit 5631f6a

File tree

6 files changed

+187
-68
lines changed

6 files changed

+187
-68
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@
297297
},
298298
{
299299
"command": "vscode-objectscript.modifyWsFolder",
300-
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
300+
"when": "workspaceFolderCount != 0"
301301
},
302302
{
303303
"command": "vscode-objectscript.openErrorLocation",
@@ -606,7 +606,7 @@
606606
},
607607
{
608608
"command": "vscode-objectscript.modifyWsFolder",
609-
"when": "vscode-objectscript.connectActive && resourceScheme =~ /^isfs(-readonly)?$/ && explorerResourceIsRoot && !listMultiSelection",
609+
"when": "(!resourceScheme || resourceScheme =~ /^isfs(-readonly)?$/) && explorerResourceIsRoot && !listMultiSelection",
610610
"group": "objectscript_modify@3"
611611
},
612612
{

src/api/index.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ export class AtelierAPI {
110110
if (schemas.includes(wsOrFile.scheme)) {
111111
workspaceFolderName = wsOrFile.authority;
112112
const parts = workspaceFolderName.split(":");
113-
if (parts.length === 2 && config("intersystems.servers").has(parts[0].toLowerCase())) {
113+
if (
114+
parts.length === 2 &&
115+
(config("intersystems.servers").has(parts[0].toLowerCase()) ||
116+
vscode.workspace.workspaceFolders.find(
117+
(ws) => ws.uri.scheme === "file" && ws.name.toLowerCase() === parts[0].toLowerCase()
118+
))
119+
) {
114120
workspaceFolderName = parts[0];
115121
namespace = parts[1];
116122
} else {
@@ -227,6 +233,35 @@ export class AtelierAPI {
227233
if (this._config.ns === "" && this.externalServer) {
228234
this._config.active = false;
229235
}
236+
} else if (conn["docker-compose"]) {
237+
// Provided a docker-compose type connection spec has previously been resolved we can use its values
238+
const resolvedSpec = getResolvedConnectionSpec(workspaceFolderName, undefined);
239+
if (resolvedSpec) {
240+
const {
241+
webServer: { scheme, host, port, pathPrefix = "" },
242+
username,
243+
password,
244+
} = resolvedSpec;
245+
this._config = {
246+
serverName: "",
247+
active: true,
248+
apiVersion: workspaceState.get(this.configName.toLowerCase() + ":apiVersion", DEFAULT_API_VERSION),
249+
serverVersion: workspaceState.get(this.configName.toLowerCase() + ":serverVersion", DEFAULT_SERVER_VERSION),
250+
https: scheme === "https",
251+
ns,
252+
host,
253+
port,
254+
username,
255+
password,
256+
pathPrefix,
257+
docker: true,
258+
dockerService: conn["docker-compose"].service,
259+
};
260+
} else {
261+
this._config = conn;
262+
this._config.ns = ns;
263+
this._config.serverName = "";
264+
}
230265
} else {
231266
this._config = conn;
232267
this._config.ns = ns;

src/commands/addServerNamespaceToWorkspace.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -397,20 +397,20 @@ export async function modifyWsFolder(wsFolderUri?: vscode.Uri): Promise<void> {
397397
if (!wsFolder) {
398398
return;
399399
}
400-
if (notIsfs(wsFolder.uri)) {
401-
vscode.window.showErrorMessage(
402-
`Workspace folder '${wsFolder.name}' does not have scheme 'isfs' or 'isfs-readonly'.`,
403-
"Dismiss"
404-
);
405-
return;
406-
}
407400
} else {
408401
// Find the workspace folder for this uri
409402
wsFolder = vscode.workspace.getWorkspaceFolder(wsFolderUri);
410403
if (!wsFolder) {
411404
return;
412405
}
413406
}
407+
if (notIsfs(wsFolder.uri)) {
408+
vscode.window.showErrorMessage(
409+
`Workspace folder '${wsFolder.name}' does not have scheme 'isfs' or 'isfs-readonly'.`,
410+
"Dismiss"
411+
);
412+
return;
413+
}
414414

415415
// Prompt the user to modify the uri
416416
const newUri = await modifyWsFolderUri(wsFolder.uri);

src/commands/webSocketTerminal.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { AtelierAPI } from "../api";
55
import { connectionTarget, currentFile, getWsServerConnection, handleError, notIsfs, outputChannel } from "../utils";
66
import { config, iscIcon, resolveConnectionSpec } from "../extension";
77

8+
const NO_ELIGIBLE_CONNECTIONS =
9+
"Lite Terminal requires an active server connection to InterSystems IRIS version 2023.2 or above.";
10+
811
const keys = {
912
enter: "\r",
1013
backspace: "\x7f",
@@ -766,6 +769,9 @@ export async function launchWebSocketTerminal(targetUri?: vscode.Uri): Promise<v
766769
} else {
767770
// Determine the server connection to use
768771
targetUri = currentFile()?.uri ?? (await getWsServerConnection("2023.2.0"));
772+
if (targetUri === undefined) {
773+
vscode.window.showErrorMessage(NO_ELIGIBLE_CONNECTIONS);
774+
}
769775
if (!targetUri) return;
770776
}
771777
const api = new AtelierAPI(targetUri);
@@ -791,10 +797,10 @@ export class WebSocketTerminalProfileProvider implements vscode.TerminalProfileP
791797
// Get the terminal configuration. Will throw if there's an error.
792798
const terminalOpts = terminalConfigForUri(new AtelierAPI(uri), uri, true);
793799
return new vscode.TerminalProfile(terminalOpts);
794-
} else if (uri == undefined) {
795-
throw new Error(
796-
"Lite Terminal requires an active server connection to InterSystems IRIS version 2023.2 or above."
797-
);
800+
} else if (uri === undefined) {
801+
throw new Error(NO_ELIGIBLE_CONNECTIONS);
802+
} else {
803+
throw new Error("No connection was chosen.");
798804
}
799805
}
800806
}

src/extension.ts

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,52 @@ const resolvedConnSpecs = new Map<string, any>();
215215
/**
216216
* If servermanager extension is available, fetch the connection spec unless already cached.
217217
* Prompt for credentials if necessary.
218-
* @param serverName authority element of an isfs uri, or `objectscript.conn.server` property
218+
* @param serverName authority element of an isfs uri, or `objectscript.conn.server` property, or the name of a root folder with an `objectscript.conn.docker-compose` property object
219+
* @param uri if passed, re-check the `objectscript.conn.docker-compose` case in case servermanager API couldn't do that because we're still running our own `activate` method.
219220
*/
220-
export async function resolveConnectionSpec(serverName: string): Promise<void> {
221-
if (serverManagerApi && serverManagerApi.getServerSpec) {
222-
if (serverName && serverName !== "" && !resolvedConnSpecs.has(serverName)) {
223-
const connSpec = await serverManagerApi.getServerSpec(serverName);
224-
if (connSpec) {
225-
await resolvePassword(connSpec);
226-
resolvedConnSpecs.set(serverName, connSpec);
221+
export async function resolveConnectionSpec(serverName: string, uri?: vscode.Uri): Promise<void> {
222+
if (!serverManagerApi || !serverManagerApi.getServerSpec || serverName === "") {
223+
return;
224+
}
225+
if (resolvedConnSpecs.has(serverName)) {
226+
// Already resolved
227+
return;
228+
}
229+
if (!vscode.workspace.getConfiguration("intersystems.servers", null).has(serverName)) {
230+
// When not a defined server see it already resolved as a foldername that matches case-insensitively
231+
if (getResolvedConnectionSpec(serverName, undefined)) {
232+
return;
233+
}
234+
}
235+
236+
let connSpec = await serverManagerApi.getServerSpec(serverName);
237+
238+
if (!connSpec && uri) {
239+
// Caller passed uri as a signal to process any docker-compose settings
240+
const { configName } = connectionTarget(uri);
241+
if (config("conn", configName)["docker-compose"]) {
242+
const serverForUri = await asyncServerForUri(uri);
243+
if (serverForUri) {
244+
connSpec = {
245+
name: serverForUri.serverName,
246+
webServer: {
247+
scheme: serverForUri.scheme,
248+
host: serverForUri.host,
249+
port: serverForUri.port,
250+
pathPrefix: serverForUri.pathPrefix,
251+
},
252+
username: serverForUri.username,
253+
password: serverForUri.password ? serverForUri.password : undefined,
254+
description: `Server for workspace folder '${serverName}'`,
255+
};
227256
}
228257
}
229258
}
259+
260+
if (connSpec) {
261+
await resolvePassword(connSpec);
262+
resolvedConnSpecs.set(serverName, connSpec);
263+
}
230264
}
231265

232266
async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promise<void> {
@@ -261,7 +295,22 @@ async function resolvePassword(serverSpec, ignoreUnauthenticated = false): Promi
261295

262296
/** Accessor for the cache of resolved connection specs */
263297
export function getResolvedConnectionSpec(key: string, dflt: any): any {
264-
return resolvedConnSpecs.has(key) ? resolvedConnSpecs.get(key) : dflt;
298+
let spec = resolvedConnSpecs.get(key);
299+
if (spec) {
300+
return spec;
301+
}
302+
303+
// Try a case-insensitive match
304+
key = resolvedConnSpecs.keys().find((oneKey) => oneKey.toLowerCase() === key.toLowerCase());
305+
if (key) {
306+
spec = resolvedConnSpecs.get(key);
307+
if (spec) {
308+
return spec;
309+
}
310+
}
311+
312+
// Return the default if not found
313+
return dflt;
265314
}
266315

267316
export async function checkConnection(
@@ -732,15 +781,20 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
732781
vscode.workspace.workspaceFolders?.map((workspaceFolder) => {
733782
const uri = workspaceFolder.uri;
734783
const { configName } = connectionTarget(uri);
735-
const serverName = notIsfs(uri) ? config("conn", configName).server : configName;
784+
const conn = config("conn", configName);
785+
786+
// When docker-compose object is defined don't fall back to server name, which may have come from user-level settings
787+
const serverName = notIsfs(uri) && !conn["docker-compose"] ? conn.server : configName;
736788
toCheck.set(serverName, uri);
737789
});
738790
for await (const oneToCheck of toCheck) {
739791
const serverName = oneToCheck[0];
740792
const uri = oneToCheck[1];
741793
try {
742794
try {
743-
await resolveConnectionSpec(serverName);
795+
// Pass the uri to resolveConnectionSpec so it will fall back to docker-compose logic if required.
796+
// Necessary because we are in our activate method, so its call to the Server Manager API cannot call back to our API to do that.
797+
await resolveConnectionSpec(serverName, uri);
744798
} finally {
745799
await checkConnection(true, uri, true);
746800
}
@@ -1518,46 +1572,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
15181572

15191573
// The API we export
15201574
const extensionApi = {
1521-
serverForUri(uri: vscode.Uri): any {
1522-
const { apiTarget } = connectionTarget(uri);
1523-
const api = new AtelierAPI(apiTarget);
1524-
1525-
// This function intentionally no longer exposes the password for a named server UNLESS it is already exposed as plaintext in settings.
1526-
// API client extensions should use Server Manager 3's authentication provider to request a missing password themselves,
1527-
// which will require explicit user consent to divulge the password to the requesting extension.
1528-
1529-
const {
1530-
serverName,
1531-
active,
1532-
host = "",
1533-
https,
1534-
port,
1535-
pathPrefix,
1536-
username,
1537-
password,
1538-
ns = "",
1539-
apiVersion,
1540-
serverVersion,
1541-
} = api.config;
1542-
return {
1543-
serverName,
1544-
active,
1545-
scheme: https ? "https" : "http",
1546-
host,
1547-
port,
1548-
pathPrefix,
1549-
username,
1550-
password:
1551-
serverName === ""
1552-
? password
1553-
: vscode.workspace
1554-
.getConfiguration(`intersystems.servers.${serverName.toLowerCase()}`, uri)
1555-
.get("password"),
1556-
namespace: ns,
1557-
apiVersion: active ? apiVersion : undefined,
1558-
serverVersion: active ? serverVersion : undefined,
1559-
};
1560-
},
1575+
serverForUri,
1576+
asyncServerForUri,
15611577
serverDocumentUriForUri(uri: vscode.Uri): vscode.Uri {
15621578
const { apiTarget } = connectionTarget(uri);
15631579
if (typeof apiTarget === "string") {
@@ -1589,6 +1605,66 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
15891605
return extensionApi;
15901606
}
15911607

1608+
// This function is exported as one of our API functions but is also used internally
1609+
// for example to implement the async variant capable of resolving docker port number.
1610+
function serverForUri(uri: vscode.Uri): any {
1611+
const { apiTarget } = connectionTarget(uri);
1612+
const api = new AtelierAPI(apiTarget);
1613+
1614+
// This function intentionally no longer exposes the password for a named server UNLESS it is already exposed as plaintext in settings.
1615+
// API client extensions should use Server Manager 3's authentication provider to request a missing password themselves,
1616+
// which will require explicit user consent to divulge the password to the requesting extension.
1617+
const {
1618+
serverName,
1619+
active,
1620+
host = "",
1621+
https,
1622+
port,
1623+
pathPrefix,
1624+
username,
1625+
password,
1626+
ns = "",
1627+
apiVersion,
1628+
serverVersion,
1629+
} = api.config;
1630+
return {
1631+
serverName,
1632+
active,
1633+
scheme: https ? "https" : "http",
1634+
host,
1635+
port,
1636+
pathPrefix,
1637+
username,
1638+
password:
1639+
serverName === ""
1640+
? password
1641+
: vscode.workspace.getConfiguration(`intersystems.servers.${serverName.toLowerCase()}`, uri).get("password"),
1642+
namespace: ns,
1643+
apiVersion: active ? apiVersion : undefined,
1644+
serverVersion: active ? serverVersion : undefined,
1645+
};
1646+
}
1647+
1648+
// An async variant capable of resolving docker port number.
1649+
// It is exported as one of our API functions but is also used internally.
1650+
async function asyncServerForUri(uri: vscode.Uri): Promise<any> {
1651+
const server = serverForUri(uri);
1652+
if (!server.port) {
1653+
let { apiTarget } = connectionTarget(uri);
1654+
if (apiTarget instanceof vscode.Uri) {
1655+
apiTarget = vscode.workspace.getWorkspaceFolder(apiTarget)?.name;
1656+
}
1657+
const { port: dockerPort, docker: withDocker } = await portFromDockerCompose(apiTarget);
1658+
if (withDocker && dockerPort) {
1659+
server.port = dockerPort;
1660+
server.host = "localhost";
1661+
server.pathPrefix = "";
1662+
server.https = false;
1663+
}
1664+
}
1665+
return server;
1666+
}
1667+
15921668
export function deactivate(): void {
15931669
if (workspaceState) {
15941670
workspaceState.update("openedClasses", openedClasses);

src/utils/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -488,21 +488,23 @@ async function composeCommand(cwd?: string): Promise<string> {
488488
});
489489
}
490490

491-
export async function portFromDockerCompose(): Promise<{ port: number; docker: boolean; service?: string }> {
491+
export async function portFromDockerCompose(
492+
workspaceFolderName?: string
493+
): Promise<{ port: number; docker: boolean; service?: string }> {
492494
// When running remotely, behave as if there is no docker-compose object within objectscript.conn
493495
if (extensionContext.extension.extensionKind === vscode.ExtensionKind.Workspace) {
494496
return { docker: false, port: null };
495497
}
496498

497499
// Seek a valid docker-compose object within objectscript.conn
498-
const { "docker-compose": dockerCompose = {} } = config("conn");
500+
const { "docker-compose": dockerCompose = {} } = config("conn", workspaceFolderName);
499501
const { service, file = "docker-compose.yml", internalPort = 52773, envFile } = dockerCompose;
500502
if (!internalPort || !file || !service || service === "") {
501503
return { docker: false, port: null };
502504
}
503505

504506
const result = { port: null, docker: true, service };
505-
const workspaceFolder = uriOfWorkspaceFolder();
507+
const workspaceFolder = uriOfWorkspaceFolder(workspaceFolderName);
506508
if (!workspaceFolder) {
507509
// No workspace folders are open
508510
return { docker: false, port: null };

0 commit comments

Comments
 (0)