View not showing up when an extension command is typed/selected - visual-studio-code

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

Related

VSCode extension development unable to use provideInlineCompletionItems

I've tried so many variations of this from code I found on github using the provideInlineCompletionItems function but cannot seem to get it to work. Is there something I am doing wrong?
const vscode = require('vscode');
function activate(context) {
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const txt = 'hi'
return [
{
text: txt,
insertText: txt,
range:new vscode.Range(position.translate(0, txt.length), position)
}
]
},
};
vscode.languages.registerInlineCompletionItemProvider({pattern: "**"}, provider);
}
exports.activate = activate;
function deactivate() {}
module.exports = {
activate,
deactivate
};
Even with https://github.com/microsoft/vscode/issues/125663 and "editor.inlineSuggest.enabled": true, set to true it doesn't work. I know inline suggestions works since I have github copilot, I just can't seem to get it to work. Copilot is also disabled so they don't interfere
I rollbacked a version of VSCode and it works now. this is the debug code I used.
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
/**
* #param {vscode.ExtensionContext} context
*/
function activate(context) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "seven" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('seven.helloWorld', function () {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from seven!');
});
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const line = document.lineAt(position.line);
console.log("420")
if (line.text.startsWith('if')) {
console.log("421")
let range = line.range;
if (line.text.indexOf(";") !== -1) {
console.log("423")
range = new vscode.Range(range.start, range.end.with(undefined, line.text.indexOf(";") + 1));
}
console.log("424")
return [{ text: 'if (hello) {\n};', insertText: "if (hello)", range }];
}
}
};
vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider);
context.subscriptions.push(disposable);
}
// This method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate
}

AEM Workflow custom input data

I need to create a workflow in AEM that for a page (specified as payload) finds all the assets used on the page and uploads a list of them to an external service. So far I have most of the code ready, but business process requires me to use a special code for each of the pages (different for each run of the workflow), so that the list is uploaded to correct place.
That is when I have a question - Can you somehow add more input values for an AEM workflow? Maybe by extending the starting dialog, or adding some special step that takes user input? I need to be able to somehow specify the code when launching the workflow or during its runtime.
I have read a lot of documentation but as this is my first time using workflows, I might be missing something really obvious. I will be grateful for any piece of advice, including a link to a relevant piece of docs.
Yes, that is possible. You need to implement a dialog step in your workflow: https://docs.adobe.com/content/help/en/experience-manager-64/developing/extending-aem/extending-workflows/workflows-step-ref.html#dialog-participant-step
You could:
Create a custom menu entry somewhere in AEM (e.g. Page Editor, /apps/wcm/core/content/editor/_jcr_content/content/items/content/header/items/headerbar/items/pageinfopopover/items/list/items/<my-action>, see under libs for examples)
Create a client-library with the categories="[cq.authoring.editor]". So it is loaded as part of the page editor (and not inside the iframe with your page)
Create a JS-Listener, that opens a dialog if the menu-entry was clicked (see code). You can either use plain Coral UI dialogs, or my example misused a Granite page dialog (Granite reads the data-structure in cq:dialog, and creates a Coral UI component edit-dialog out of it - while Coral is the plain JS UI-framework)
Create a Java-Servlet, that catches your request, and creates the workflow. You could theoretically use the AEM servlet. But I often have to write my own, because it lacks some features.
Here is the JS Listener:
/*global Granite,jQuery,document,window */
(function ($, ns, channel, window) {
"use strict";
var START_WORKFLOW_ACTIVATOR_SELECTOR = ".js-editor-myexample-activator";
function onSuccess() {
ns.ui.helpers.notify({
heading: "Example Workflow",
content: "successfully started",
type: ns.ui.helpers.NOTIFICATION_TYPES.SUCCESS
});
}
function onSubmitFail(event, jqXHR) {
var errorMsg = Granite.I18n.getVar($(jqXHR.responseText).find("#Message").html());
ns.ui.helpers.notify({
heading: "Example Workflow",
content: errorMsg,
type: ns.ui.helpers.NOTIFICATION_TYPES.ERROR
});
}
function onReady() {
// add selector for special servlet to form action-url
var $form = ns.DialogFrame.currentFloatingDialog.find("form");
var action = $form.attr("action");
if (action) {
$form.attr("action", action + ".myexample-selector.html");
}
// register dialog-fail event, to show a relevant error message
$(document).on("dialog-fail", onSubmitFail);
// init your dialog here ...
}
function onClose() {
$(document).off("dialog-fail", onSubmitFail);
}
// Listen for the tap on the 'myexample' activator
channel.on("click", START_WORKFLOW_ACTIVATOR_SELECTOR, function () {
var activator = $(this);
// this is a dirty trick, to use a Granite dialog directly (point to data-structure like in cq:dialog)
var dialogUrl = Granite.HTTP.externalize("/apps/...." + Granite.author.ContentFrame.getContentPath());
var dlg = new ns.ui.Dialog({
getConfig: function () {
return {
src: dialogUrl,
loadingMode: "auto",
layout: "auto"
}
},
getRequestData: function () {
return {};
},
"onSuccess": onSuccess,
"onReady": onReady,
"onClose": onClose
});
ns.DialogFrame.openDialog(dlg);
});
}(jQuery, Granite.author, jQuery(document), window));
And here is the servlet
#Component(service = Servlet.class,
property = {
SLING_SERVLET_RESOURCE_TYPES + "=cq:Page",
SLING_SERVLET_SELECTORS + "=myexample-selector",
SLING_SERVLET_METHODS + "=POST",
SLING_SERVLET_EXTENSIONS + "=html"
})
public class RequestExampleWorkflowServlet extends SlingAllMethodsServlet {
#Override
protected void doPost(#Nonnull SlingHttpServletRequest request, #Nonnull SlingHttpServletResponse response) throws IOException {
final Page page = request.getResource().adaptTo(Page.class);
if (page != null) {
Map<String, Object> wfMetaData = new HashMap<>();
wfMetaData.put("workflowTitle", "Request Translation for " + page.getTitle());
wfMetaData.put("something", "Hello World");
try {
WorkflowSession wfSession = request.getResourceResolver().adaptTo(WorkflowSession.class);
if (wfSession != null) {
WorkflowModel wfModel = wfSession.getModel("/var/workflow/models/example-workflow");
WorkflowData wfData = wfSession.newWorkflowData(PayloadInfo.PAYLOAD_TYPE.JCR_PATH.name(), page.getPath());
wfSession.startWorkflow(wfModel, wfData, wfMetaData);
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_OK, "Triggered Example Workflow");
} else {
throw new WorkflowException("Cannot retrieve WorkflowSession");
}
} catch (WorkflowException e) {
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
} else {
MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal error - cannot get page");
}
}
}

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"

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

