Skip to content
This repository was archived by the owner on May 11, 2022. It is now read-only.

Commit 2f87535

Browse files
committed
Hintbox work in progress
Signed-off-by: Brian Fitzpatrick <[email protected]>
1 parent e8b7b0c commit 2f87535

File tree

5 files changed

+322
-1
lines changed

5 files changed

+322
-1
lines changed

demos/hints/demohints.didact.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### Handy Commands
2+
3+
* [Open a terminal](didact://?commandId=vscode.didact.startTerminalWithName&text=Terminal-Name)
4+
* [Create an untitled file](didact://?commandId=workbench.action.files.newUntitledFile)

package.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
],
4040
"activationEvents": [
4141
"*",
42-
"onWebviewPanel:didact"
42+
"onView:didact.hintBoxView"
4343
],
4444
"main": "./out/extension.js",
4545
"contributes": {
@@ -73,6 +73,11 @@
7373
{
7474
"id": "didact.tutorials",
7575
"name": "Didact Tutorials"
76+
},
77+
{
78+
"type": "webview",
79+
"id": "didact.hintBoxView",
80+
"name": "Hintbox"
7681
}
7782
]
7883
},
@@ -105,6 +110,14 @@
105110
"light": "resources/light/refresh.svg"
106111
}
107112
},
113+
{
114+
"command": "didact.hintbox.refresh",
115+
"title": "Refresh Didact Hintbox",
116+
"icon": {
117+
"dark": "resources/dark/refresh.svg",
118+
"light": "resources/light/refresh.svg"
119+
}
120+
},
108121
{
109122
"command": "vscode.didact.verifyCommands",
110123
"title": "Validate Didact File",

src/extension.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { registerTutorialWithCategory, clearRegisteredTutorials, getOpenAtStartu
2222
import { DidactUriCompletionItemProvider } from './didactUriCompletionItemProvider';
2323
import { DidactPanelSerializer } from './didactPanelSerializer';
2424
import { VIEW_TYPE } from './didactManager';
25+
import { HintBoxViewProvider } from './hintBoxViewProvider';
2526

2627
const DIDACT_VIEW = 'didact.tutorials';
2728

@@ -30,6 +31,8 @@ const DEFAULT_TUTORIAL_NAME = "Didact Demo";
3031

3132
export const didactTutorialsProvider = new DidactNodeProvider();
3233
let didactTreeView : vscode.TreeView<TreeNode>;
34+
let hintBoxProvider : HintBoxViewProvider;
35+
const REFRESH_HINTBOX_ACTION = 'didact.hintbox.refresh';
3336

3437
export async function activate(context: vscode.ExtensionContext): Promise<void> {
3538

@@ -63,6 +66,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
6366
context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.PASTE_TO_EDITOR_FOR_FILE_COMMAND, extensionFunctions.pasteClipboardToEditorForFile));
6467
context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.PASTE_TO_NEW_FILE_COMMAND, extensionFunctions.pasteClipboardToNewTextFile));
6568
context.subscriptions.push(vscode.commands.registerCommand(extensionFunctions.REFRESH_DIDACT, extensionFunctions.refreshDidactWindow));
69+
context.subscriptions.push(vscode.commands.registerCommand(REFRESH_HINTBOX_ACTION, refreshHintBox));
6670

