diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 6fa682e351d..9ea81491be4 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -27,6 +27,11 @@ ], "contributes": { "commands": [ + { + "command": "unicorn", + "title": "🦄 unicorn", + "category": "Markdown" + }, { "command": "markdown.showPreview", "title": "%markdown.preview.title%", diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 53e0c6286e1..536a12d9f57 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -59,4 +59,12 @@ export function activate(context: vscode.ExtensionContext) { logger.updateConfiguration(); previewManager.updateConfiguration(); })); + + vscode.commands.registerCommand('unicorn', () => { + if (vscode.window.activeTextEditor) { + vscode.window.showWebviewWidget(vscode.window.activeTextEditor, vscode.window.activeTextEditor.selection.active, 'unicorn', 'Webview', {}).then(webview => { + webview.html = ''; + }); + } + }); } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f61737f5919..088c3616e2d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -685,6 +685,8 @@ declare module 'vscode' { * @param reviver Webview serializer. */ export function registerWebviewSerializer(viewType: string, reviver: WebviewSerializer): Disposable; + + export function showWebviewWidget(editor: TextEditor, position: Position, viewType: string, title: string, options: WebviewOptions): Thenable; } //#endregion diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index 0bf8ddedefa..04052285280 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -18,7 +18,10 @@ import { IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/w import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { ICodeEditor } from '../../../editor/browser/editorBrowser'; +import { EDITOR_CONTRIBUTION_ID, WebviewWidgetContribution } from '../../parts/webview/electron-browser/webviewWidget'; import { extHostNamedCustomer } from './extHostCustomers'; +import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver { @@ -34,7 +37,8 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv private _toDispose: IDisposable[] = []; private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviews = new Map(); + private readonly _webviewInputs = new Map(); + private readonly _webviews = new Map(); private readonly _revivers = new Set(); private _activeWebview: WebviewHandle | undefined = undefined; @@ -77,7 +81,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv state: undefined }; - this._webviews.set(handle, webview); + this._webviewInputs.set(handle, webview); } $disposeWebview(handle: WebviewHandle): void { @@ -92,7 +96,14 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv $setHtml(handle: WebviewHandle, value: string): void { const webview = this.getWebview(handle); - webview.html = value; + if (webview) { + webview.html = value; + } else { + const webview = this._webviews.get(handle); + if (webview) { + webview.contents = value; + } + } } $reveal(handle: WebviewHandle, column: Position): void { @@ -122,11 +133,24 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._revivers.delete(viewType); } + $showWebviewWidget(handle: WebviewHandle, editorId: string, lineNumber: number, viewType: string, options: WebviewInputOptions): void { + const editor = this._editorService.getActiveEditor(); + if (editor && editor.getControl()) { + (editor.getControl() as ICodeEditor).getContribution(EDITOR_CONTRIBUTION_ID).showWebviewWidget(lineNumber, 0, webview => { + this._webviews.set(handle, webview); + webview.onDidClickLink(uri => this.onDidClickLink(uri, webview.options)); + webview.onMessage(message => this._proxy.$onMessage(handle, message)); + }); + } + + return undefined; + } + reviveWebview(webview: WebviewEditorInput): TPromise { const viewType = webview.state.viewType; return this._extensionService.activateByEvent(`onView:${viewType}`).then(() => { const handle = 'revival-' + MainThreadWebviews.revivalPool++; - this._webviews.set(handle, webview); + this._webviewInputs.set(handle, webview); webview._events = this.createWebviewEventDelegate(handle); return this._proxy.$deserializeWebview(handle, webview.state.viewType, webview.state.state, webview.position, webview.options) @@ -142,7 +166,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv private _onWillShutdown(): TPromise { const toRevive: WebviewHandle[] = []; - this._webviews.forEach((view, key) => { + this._webviewInputs.forEach((view, key) => { if (this.canRevive(view)) { toRevive.push(key); } @@ -158,7 +182,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv return TPromise.join(reviveResponses).then(results => { for (const result of results) { - const view = this._webviews.get(result.handle); + const view = this._webviewInputs.get(result.handle); if (view) { if (result.state) { view.state.state = result.state; @@ -184,10 +208,10 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv } private getWebview(handle: WebviewHandle): WebviewEditorInput { - const webview = this._webviews.get(handle); - if (!webview) { - throw new Error('Unknown webview handle:' + handle); - } + const webview = this._webviewInputs.get(handle); + // if (!webview) { + // throw new Error('Unknown webview handle:' + handle); + // } return webview; } @@ -195,8 +219,8 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv const activeEditor = this._editorService.getActiveEditor(); let newActiveWebview: { input: WebviewEditorInput, handle: WebviewHandle } | undefined = undefined; if (activeEditor && activeEditor.input instanceof WebviewEditorInput) { - for (const handle of map.keys(this._webviews)) { - const input = this._webviews.get(handle); + for (const handle of map.keys(this._webviewInputs)) { + const input = this._webviewInputs.get(handle); if (input.matches(activeEditor.input)) { newActiveWebview = { input, handle }; break; @@ -211,7 +235,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv // Broadcast view state update for currently active if (typeof this._activeWebview !== 'undefined') { - const oldActiveWebview = this._webviews.get(this._activeWebview); + const oldActiveWebview = this._webviewInputs.get(this._activeWebview); if (oldActiveWebview) { this._proxy.$onDidChangeWeviewViewState(this._activeWebview, false, oldActiveWebview.position); } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 6757325af76..80e479583b5 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -421,6 +421,9 @@ export function createApiFactory( }), registerWebviewSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewSerializer) => { return extHostWebviews.registerWebviewSerializer(viewType, serializer); + }), + showWebviewWidget: proposedApiFunction(extension, (editor: vscode.TextEditor, position: vscode.Position, viewType: string, title: string, options: vscode.WebviewOptions) => { + return extHostWebviews.showWebviewWidget(editor, position.line, viewType, title, options); }) }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index aa980077368..8529238873c 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -359,6 +359,8 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; + + $showWebviewWidget(handle: WebviewHandle, editorId: string, lineNumber: number, viewType: string, options: vscode.WebviewOptions): void; } export interface ExtHostWebviewsShape { diff --git a/src/vs/workbench/api/node/extHostWebview.ts b/src/vs/workbench/api/node/extHostWebview.ts index b947841462d..5746e13bb29 100644 --- a/src/vs/workbench/api/node/extHostWebview.ts +++ b/src/vs/workbench/api/node/extHostWebview.ts @@ -10,6 +10,7 @@ import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { Position } from 'vs/platform/editor/common/editor'; import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from './extHostTypes'; +import { ExtHostTextEditor } from './extHostTextEditor'; export class ExtHostWebview implements vscode.Webview { @@ -141,7 +142,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly _serializers = new Map(); constructor( - mainContext: IMainContext + mainContext: IMainContext, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -178,6 +179,15 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }); } + async showWebviewWidget(editor: vscode.TextEditor, lineNumber: number, viewType: string, title: string, options: vscode.WebviewOptions) { + const handle = ExtHostWebviews.webviewHandlePool++ + ''; + this._proxy.$showWebviewWidget(handle, (editor as ExtHostTextEditor).id, lineNumber, viewType, options); + + const webview = new ExtHostWebview(handle, this._proxy, viewType, undefined, options); + this._webviews.set(handle, webview); + return webview; + } + $onMessage(handle: WebviewHandle, message: any): void { const webview = this.getWebview(handle); if (webview) { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts index 0ea8ed3965e..9e9ccc1d6dc 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts @@ -184,6 +184,10 @@ export class WebviewElement { parent.appendChild(this._webview); } + public getDomNode() { + return this._webview; + } + public notifyFindWidgetFocusChanged(isFocused: boolean) { this._contextKey.set(isFocused || document.activeElement === this._webview); } diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewWidget.ts b/src/vs/workbench/parts/webview/electron-browser/webviewWidget.ts new file mode 100644 index 00000000000..78e33ac2a0f --- /dev/null +++ b/src/vs/workbench/parts/webview/electron-browser/webviewWidget.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; +import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; + +export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.webview'; + +export class WebviewWidget extends ZoneWidget { + + private _webview: WebviewElement; + + constructor( + editor: ICodeEditor, + private readonly _delegate: (view: WebviewElement) => void, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService private readonly _themeService: IThemeService, + @IPartService private readonly _partService: IPartService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService + ) { + super(editor, {}); + + // this._applyTheme(_themeService.getTheme()); + // this._callOnDispose.push(_themeService.onThemeChange(this._applyTheme.bind(this))); + + this.create(); + } + + protected _fillContainer(container: HTMLElement): void { + this._webview = new WebviewElement( + this._partService.getContainer(Parts.EDITOR_PART), + this._themeService, + this._environmentService, + this._contextViewService, + undefined, + undefined, + { + enableWrappedPostMessage: true, + useSameOriginForRoot: false + }); + this._webview.mountTo(container); + + const e = new DomScrollableElement(this._webview.getDomNode(), {}); + e.getDomNode().style.width = '100%'; + e.getDomNode().style.height = '100%'; + container.appendChild(e.getDomNode()); + this._delegate(this._webview); + } +} + +export interface IWebviewWidgetContribution extends IEditorContribution { + showWebviewWidget(lineNumber: number, column: number, delegate: (view: WebviewElement) => void): void; + closeWebviewWidget(): void; +} + + +export class WebviewWidgetContribution implements IWebviewWidgetContribution { + private _webviewWidget: WebviewWidget; + + constructor( + private editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + showWebviewWidget(lineNumber: number, column: number, delegate: (view: WebviewElement) => void): void { + if (this._webviewWidget) { + this._webviewWidget.dispose(); + } + + this._webviewWidget = this.instantiationService.createInstance(WebviewWidget, this.editor, delegate); + this._webviewWidget.show({ lineNumber, column: 1 }, 20); + // this.webviewWidgetVisible.set(true); + } + + public closeWebviewWidget(): void { + if (this._webviewWidget) { + this._webviewWidget.dispose(); + this._webviewWidget = null; + // this.webviewWidgetVisible.reset(); + this.editor.focus(); + } + } + + getId(): string { + return EDITOR_CONTRIBUTION_ID; + } + + dispose(): void { + this.closeWebviewWidget(); + } +} + +registerEditorContribution(WebviewWidgetContribution); \ No newline at end of file