VSCode extension with a tree view and custom context menu - visual-studio-code

I'm implementing a Visual Studio Code extension that provides a custom tree view, and in the tree view I'm showing custom commands in context menu using the following contributes setup:
"contributes": {
...
"menus": {
"view/item/context": [
{
"command": "myExtension.uploadFile",
"when": "view == myBucketExplorer"
}
]
}
...
}
Now, is there a way to only show this command for root nodes in the tree view? Is there perhaps a when clause that could help with that, or would I need to somehow disable the command programatically when the menu is actually invoked?

You can set contextValue for your TreeItem.
export class Something extends vscode.TreeItem {
// ...
constructor(
isRoot: boolean
) {
this.contextValue = isRoot ? 'YOUR_CONTEXT' : undefined;
}
}
async getChildren(element?: Something): Promise<Something[]> {
if (element) {
// NOT root
} else {
// ROOT -- Use different context for items
}
}
And then use
"when": "view == myBucketExplorer && viewItem == YOUR_CONTEXT"

Related

View not showing up when an extension command is typed/selected

I'm creating a vscode client extension that needs to show
Shows a webview on the Primary Sidebar (already works)
Opens a view when a certain extension command is selected (it fails)
The package.json looks like this
"activationEvents": [
"onView:navCode.search",
"onLanguage:typescript",
"onCommand:navCode.start"
],
"main": "./dist/extension",
"contributes": {
"commands": [
{
"command": "navCode.start",
"title": "Show class diagram",
"category": "NavCode Diagram"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "nav-code-search",
"title": "Nav Code",
"icon": "images/nav-code-logo.png"
}
]
},
"views": {
"nav-code-search": [
{
"type": "webview",
"id": "navCode.search",
"name": "Code search"
}
]
}
},
The activate method of my extension looks like these
export function activate(context: vscode.ExtensionContext) {
let refManager = new ReferenceManager();
refManager.updateWorkspaceReferences();
//Register the webview for code nav search
const provider = new SearchViewProvider(context.extensionUri, context, refManager);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SearchViewProvider.viewType, provider)
);
context.subscriptions.push(// Create and show a new webview
vscode.commands.registerCommand('navCode.start', () => {
const panel = vscode.window.createWebviewPanel(
'navCode', // Identifies the type of the webview. Used internally
'NavCode - Class Diagram', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{} // Webview options. More on these later.
);
})
);
}
The command appears correctly
But when is selected it shows this error
There is nothing else on Console or Output views, what could be causing this ?
I have searched without any success about how to register multiple views of the same extension
UPDATE
But back to the problem I believe that is realted to this method
refManager.updateWorkspaceReferences();
Because it awaits until a typescript parses all the *.ts files of the current workspace
public async updateWorkspaceReferences() {
let message: string;
let folders = vscode.workspace.workspaceFolders;
if (folders && folders.length > 0) {
message = await this.processProject(folders[0]);
}
else {
message = 'nav-code requires an open workspace to work';
}
this.log.append(message);
}
How can a have a running task, right after the extension is activated ? can I use threads ?
Here is an overview of what I ended up doing to solve the problem
Put the long-running logic inside the SearchViewProvider (not running directly inside the activate method but as a promise that notifies when done
SearchViewProvider gets notified by ReferenceManager that the long-running logic has finished using BehaviorSubject (rxjs) : referencesUpdated
The view is initialized with a default content when notified the content is refreshed.
Here is the relevant code
extension.ts
export function activate(context: vscode.ExtensionContext) {
//Register the webview for code nav search
const provider = new SearchViewProvider(context, logger);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SearchViewProvider.viewType, provider)
);
context.subscriptions.push(// Create and show a new webview
vscode.commands.registerCommand('navCode.start', () => {
const panel = vscode.window.createWebviewPanel(
'navCode', // Identifies the type of the webview. Used internally
'NavCode - Class Diagram', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{} // Webview options. More on these later.
);
})
);
}
SearchViewProvider.ts
constructor(
private context: vscode.ExtensionContext,
private logger: Logger
) {
this.extensionUri = this.context.extensionUri;
this.referenceManager = new ReferenceManager(logger);
this.referenceManager.referencesUpdated.subscribe(() => { this.refreshView() });
}
public resolveWebviewView(webview: vscode.WebviewView, context: vscode.WebviewViewResolveContext<unknown>, token: vscode.CancellationToken): void | Thenable<void> {
this.view = webview;
if (this.view) {
/** existing view logic **/
this.refreshView();
}
}
refreshView() {
if (this.view) {
this.view.webview.html = this.buildView(this.view.webview);
}
}

