diff --git a/demos/hints/demohints.didact.md b/demos/hints/demohints.didact.md new file mode 100644 index 00000000..3a269997 --- /dev/null +++ b/demos/hints/demohints.didact.md @@ -0,0 +1,4 @@ +### Handy Commands + +* [Open a terminal](didact://?commandId=vscode.didact.startTerminalWithName&text=Terminal-Name) +* [Create an untitled file](didact://?commandId=workbench.action.files.newUntitledFile) diff --git a/package.json b/package.json index ef2178a9..a6f12005 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ ], "activationEvents": [ "*", - "onWebviewPanel:didact" + "onView:didact.hintBoxView" ], "main": "./out/extension.js", "contributes": { @@ -73,6 +73,11 @@ { "id": "didact.tutorials", "name": "Didact Tutorials" + }, + { + "type": "webview", + "id": "didact.hintBoxView", + "name": "Hintbox" } ] }, @@ -105,6 +110,14 @@ "light": "resources/light/refresh.svg" } }, + { + "command": "didact.hintbox.refresh", + "title": "Refresh Didact Hintbox", + "icon": { + "dark": "resources/dark/refresh.svg", + "light": "resources/light/refresh.svg" + } + }, { "command": "vscode.didact.verifyCommands", "title": "Validate Didact File", diff --git a/src/extension.ts b/src/extension.ts index 3a89c08a..c5fbf7b8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { registerTutorialWithCategory, clearRegisteredTutorials, getOpenAtStartu import { DidactUriCompletionItemProvider } from './didactUriCompletionItemProvider'; import { DidactPanelSerializer } from './didactPanelSerializer'; import { VIEW_TYPE } from './didactManager'; +import { HintBoxViewProvider } from './hintBoxViewProvider'; const DIDACT_VIEW = 'didact.tutorials'; @@ -30,6 +31,8 @@ const DEFAULT_TUTORIAL_NAME = "Didact Demo"; export const didactTutorialsProvider = new DidactNodeProvider(); let didactTreeView : vscode.TreeView; +let hintBoxProvider : HintBoxViewProvider; +const REFRESH_HINTBOX_ACTION = 'didact.hintbox.refresh'; export async function activate(context: vscode.ExtensionContext): Promise { @@ -63,6 +66,7 @@ export async function activate(context: vscode.ExtensionContext): Promise context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.PASTE_TO_EDITOR_FOR_FILE_COMMAND, extensionFunctions.pasteClipboardToEditorForFile)); context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.PASTE_TO_NEW_FILE_COMMAND, extensionFunctions.pasteClipboardToNewTextFile)); context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.REFRESH_DIDACT, extensionFunctions.refreshDidactWindow)); + context.subscriptions.push(vscode.commands.registerCommand(REFRESH_HINTBOX_ACTION, refreshHintBox)); // set up the vscode URI handler vscode.window.registerUriHandler({ @@ -108,6 +112,10 @@ export async function activate(context: vscode.ExtensionContext): Promise // create the view createIntegrationsView(); + // create the hintbox + hintBoxProvider = new HintBoxViewProvider(context.extensionUri); + context.subscriptions.push(vscode.window.registerWebviewViewProvider(HintBoxViewProvider.viewType, hintBoxProvider)); + // open at startup if setting is true const openAtStartup : boolean = getOpenAtStartupSetting(); if (openAtStartup) { @@ -136,3 +144,9 @@ export function refreshTreeview(): void { didactTutorialsProvider.refresh(); } } + +export function refreshHintBox() : void { + if (hintBoxProvider) { + hintBoxProvider.show(true); + } +} diff --git a/src/extensionFunctions.ts b/src/extensionFunctions.ts index e3e465f9..8dce1f90 100644 --- a/src/extensionFunctions.ts +++ b/src/extensionFunctions.ts @@ -970,3 +970,7 @@ export async function pasteClipboardToNewTextFile() : Promise { await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); await pasteClipboardToActiveEditorOrPreviouslyUsedOne(); } + +export async function setDidactFileUri(newFileUri : vscode.Uri | undefined) { + _didactFileUri = newFileUri; +} diff --git a/src/hintBoxViewProvider.ts b/src/hintBoxViewProvider.ts new file mode 100644 index 00000000..6aba6d89 --- /dev/null +++ b/src/hintBoxViewProvider.ts @@ -0,0 +1,279 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as extensionFunctions from './extensionFunctions'; +import * as path from 'path'; +import { Disposable, Uri, workspace, window, extensions, WebviewViewProvider, + WebviewView, WebviewViewResolveContext, CancellationToken, commands } from 'vscode'; +import { DIDACT_DEFAULT_URL } from './utils'; +import { didactManager } from './didactManager'; +import * as commandHandler from './commandHandler'; + +export class HintBoxViewProvider implements WebviewViewProvider { + + public static readonly viewType = 'didact.hintBoxView'; + private _view?: WebviewView; + private _disposables: Disposable[] = []; + private currentHtml : string | undefined = undefined; + private didactUriPath : Uri | undefined; + private isAsciiDoc = false; + private _disposed = false; + public visible = false; + + constructor(private readonly _extensionUri: Uri) {} + + public async resolveWebviewView( + webviewView: WebviewView, + context: WebviewViewResolveContext, + _token: CancellationToken, + ) { + this._view = webviewView; + + this._view.onDidDispose(() => this._view = undefined); + + const extPath = this._extensionUri; + const _localResourceRoots = [this._extensionUri]; + if (extPath) { + _localResourceRoots.push(Uri.file(path.resolve(extPath.fsPath, 'media'))); + const localIconPath = Uri.file(path.resolve(extPath.fsPath, 'icon/logo.svg')); + const iconDirPath = path.dirname(localIconPath.fsPath); + _localResourceRoots.push(Uri.file(iconDirPath)); + } + + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + localResourceRoots : _localResourceRoots + }; + + // hardcoded example file for now + const hintsPath = Uri.file( + path.resolve(this._extensionUri.fsPath, 'demos', 'hints', 'demohints.didact.md') + ); + this.setDidactUriPath(hintsPath); + + await this._update(); + this.handleEvents(); + } + + public async show(forceFocus?: boolean): Promise { + if (this._view && !forceFocus) { + this._view.show(); + } else { + await commands.executeCommand(`${HintBoxViewProvider.viewType}.focus`); + } + await this._update(); + } + + private async _update() { + const content = await extensionFunctions.getWebviewContent(); + if (content) { + const wrapped = this.wrapDidactContent(content); + if (this._view && this._view.webview && wrapped) { + this._view.webview.html = wrapped; + } + } + } + + public handleEvents() : void { + this._view?.webview.onDidReceiveMessage( + async message => { + console.log(message); + switch (message.command) { + case 'update': + if (message.text) { + this.currentHtml = message.text; + } + return; + case 'link': + if (message.text) { + try { + await commandHandler.processInputs(message.text, didactManager.getExtensionPath()); + } catch (error) { + window.showErrorMessage(`Didact was unable to call commands: ${message.text}: ${error}`); + } + } + return; + } + }, + null, + this._disposables + ); + } + + public async setDidactUriPath(inpath : Uri | undefined): Promise { + this.didactUriPath = inpath; + await extensionFunctions.setDidactFileUri(inpath); + await this._update(); + } + + public hardReset(): void { + const configuredUri : string | undefined = workspace.getConfiguration().get(DIDACT_DEFAULT_URL); + if (configuredUri) { + const defaultUri = Uri.parse(configuredUri); + this.didactUriPath = defaultUri; + } + } + + wrapDidactContent(didactHtml: string | undefined) : string | undefined { + if (!didactHtml || this._disposed) { + return; + } + const nonce = this.getNonce(); + const extPath = this._extensionUri.fsPath; + + // Base uri to support images + const didactUri : Uri = this.didactUriPath as Uri; + + let uriBaseHref = undefined; + if (didactUri && this._view) { + try { + const didactUriPath = path.dirname(didactUri.fsPath); + const uriBase = this._view.webview.asWebviewUri(Uri.file(didactUriPath)).toString(); + uriBaseHref = ``; + } catch (error) { + console.error(error); + } + } + + if (!extPath) { + console.error(`Error: Extension context not set on Didact manager`); + return undefined; + } + + // Local path to main script run in the webview + const scriptPathOnDisk = Uri.file( + path.resolve(extPath, 'media', 'main.js') + ); + + // And the uri we use to load this script in the webview + const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' }); + + // the cssUri is our path to the stylesheet included in the security policy + const cssPathOnDisk = Uri.file( + path.resolve(extPath, 'media', 'webviewslim.css') + ); + const cssUri = cssPathOnDisk.with({ scheme: 'vscode-resource' }); + + // this css holds our overrides for both asciidoc and markdown html + const cssUriHtml = ``; + + // process the stylesheet details for asciidoc or markdown-based didact files + const stylesheetHtml = this.produceStylesheetHTML(cssUriHtml); + + const extensionHandle = extensions.getExtension(extensionFunctions.EXTENSION_ID); + let didactVersionLabel = 'Didact'; + if (extensionHandle) { + const didactVersion = extensionHandle.packageJSON.version; + if (didactVersion) { + didactVersionLabel += ` ${didactVersion}`; + } + } + + let cspSrc = undefined; + if (this._view) { + cspSrc = this._view.webview.cspSource; + } else { + console.error(`Error: Content Security Policy not set on webview`); + return undefined; + } + + let metaHeader = ` + + `; + if (uriBaseHref) { + metaHeader += `\n${uriBaseHref}\n`; + } + + return ` + + + ${metaHeader} + Didact Tutorial` + + stylesheetHtml + + ` + + +
` + + didactHtml + + `
+
${didactVersionLabel}
+