VideoJS 5 plugin add button

I looked everywhere on the internet but I couldn't find any clear documentation or some examples to create my verySimplePlugin for videoJS 5 (Since it uses ES6).
I just want to add a button next to the big play button... Can someone help me?
Thanks...
PS: I'm using it in angularJS but I guess this can not a problem
This is how you can add download button to the end of control bar without any plugins or other complicated code:
var vjsButtonComponent = videojs.getComponent('Button');
videojs.registerComponent('DownloadButton', videojs.extend(vjsButtonComponent, {
constructor: function () {
vjsButtonComponent.apply(this, arguments);
},
handleClick: function () {
document.location = '/path/to/your/video.mp4'; //< there are many variants here so it is up to you how to get video url
},
buildCSSClass: function () {
return 'vjs-control vjs-download-button';
},
createControlTextEl: function (button) {
return $(button).html($('<span class="glyphicon glyphicon-download-alt"></span>').attr('title', 'Download'));
}
}));
videojs(
'player-id',
{fluid: true},
function () {
this.getChild('controlBar').addChild('DownloadButton', {});
}
);
I used 'glyphicon glyphicon-download-alt' icon and a title for it so it fits to the player control bar styling.
How it works:
We registering a new component called 'DownloadButton' that extends built-in 'Button' component of video.js lib
In constructor we're calling constructor of the 'Button' component (it is quite complicated for me to understand it 100% but it is similar as calling parent::__construct() in php)
buildCSSClass - set button classes ('vjs-control' is must have!)
createControlTextEl - adds content to the button (in this case - an icon and title for it)
handleClick - does something when user presses this button
After player was initialized we're adding 'DownloadButton' to 'controlBar'
Note: there also should be a way to place your button anywhere within 'controlBar' but I haven't figured out how because download button is ok in the end of the control bar
This is how I created a simple button plugin for videojs 5:
(function() {
var vsComponent = videojs.getComponent('Button');
// Create the button
videojs.SampleButton = videojs.extend(vsComponent, {
constructor: function() {
vsComponent.call(this, videojs, null);
}
});
// Set the text for the button
videojs.SampleButton.prototype.buttonText = 'Mute Icon';
// These are the defaults for this class.
videojs.SampleButton.prototype.options_ = {};
// videojs.Button uses this function to build the class name.
videojs.SampleButton.prototype.buildCSSClass = function() {
// Add our className to the returned className
return 'vjs-mute-button ' + vsComponent.prototype.buildCSSClass.call(this);
};
// videojs.Button already sets up the onclick event handler, we just need to overwrite the function
videojs.SampleButton.prototype.handleClick = function( e ) {
// Add specific click actions here.
console.log('clicked');
};
videojs.SampleButton.prototype.createEl = function(type, properties, attributes) {
return videojs.createEl('button', {}, {class: 'vjs-mute-btn'});
};
var pluginFn = function(options) {
var SampleButton = new videojs.SampleButton(this, options);
this.addChild(SampleButton);
return SampleButton;
};
videojs.plugin('sampleButton', pluginFn);
})();
You can use it this way:
var properties = { "plugins": { "muteBtn": {} } }
var player = videojs('really-cool-video', properties , function() { //do something cool here });
Or this way:
player.sampleButton()