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

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

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);
}
}

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"
}
]
},

VSCode extension with a tree view and custom context menu

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"

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.

How to Attach Page or Navigation Pane in TabbedPane defined in different qml files?

I have defined a TabbedPane as below but one each tab, I would like to show content"questions.qml" (It's a Navigation Pane") and "stats.qml" file instead of embedding the code in single file. So I was wondering how I can achieve that?
TabbedPane {
showTabsOnActionBar: true
Tab {
id: questions
title: "Questions"
description: "This tab will have questions in current hub"
}
Tab {
id: stats
title: "Stats"
}
}
What I have done in that case is declare each tab in the QML file the sets up the TabbedPane, as you have:
import "UI" // The file DataManagement.qml is located in the directory UI
// which is a sub-directory of the location of this QML file.
...
Tab {
title: qsTr("Data Management")
imageSource: "asset:///images/icons/database.png"
id: dataManagement
DataManagement {
id: dataManagementPage
}
}
...
Then in a separate QML file, DataManagement.qml in this case, I declare the content of the tab:
import bb.cascades 1.0
Page {
// content of page to render in the tab.
content: Container {
...
}
}
As long as the QML files are in the same directory, or the referenced file (DataManagement.qml) is in a directory included in the first QML file it works.