6771
// set up the vscode URI handler
6872
vscode.window.registerUriHandler({
@@ -108,6 +112,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
108112
// create the view
109113
createIntegrationsView();
110114

115+
// create the hintbox
116+
hintBoxProvider = new HintBoxViewProvider(context.extensionUri);
117+
context.subscriptions.push(vscode.window.registerWebviewViewProvider(HintBoxViewProvider.viewType, hintBoxProvider));
118+
111119
// open at startup if setting is true
112120
const openAtStartup : boolean = getOpenAtStartupSetting();
113121
if (openAtStartup) {
@@ -136,3 +144,9 @@ export function refreshTreeview(): void {
136144
didactTutorialsProvider.refresh();
137145
}
138146
}
147+
148+
export function refreshHintBox() : void {
149+
if (hintBoxProvider) {
150+
hintBoxProvider.show(true);
151+
}
152+
}

src/extensionFunctions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,3 +970,7 @@ export async function pasteClipboardToNewTextFile() : Promise<void> {
970970
await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');
971971
await pasteClipboardToActiveEditorOrPreviouslyUsedOne();
972972
}
973+
974+
export async function setDidactFileUri(newFileUri : vscode.Uri | undefined) {
975+
_didactFileUri = newFileUri;
976+
}

src/hintBoxViewProvider.ts

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as extensionFunctions from './extensionFunctions';
19+
import * as path from 'path';
20+
import { Disposable, Uri, workspace, window, extensions, WebviewViewProvider,
21+
WebviewView, WebviewViewResolveContext, CancellationToken, commands } from 'vscode';
22+
import { DIDACT_DEFAULT_URL } from './utils';
23+
import { didactManager } from './didactManager';
24+
import * as commandHandler from './commandHandler';
25+
26+
export class HintBoxViewProvider implements WebviewViewProvider {
27+
28+
public static readonly viewType = 'didact.hintBoxView';
29+
private _view?: WebviewView;
30+
private _disposables: Disposable[] = [];
31+
private currentHtml : string | undefined = undefined;
32+
private didactUriPath : Uri | undefined;
33+
private isAsciiDoc = false;
34+
private _disposed = false;
35+
public visible = false;
36+
37+
constructor(private readonly _extensionUri: Uri) {}
38+
39+
public async resolveWebviewView(
40+
webviewView: WebviewView,
41+
context: WebviewViewResolveContext,
42+
_token: CancellationToken,
43+
) {
44+
this._view = webviewView;
45+
46+
this._view.onDidDispose(() => this._view = undefined);
47+
48+
const extPath = this._extensionUri;
49+
if (!extPath) {
50+
console.error(`Error: Extension context not set on Didact manager`);
51+
}
52+
53+
const _localResourceRoots = [this._extensionUri];
54+
if (extPath) {
55+
_localResourceRoots.push(Uri.file(path.resolve(extPath.fsPath, 'media')));
56+
const localIconPath = Uri.file(path.resolve(extPath.fsPath, 'icon/logo.svg'));
57+
const iconDirPath = path.dirname(localIconPath.fsPath);
58+
_localResourceRoots.push(Uri.file(iconDirPath));
59+
}
60+
61+
webviewView.webview.options = {
62+
// Allow scripts in the webview
63+
enableScripts: true,
64+
localResourceRoots : _localResourceRoots
65+
};
66+
67+
const hintsPath = Uri.file(
68+
path.resolve(this._extensionUri.fsPath, 'demos', 'hints', 'demohints.didact.md')
69+
);
70+
this.setDidactUriPath(hintsPath);
71+
72+
await this._update();
73+
this.handleEvents();
74+
}
75+
76+
public async show(forceFocus?: boolean): Promise<void> {
77+
if (this._view && !forceFocus) {
78+
this._view.show();
79+
} else {
80+
await commands.executeCommand(`${HintBoxViewProvider.viewType}.focus`);
81+
}
82+
await this._update();
83+
}
84+
85+
private async _update() {
86+
const content = await extensionFunctions.getWebviewContent();
87+
if (content) {
88+
const wrapped = this.wrapDidactContent(content);
89+
if (this._view && this._view.webview && wrapped) {
90+
this._view.webview.html = wrapped;
91+
}
92+
}
93+
}
94+
95+
public handleEvents() : void {
96+
this._view?.webview.onDidReceiveMessage(
97+
async message => {
98+
console.log(message);
99+
switch (message.command) {
100+
case 'update':
101+
if (message.text) {
102+
this.currentHtml = message.text;
103+
}
104+
return;
105+
case 'link':
106+
if (message.text) {
107+
try {
108+
await commandHandler.processInputs(message.text, didactManager.getExtensionPath());
109+
} catch (error) {
110+
window.showErrorMessage(`Didact was unable to call commands: ${message.text}: ${error}`);
111+
}
112+
}
113+
return;
114+
}
115+
},
116+
null,
117+
this._disposables
118+
);
119+
}
120+
121+
public getDidactUriPath(): Uri | undefined {
122+
return this.didactUriPath;
123+
}
124+
125+
public async setDidactUriPath(inpath : Uri | undefined): Promise<void> {
126+
this.didactUriPath = inpath;
127+
await extensionFunctions.setDidactFileUri(inpath);
128+
await this._update();
129+
}
130+
131+
public hardReset(): void {
132+
const configuredUri : string | undefined = workspace.getConfiguration().get(DIDACT_DEFAULT_URL);
133+
if (configuredUri) {
134+
const defaultUri = Uri.parse(configuredUri);
135+
this.didactUriPath = defaultUri;
136+
}
137+
}
138+
139+
wrapDidactContent(didactHtml: string | undefined) : string | undefined {
140+
if (!didactHtml || this._disposed) {
141+
return;
142+
}
143+
const nonce = this.getNonce();
144+
const extPath = this._extensionUri.fsPath;
145+
146+
// Base uri to support images
147+
const didactUri : Uri = this.didactUriPath as Uri;
148+
149+
let uriBaseHref = undefined;
150+
if (didactUri && this._view) {
151+
try {
152+
const didactUriPath = path.dirname(didactUri.fsPath);
153+
const uriBase = this._view.webview.asWebviewUri(Uri.file(didactUriPath)).toString();
154+
uriBaseHref = `<base href="${uriBase}${uriBase.endsWith('/') ? '' : '/'}"/>`;
155+
} catch (error) {
156+
console.error(error);
157+
}
158+
}
159+
160+
if (!extPath) {
161+
console.error(`Error: Extension context not set on Didact manager`);
162+
return undefined;
163+
}
164+
165+
// Local path to main script run in the webview
166+
const scriptPathOnDisk = Uri.file(
167+
path.resolve(extPath, 'media', 'main.js')
168+
);
169+
170+
// And the uri we use to load this script in the webview
171+
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' });
172+
173+
// the cssUri is our path to the stylesheet included in the security policy
174+
const cssPathOnDisk = Uri.file(
175+
path.resolve(extPath, 'media', 'webviewslim.css')
176+
);
177+
const cssUri = cssPathOnDisk.with({ scheme: 'vscode-resource' });
178+
179+
// this css holds our overrides for both asciidoc and markdown html
180+
const cssUriHtml = `<link rel="stylesheet" href="${cssUri}"/>`;
181+
182+
// process the stylesheet details for asciidoc or markdown-based didact files
183+
const stylesheetHtml = this.produceStylesheetHTML(cssUriHtml);
184+
185+
const extensionHandle = extensions.getExtension(extensionFunctions.EXTENSION_ID);
186+
let didactVersionLabel = 'Didact';
187+
if (extensionHandle) {
188+
const didactVersion = extensionHandle.packageJSON.version;
189+
if (didactVersion) {
190+
didactVersionLabel += ` ${didactVersion}`;
191+
}
192+
}
193+
194+
let cspSrc = undefined;
195+
if (this._view) {
196+
cspSrc = this._view.webview.cspSource;
197+
} else {
198+
console.error(`Error: Content Security Policy not set on webview`);
199+
return undefined;
200+
}
201+
202+
let metaHeader = `<meta charset="UTF-8"/>
203+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
204+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data: https: http: blob: ${cspSrc}; media-src vscode-resource: https: data:; script-src 'nonce-${nonce}' https:; style-src 'unsafe-inline' ${this._view.webview.cspSource} https: data:; font-src ${this._view.webview.cspSource} https: data:; object-src 'none';"/>`;
205+
if (uriBaseHref) {
206+
metaHeader += `\n${uriBaseHref}\n`;
207+
}
208+
209+
return `<!DOCTYPE html>
210+
<html lang="en">
211+
<head>
212+
${metaHeader}
213+
<title>Didact Tutorial</title>` +
214+
stylesheetHtml +
215+
`<script defer="true" src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
216+
</head>
217+
<body class="content">
218+
<div class="tutorialContent">`
219+
+ didactHtml +
220+
`</div>
221+
<div class="didactFooter">${didactVersionLabel}</div>
222+
<script nonce="${nonce}" src="${scriptUri}"/>
223+
</body>
224+
</html>`;
225+
}
226+
227+
produceStylesheetHTML(cssUriHtml : string) : string {
228+
let stylesheetHtml = '';
229+
if (this.isAsciiDoc) {
230+
// use asciidoctor-default.css with import from
231+
// https://cdn.jsdelivr.net/gh/asciidoctor/asciidoctor@v2.0.10/data/stylesheets/asciidoctor-default.css
232+
const adUriHtml = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/asciidoctor/[email protected]/data/stylesheets/asciidoctor-default.css"/>`;
233+
stylesheetHtml = `${adUriHtml}\n ${cssUriHtml}\n`;
234+
} else {
235+
// use bulma.min.css as the default stylesheet for markdown from https://bulma.io/
236+
const bulmaCssHtml = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css"/>`;
237+
stylesheetHtml = `${bulmaCssHtml}\n ${cssUriHtml}\n`;
238+
}
239+
return stylesheetHtml;
240+
}
241+
242+
getNonce() : string {
243+
let text = '';
244+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
245+
for (let i = 0; i < 32; i++) {
246+
text += possible.charAt(Math.floor(Math.random() * possible.length));
247+
}
248+
return text;
249+
}
250+
251+
public async postMessage(message: string): Promise<void> {
252+
if (!this._view) {
253+
return;
254+
}
255+
const jsonMsg:string = "{ \"command\": \"sendMessage\", \"data\": \"" + message + "\"}";
256+
this._view.webview.postMessage(jsonMsg);
257+
}
258+
259+
public async postRequirementsResponseMessage(requirementName: string, result: boolean): Promise<void> {
260+
if (!this._view) {
261+
return;
262+
}
263+
const jsonMsg:string = "{ \"command\": \"requirementCheck\", \"requirementName\": \"" + requirementName + "\", \"result\": \"" + result + "\"}";
264+
this._view.webview.postMessage(jsonMsg);
265+
}
266+
267+
async postNamedSimpleMessage(msg: string): Promise<void> {
268+
if (!this._view) {
269+
return;
270+
}
271+
const jsonMsg = `{ "command" : "${msg}"}`;
272+
this._view.webview.postMessage(jsonMsg);
273+
}
274+
275+
public async postTestAllRequirementsMessage(): Promise<void> {
276+
this.postNamedSimpleMessage("allRequirementCheck");
277+
}
278+
279+
public async postCollectAllRequirementsMessage(): Promise<void> {
280+
this.postNamedSimpleMessage("returnRequirements");
281+
}
282+
283+
public async postCollectAllCommandIdsMessage(): Promise<void> {
284+
this.postNamedSimpleMessage("returnCommands");
285+
}
286+
}

0 commit comments

Comments
 (0)