Skip to content
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

feat: open Zowe Explorer resources using VSCode URLs #3271

Merged
merged 13 commits into from
Oct 30, 2024
20 changes: 15 additions & 5 deletions packages/zowe-explorer-api/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,24 @@ export interface TreeDataProvider<T> {
}

export class Uri {
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;

public static file(path: string): Uri {
return Uri.parse(path);
}
public static parse(value: string, _strict?: boolean): Uri {
const newUri = new Uri();
newUri.path = value;
const match = Uri._regexp.exec(value);
if (!match) {
return new Uri();
}

return newUri;
return Uri.from({
scheme: match[2] || "",
authority: match[4] || "",
path: match[5] || "",
query: match[7] || "",
fragment: match[9] || "",
});
}

public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
Expand Down Expand Up @@ -688,7 +698,7 @@ export class Uri {
/**
* Path is the `/some/path` part of `http://www.example.com/some/path?query#fragment`.
*/
path: string;
path: string = "";

/**
* Query is the `query` part of `http://www.example.com/some/path?query#fragment`.
Expand Down Expand Up @@ -720,7 +730,7 @@ export class Uri {
* u.fsPath === '\\server\c$\folder\file.txt'
* ```
*/
fsPath: string;
fsPath: string = "";

public toString(): string {
let result = this.scheme ? `${this.scheme}://` : "";
Expand Down
2 changes: 2 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen

- Update Zowe SDKs to `8.2.0` to get the latest enhancements from Imperative.
- Added expired JSON web token detection for profiles in each tree view (Data Sets, USS, Jobs). When a user performs a search on a profile, they are prompted to log in if their token expired. [#3175](https://github.com/zowe/zowe-explorer-vscode/issues/3175)
- Added the ability to open VS Code URLs that point to Zowe Resources. [#3271](https://github.com/zowe/zowe-explorer-vscode/pull/3271)
- Example: `vscode://Zowe.vscode-extension-for-zowe?zowe-ds:/lpar.zosmf/TEST.PDS/MEMB` will open the `MEMB` PDS member under the `TEST.PDS` using the `lpar.zosmf` profile.

### Bug fixes

Expand Down
59 changes: 54 additions & 5 deletions packages/zowe-explorer/__tests__/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,47 @@ export interface WebviewViewProvider {
resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Thenable<void> | void;
}

/**
* A uri handler is responsible for handling system-wide {@link Uri uris}.
*
* @see {@link window.registerUriHandler}.
*/
export interface UriHandler {
/**
* Handle the provided system-wide {@link Uri}.
*
* @see {@link window.registerUriHandler}.
*/
handleUri(uri: Uri): ProviderResult<void>;
}

export namespace window {
/**
* Registers a {@link UriHandler uri handler} capable of handling system-wide {@link Uri uris}.
* In case there are multiple windows open, the topmost window will handle the uri.
* A uri handler is scoped to the extension it is contributed from; it will only
* be able to handle uris which are directed to the extension itself. A uri must respect
* the following rules:
*
* - The uri-scheme must be `vscode.env.uriScheme`;
* - The uri-authority must be the extension id (e.g. `my.extension`);
* - The uri-path, -query and -fragment parts are arbitrary.
*
* For example, if the `my.extension` extension registers a uri handler, it will only
* be allowed to handle uris with the prefix `product-name://my.extension`.
*
* An extension can only register a single uri handler in its entire activation lifetime.
*
* * *Note:* There is an activation event `onUri` that fires when a uri directed for
* the current extension is about to be handled.
*
* @param handler The uri handler to register for this extension.
* @returns A {@link Disposable disposable} that unregisters the handler.
*/
export function registerUriHandler(handler: UriHandler): Disposable {
return () => {};
}

/**
* Register a new provider for webview views.
*
Expand Down Expand Up @@ -1512,14 +1552,23 @@ export interface TextDocument {
}

export class Uri {
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
public static file(path: string): Uri {
return Uri.parse(path);
}
public static parse(value: string, strict?: boolean): Uri {
const newUri = new Uri();
newUri.path = value;
public static parse(value: string, _strict?: boolean): Uri {
const match = Uri._regexp.exec(value);
if (!match) {
return new Uri();
}

return newUri;
return Uri.from({
scheme: match[2] || "",
authority: match[4] || "",
path: match[5] || "",
query: match[7] || "",
fragment: match[9] || "",
});
}

public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
Expand Down Expand Up @@ -1589,7 +1638,7 @@ export class Uri {
/**
* Path is the `/some/path` part of `http://www.example.com/some/path?query#fragment`.
*/
path: string;
path: string = "";

/**
* Query is the `query` part of `http://www.example.com/some/path?query#fragment`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { commands, Uri } from "vscode";
import { ZoweUriHandler } from "../../../src/utils/UriHandler";

describe("ZoweUriHandler", () => {
function getBlockMocks() {
return {
executeCommand: jest.spyOn(commands, "executeCommand"),
};
}

it("does nothing if the parsed query does not start with a Zowe scheme", async () => {
const blockMocks = getBlockMocks();
await ZoweUriHandler.getInstance().handleUri(Uri.parse("vscode://Zowe.vscode-extension-for-zowe?blah-some-unknown-query"));
expect(blockMocks.executeCommand).not.toHaveBeenCalled();
});

it("calls vscode.open with the parsed URI if a Zowe resource URI was provided", async () => {
const blockMocks = getBlockMocks();
const uri = Uri.parse("vscode://Zowe.vscode-extension-for-zowe?zowe-ds:/lpar.zosmf/TEST.PS");
await ZoweUriHandler.getInstance().handleUri(uri);
const zoweUri = Uri.parse(uri.query);
expect(blockMocks.executeCommand).toHaveBeenCalledWith("vscode.open", zoweUri, { preview: false });
});
});
2 changes: 2 additions & 0 deletions packages/zowe-explorer/src/trees/shared/SharedInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { SharedContext } from "./SharedContext";
import { TreeViewUtils } from "../../utils/TreeViewUtils";
import { CertificateWizard } from "../../utils/CertificateWizard";
import { ZosConsoleViewProvider } from "../../zosconsole/ZosConsolePanel";
import { ZoweUriHandler } from "../../utils/UriHandler";

export class SharedInit {
private static originalEmitZoweEvent: typeof imperative.EventProcessor.prototype.emitEvent;
Expand Down Expand Up @@ -276,6 +277,7 @@ export class SharedInit {
return LocalFileManagement.fileSelectedToCompare;
})
);
context.subscriptions.push(vscode.window.registerUriHandler(ZoweUriHandler.getInstance()));
context.subscriptions.push(
vscode.commands.registerCommand("zowe.placeholderCommand", () => {
// This command does nothing, its here to let us disable individual items in the tree view
Expand Down
23 changes: 23 additions & 0 deletions packages/zowe-explorer/src/utils/UriHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { commands, ProviderResult, Uri, UriHandler } from "vscode";
import { ZoweScheme } from "../../../zowe-explorer-api/src";

export class ZoweUriHandler implements UriHandler {
private static instance: ZoweUriHandler = null;
private constructor() {}

public static getInstance(): ZoweUriHandler {
if (ZoweUriHandler.instance == null) {
ZoweUriHandler.instance = new ZoweUriHandler();
}

return ZoweUriHandler.instance;
}

public handleUri(uri: Uri): ProviderResult<void> {
const parsedUri = Uri.parse(uri.query);
if (!Object.values(ZoweScheme).some((scheme) => scheme === parsedUri.scheme)) {
return;
}
return commands.executeCommand("vscode.open", parsedUri, { preview: false });
}
}
Loading