How do you show a context menu in a VS Code Extension

I'm writing a custom editor (graphical editor) as a Visual Studio Code Extension, and want to show a context menu in a WebView when a given element is right clicked.
Any ideas?
You can check VSCode 1.70 (July 2022) which includes issue 54285 ("Contributed webview context menu action")
It is resolved by PR 154524:
Adds webview/context contribution
(Available today in VSCode insiders)
With these changes I was able to get modified version of GitLens to add custom menu items to a specific webview (e.g. the welcome view) with the following contribution:
"webview/context": [
{
"command": "gitlens.showSettingsPage",
"when": "webview == gitlens.welcome"
}
],
Which looks like:
When executed, the command callback gets an arg which looks like { webview: 'gitlens.welcome; } (where gitlens.welcome is the viewType/providedWebviewId of that webview)
With this contribution, extensions could then use their own context keys to hide and show the desired commands.
So, this PR adds:
I've added a new preventDefaultContextMenuItems property on WebviewPanelOptions and on the webviewOptions object we pass to registerWebviewViewProvider which allows a webview to opt-out of the default cut/copy/paste (or any future) menu items.
I've also provided 3 context keys for the context menu when clause:
webview — the extension provided id of the webview
for webview panels and custom editors it is the viewType
for webview views it is the view id
webviewItem — an optional value that an extension can provide in the DOM via a data-vscode-context-menu-item attribute, by searching for closest element to the element that triggered the context menu
webviewItemElement — an optional value that an extension can provide in the DOM via a data-vscode-context-menu-item-element attribute, by searching for closest element to the element that triggered the context menu (but only within the scope of the found item element
Here's an example using the GitLens Rebase Editor:
"webview/context": [
{
"command": "gitlens.rebase.help",
"when": "webview == gitlens.rebase",
"group": "9_gitlens#1"
},
{
"command": "gitlens.rebase.copySha",
"when": "webview == gitlens.rebase && webviewItem == commit && webviewItemElement == sha",
"group": "1_gitlens#1"
},
{
"command": "gitlens.rebase.copyMessage",
"when": "webview == gitlens.rebase && webviewItem == commit && webviewItemElement == message",
"group": "1_gitlens#1"
},
{
"command": "gitlens.rebase.showCommitDetails",
"when": "webview == gitlens.rebase && webviewItem == commit",
"group": "2_gitlens#1"
}
],
Here's the DOM -- I have:
data-vscode-context-menu-item="commit" on each of the commit rows, and
additional data-vscode-context-menu-item-element attrs on the message and
sha the with "message" and "sha" values respectively
Here's the context menus in action:

Create duplicate tab of an already open file [duplicate]

We can use the "split editor" option to make two views into one file.
I'm looking for an option to open the same file in separated tabs like I can do in Sublime Text (open new view of file). Is that possible?
Note: I want to do this without splitting the view, so there should be two tabs for the same file within the same view container.
I couldn't find anything built-in that lets you do this, nor an existing extension in the marketplace. I thought it should be quite trivial to implement a "Duplicate Tab" command yourself in a custom extension, but it turns out VSCode only allows the same resource to be opened once within the same view column.
It's still possible to do this on Windows or macOS, but only by abusing this bug:
Issues with not case/fragment-normalizing file paths (macOS, Windows) #12448
Here's what the code for the extension looks like:
'use strict';
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("duplicateTab", () => {
var activeEditor = vscode.window.activeTextEditor;
if (activeEditor == null) {
return;
}
// HACK!
const sameFileNameButDifferent = activeEditor.document.fileName.toUpperCase();
vscode.workspace.openTextDocument(sameFileNameButDifferent).then(document => {
vscode.window.showTextDocument(document, {preview: false});
});
});
}
In package.json:
"contributes": {
"commands": [
{
"title": "Duplicate Tab",
"command": "duplicateTab"
}
]
},

How do I contribute a command to the VS Code explorer title bar or command palette that is only active when my webview is focused?

I'm writing an extension that uses VS Code's webview api. My extension also registers a refresh preview command that updates the webview's content. How can I make it so that:
The refresh preview command only shows up in the command palette when my webview is focused?
The refresh preview command shows in the webview's editor title bar?
First, create a custom context key that tracks when your webview is focused. Use VS Code's setContext command to make this context key track when your webview is focused:
const myWebview = ...;
// Make the context key track when one of your webviews is focused
myWebview.onDidChangeViewState(({ webviewPanel }) => {
vscode.commands.executeCommand('setContext',
'myWebviewFocused',
webviewPanel.active);
});
Then use your context key in the when clauses of your extension's menus contribution point
For a command with the id myExtension.refreshPreview, to ensure that command only shows up in the command palette when your webview is focused, use the following contribution:
{
"contributes": {
"menus": {
"commandPalette": [
{
"command": "myExtension.refreshPreview",
"when": "myWebviewFocused",
}
]
}
}
}
For adding a command (possible with icon) to the editor title bar of your webview, use the editor/title contribution point:
{
"contributes": {
"menus": {
"editor/title": [
{
"command": "myExtension.refreshPreview",
"when": "myWebviewFocused",
}
]
}
}
}
Check out VS Code's built-in markdown extension for a more advanced example of this.

Within a VSCode extension is it possible to have a panel that switches between a webview and tree view

i want to add a new explorer panel into vscode. I want it to display either a treeView or a webView depending on if the user has connected to my backend application. I can see something similar in the base of vscode in the folder view. When no folder is open this view is shown
and when you have a folder open it looks like
For anyone else who finds this question, the behaviour seen in the file explorer is achievable through a Welcome Message.
A view's welcome message will show when the tree for that view is empty.
Preview
Welcome Message
Normal tree view
Example
In your package.json, declare:
The view
The view welcome message
The command which the welcome message button should execute
"contributes": {
"commands": [
{
"command": "myExtension.myCommand",
"title": "My Custom Command"
}
],
"views": {
"explorer": [
{
"id": "myCustomView",
"name": "My Custom View",
"contextualTitle": "My Custom View"
}
]
},
"viewsWelcome": [
{
"view": "myCustomView",
"contents": "Welcome to my custom view! [learn more](https://google.com/).\n[Get Started](command:myExtension.myCommand)"
}
]
}
In your extension.ts
Define the button command
Hook up the view to the view provider
import * as vscode from 'vscode';
import { CustomViewProvider } from './CustomViewProvider';
export function activate(context: vscode.ExtensionContext) {
// Add the custom view
const customViewProvider = new CustomViewProvider();
vscode.window.registerTreeDataProvider('myCustomView', customViewProvider);
// Add the command
let myCustomCommand = vscode.commands.registerCommand('myExtension.myCommand', () => {
vscode.window.showInformationMessage('This is my custom command!');
});
context.subscriptions.push(myCustomCommand);
}
export function deactivate() { }
In CustomViewProvider.ts, define when your view is empty or not.
import * as vscode from 'vscode';
export class CustomViewProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
getChildren(element?: vscode.TreeItem): Thenable<vscode.TreeItem[]> {
// NOTE:
// When TRUE, the welcome message will show
// When FALSE, the welcome message will NOT show
var showEmptyView = true;
if (showEmptyView) {
return Promise.resolve([]);
}
return Promise.resolve([
new vscode.TreeItem('This view is not empty!')
]);
}
}
As of VS Code 1.25, views may only contain tree views. Support for showing a webview in the side bar is tracked by https://github.com/Microsoft/vscode/issues/46585
If all you need is a button or simple prompt, you can use a tree view with a single node in the first case