I am building a VS Code extension that can create multiple webviews; each text editor document can have an associated webview. I want the user to be able to right-click in a webview and execute a command. The command must know which webview instance was clicked within—or active/focused when the command was issued from the command palette—to Do The Right Thing.
The context menu callback only receives {webview:<viewType>}
as the argument.
How can I determine which webview the context menu originated in?
In case it helps, following is simplified extension code showing how I'm creating a new webview (and associated viewer manager) for each document.uri
:
import * as vscode from 'vscode';
import {commands, TextEditor, window} from 'vscode';
import {Viewer} from './viewer';
export function activate(ctx: vscode.ExtensionContext) {
let manager = new MyExtManager();
const subs = ctx.subscriptions;
subs.push(commands.registerCommand('myExt.show', () => {
manager.showWebview(ctx.extensionUri);
}));
subs.push(commands.registerCommand('myExt.myCtxItem', (wv) => {
// need some unique identifier here to locate original editor
console.log(wv);
}));
}
class MyExtManager {
private viewerByDoc: Map<vscode.Uri, Viewer> = new Map();
public showWebview(extensionURI: vscode.Uri) {
const editor: TextEditor | undefined = window.activeTextEditor;
const doc = editor?.document;
if (doc && doc.languageId === "xml") {
let viewerForDoc = this.viewerByDoc.get(doc.uri);
if (viewerForDoc) {
viewerForDoc.reveal();
} else {
const panel = vscode.window.createWebviewPanel(
'myxmlviewer', `My Viewer for ${doc.uri}`,
vscode.ViewColumn.Beside
);
panel.onDidDispose(() => this.viewerByDoc.delete(doc.uri));
viewerForDoc = new Viewer(editor, panel)
this.viewerByDoc.set(doc.uri, viewerForDoc);
}
}
}
}
Things I've investigated:
vscode.window.active*
because there is no collection for seeing active webviews like there is for activeTextEditor
.The only hack I can think of is to hijack the viewType
parameter to be unique for each viewer instance. I can't find much about the intention of this parameter, but having unique values seems like an unintended use.
The context menu is constructed via these additions in the package.json
file:
{
"contributes": {
"commands": [
{
"category": "XML Viewer",
"command": "myExt.show",
"title": "Open to the Side"
},
{
"category": "XML Viewer",
"command": "myExt.myCtxItem",
"title": "Do a Thing Here"
},
],
"menus": {
"webview/context": [
{
"command": "myExt.myCtxItem",
"group": "xmlViewer"
}
]
}
}
}
Figured out a way:
When the WebviewPanel is created, register a onDidChangeViewState
callback. Check if the panel is active and visible, and if so override a single variable with it.
public activeWebview: WebviewPanel | null = null;
// ...
panel.onDidChangeViewState(e => {
if (panel.active && panel.visible) {
this.activeWebview = panel;
} else if (activeWebview===panel) {
this.activeWebview = null;
}
});
...and then use that in the extension's command registration:
subs.push(commands.registerCommand('myExt.myCtxItem', () => {
if (manager.activeWebview) {
// ...
}
}));
I'm actually doing far more work than the above so that the command is still available if the TextEditor
associated with a webview is available…but that's outside the scope of this question.