debt - decouple webviews from code insets, move things to /browser/-layer, change inset api proposal to push style, re #66418
This commit is contained in:
parent
3dc76ed11f
commit
6f1da34c2e
|
@ -38,10 +38,6 @@
|
|||
"name": "vs/workbench/contrib/codeEditor",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/codeinset",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/callHierarchy",
|
||||
"project": "vscode-workbench"
|
||||
|
|
28
src/vs/vscode.proposed.d.ts
vendored
28
src/vs/vscode.proposed.d.ts
vendored
|
@ -137,32 +137,20 @@ declare module 'vscode' {
|
|||
|
||||
// #region Joh - code insets
|
||||
|
||||
/**
|
||||
*/
|
||||
export class CodeInset {
|
||||
range: Range;
|
||||
height?: number;
|
||||
constructor(range: Range, height?: number);
|
||||
export interface WebviewEditorInset {
|
||||
readonly editor: TextEditor;
|
||||
readonly range: Range;
|
||||
readonly webview: Webview;
|
||||
readonly onDidDispose: Event<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface CodeInsetProvider {
|
||||
onDidChangeCodeInsets?: Event<void>;
|
||||
provideCodeInsets(document: TextDocument, token: CancellationToken): ProviderResult<CodeInset[]>;
|
||||
resolveCodeInset(codeInset: CodeInset, webview: Webview, token: CancellationToken): ProviderResult<CodeInset>;
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
|
||||
/**
|
||||
* Register a code inset provider.
|
||||
*
|
||||
*/
|
||||
export function registerCodeInsetProvider(selector: DocumentSelector, provider: CodeInsetProvider): Disposable;
|
||||
export namespace window {
|
||||
export function createWebviewTextEditorInset(editor: TextEditor, range: Range, options?: WebviewOptions): WebviewEditorInset;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Joh - read/write in chunks
|
||||
|
||||
export interface FileSystemProvider {
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
// --- other interested parties
|
||||
import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint';
|
||||
import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint';
|
||||
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
|
||||
|
||||
// --- mainThread participants
|
||||
import './mainThreadClipboard';
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
import './mainThreadConsole';
|
||||
import './mainThreadDebugService';
|
||||
import './mainThreadDecorations';
|
||||
import './mainThreadDiagnostics';
|
||||
import './mainThreadDialogs';
|
||||
import './mainThreadDocumentContentProviders';
|
||||
import './mainThreadDocuments';
|
||||
import './mainThreadDocumentsAndEditors';
|
||||
import './mainThreadEditor';
|
||||
import './mainThreadEditors';
|
||||
import './mainThreadErrors';
|
||||
import './mainThreadExtensionService';
|
||||
import './mainThreadFileSystem';
|
||||
import './mainThreadFileSystemEventService';
|
||||
import './mainThreadHeapService';
|
||||
import './mainThreadKeytar';
|
||||
import './mainThreadLanguageFeatures';
|
||||
import './mainThreadLanguages';
|
||||
import './mainThreadLogService';
|
||||
import './mainThreadMessageService';
|
||||
import './mainThreadOutputService';
|
||||
import './mainThreadProgress';
|
||||
import './mainThreadQuickOpen';
|
||||
import './mainThreadSaveParticipant';
|
||||
import './mainThreadSCM';
|
||||
import './mainThreadSearch';
|
||||
import './mainThreadStatusBar';
|
||||
import './mainThreadStorage';
|
||||
import './mainThreadTelemetry';
|
||||
import './mainThreadTerminalService';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadUrls';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWorkspace';
|
||||
import './mainThreadComments';
|
||||
import './mainThreadTask';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
// Classes that handle extension points...
|
||||
this.instantiationService.createInstance(JSONValidationExtensionPoint);
|
||||
this.instantiationService.createInstance(ColorExtensionPoint);
|
||||
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionPoints, LifecyclePhase.Starting);
|
|
@ -3,5 +3,70 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './extensionHost.contribution.common';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
// --- other interested parties
|
||||
import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint';
|
||||
import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint';
|
||||
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
|
||||
|
||||
// --- mainThread participants
|
||||
import './mainThreadCodeInsets';
|
||||
import './mainThreadClipboard';
|
||||
import './mainThreadCommands';
|
||||
import './mainThreadConfiguration';
|
||||
import './mainThreadConsole';
|
||||
import './mainThreadDebugService';
|
||||
import './mainThreadDecorations';
|
||||
import './mainThreadDiagnostics';
|
||||
import './mainThreadDialogs';
|
||||
import './mainThreadDocumentContentProviders';
|
||||
import './mainThreadDocuments';
|
||||
import './mainThreadDocumentsAndEditors';
|
||||
import './mainThreadEditor';
|
||||
import './mainThreadEditors';
|
||||
import './mainThreadErrors';
|
||||
import './mainThreadExtensionService';
|
||||
import './mainThreadFileSystem';
|
||||
import './mainThreadFileSystemEventService';
|
||||
import './mainThreadHeapService';
|
||||
import './mainThreadKeytar';
|
||||
import './mainThreadLanguageFeatures';
|
||||
import './mainThreadLanguages';
|
||||
import './mainThreadLogService';
|
||||
import './mainThreadMessageService';
|
||||
import './mainThreadOutputService';
|
||||
import './mainThreadProgress';
|
||||
import './mainThreadQuickOpen';
|
||||
import './mainThreadSaveParticipant';
|
||||
import './mainThreadSCM';
|
||||
import './mainThreadSearch';
|
||||
import './mainThreadStatusBar';
|
||||
import './mainThreadStorage';
|
||||
import './mainThreadTelemetry';
|
||||
import './mainThreadTerminalService';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadUrls';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWebview';
|
||||
import './mainThreadWorkspace';
|
||||
import './mainThreadComments';
|
||||
import './mainThreadTask';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
// Classes that handle extension points...
|
||||
this.instantiationService.createInstance(JSONValidationExtensionPoint);
|
||||
this.instantiationService.createInstance(ColorExtensionPoint);
|
||||
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionPoints, LifecyclePhase.Starting);
|
||||
|
|
144
src/vs/workbench/api/browser/mainThreadCodeInsets.ts
Normal file
144
src/vs/workbench/api/browser/mainThreadCodeInsets.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { MainContext, MainThreadEditorInsetsShape, IExtHostContext, ExtHostEditorInsetsShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/webview';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
// todo@joh move these things back into something like contrib/insets
|
||||
class EditorWebviewZone implements IViewZone {
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
readonly afterLineNumber: number;
|
||||
readonly afterColumn: number;
|
||||
readonly heightInLines: number;
|
||||
|
||||
private _id: number;
|
||||
// suppressMouseDown?: boolean | undefined;
|
||||
// heightInPx?: number | undefined;
|
||||
// minWidthInPx?: number | undefined;
|
||||
// marginDomNode?: HTMLElement | null | undefined;
|
||||
// onDomNodeTop?: ((top: number) => void) | undefined;
|
||||
// onComputedHeight?: ((height: number) => void) | undefined;
|
||||
|
||||
constructor(
|
||||
readonly editor: IActiveCodeEditor,
|
||||
readonly range: IRange,
|
||||
readonly webview: Webview,
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
this.afterLineNumber = range.startLineNumber;
|
||||
this.afterColumn = range.startColumn;
|
||||
this.heightInLines = range.endLineNumber - range.startLineNumber;
|
||||
|
||||
editor.changeViewZones(accessor => this._id = accessor.addZone(this));
|
||||
webview.mountTo(this.domNode);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.editor.changeViewZones(accessor => accessor.removeZone(this._id));
|
||||
}
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadEditorInsets)
|
||||
export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
||||
|
||||
private readonly _proxy: ExtHostEditorInsetsShape;
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _insets = new Map<number, EditorWebviewZone>();
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@IWebviewService private readonly _webviewService: IWebviewService,
|
||||
) {
|
||||
this._proxy = context.getProxy(ExtHostContext.ExtHostEditorInsets);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
async $createEditorInset(handle: number, id: string, uri: UriComponents, range: IRange, options: modes.IWebviewOptions): Promise<void> {
|
||||
|
||||
let editor: IActiveCodeEditor | undefined;
|
||||
id = id.substr(0, id.indexOf(',')); //todo@joh HACK
|
||||
|
||||
for (const candidate of this._editorService.listCodeEditors()) {
|
||||
if (candidate.getId() === id && candidate.hasModel() && candidate.getModel()!.uri.toString() === URI.revive(uri).toString()) {
|
||||
editor = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!editor) {
|
||||
setTimeout(() => this._proxy.$onDidDispose(handle));
|
||||
return;
|
||||
}
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const webview = this._webviewService.createWebview({
|
||||
enableFindWidget: false,
|
||||
allowSvgs: false,
|
||||
extension: undefined
|
||||
}, {
|
||||
allowScripts: options.enableScripts
|
||||
});
|
||||
|
||||
const webviewZone = new EditorWebviewZone(editor, range, webview);
|
||||
|
||||
const remove = () => {
|
||||
disposables.dispose();
|
||||
this._proxy.$onDidDispose(handle);
|
||||
this._insets.delete(handle);
|
||||
};
|
||||
|
||||
disposables.add(editor.onDidChangeModel(remove));
|
||||
disposables.add(editor.onDidDispose(remove));
|
||||
disposables.add(webviewZone);
|
||||
disposables.add(webview);
|
||||
disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg)));
|
||||
|
||||
this._insets.set(handle, webviewZone);
|
||||
}
|
||||
|
||||
$disposeEditorInset(handle: number): void {
|
||||
const inset = this._insets.get(handle);
|
||||
if (inset) {
|
||||
this._insets.delete(handle);
|
||||
inset.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
$setHtml(handle: number, value: string): void {
|
||||
const inset = this._insets.get(handle);
|
||||
if (inset) {
|
||||
inset.webview.html = value;
|
||||
}
|
||||
}
|
||||
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void {
|
||||
const inset = this._insets.get(handle);
|
||||
if (inset) {
|
||||
inset.webview.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
$postMessage(handle: number, value: any): Promise<boolean> {
|
||||
const inset = this._insets.get(handle);
|
||||
if (inset) {
|
||||
inset.webview.sendMessage(value);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
|
@ -11,14 +11,13 @@ import * as search from 'vs/workbench/contrib/search/common/search';
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Position as EditorPosition } from 'vs/editor/common/core/position';
|
||||
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto, CallHierarchyDto, SuggestDataDto } from '../common/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto } from '../common/extHost.protocol';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { IHeapService } from 'vs/workbench/services/heap/common/heap';
|
||||
|
@ -178,35 +177,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
|||
}
|
||||
}
|
||||
|
||||
// -- code inset
|
||||
|
||||
$registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void {
|
||||
|
||||
const provider = <codeInset.CodeInsetProvider>{
|
||||
provideCodeInsets: (model: ITextModel, token: CancellationToken): CodeInsetDto[] | Thenable<CodeInsetDto[]> => {
|
||||
return this._proxy.$provideCodeInsets(handle, model.uri, token).then(dto => {
|
||||
if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); }
|
||||
return dto;
|
||||
});
|
||||
},
|
||||
resolveCodeInset: (model: ITextModel, codeInset: CodeInsetDto, token: CancellationToken): CodeInsetDto | Thenable<CodeInsetDto> => {
|
||||
return this._proxy.$resolveCodeInset(handle, model.uri, codeInset, token).then(obj => {
|
||||
this._heapService.trackObject(obj);
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof eventHandle === 'number') {
|
||||
const emitter = new Emitter<codeInset.CodeInsetProvider>();
|
||||
this._registrations[eventHandle] = emitter;
|
||||
provider.onDidChange = emitter.event;
|
||||
}
|
||||
|
||||
const langSelector = selector;
|
||||
this._registrations[handle] = codeInset.CodeInsetProviderRegistry.register(langSelector, provider);
|
||||
}
|
||||
|
||||
// --- declaration
|
||||
|
||||
$registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
|
|
|
@ -3,46 +3,365 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { UriComponents } from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as map from 'vs/base/common/map';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { MainContext, MainThreadWebviewsShape, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
|
||||
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
|
||||
import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
||||
export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape {
|
||||
$createWebviewPanel(handle: string, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
private static readonly standardSupportedLinkSchemes = new Set([
|
||||
'http',
|
||||
'https',
|
||||
'mailto',
|
||||
'vscode',
|
||||
'vscode-insider',
|
||||
]);
|
||||
|
||||
private static revivalPool = 0;
|
||||
|
||||
|
||||
private readonly _proxy: ExtHostWebviewsShape;
|
||||
private readonly _webviews = new Map<WebviewPanelHandle, WebviewEditorInput>();
|
||||
private readonly _revivers = new Map<string, IDisposable>();
|
||||
|
||||
private _activeWebview: WebviewPanelHandle | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
|
||||
this._register(_editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this));
|
||||
this._register(_editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this));
|
||||
|
||||
// This reviver's only job is to activate webview extensions
|
||||
// This should trigger the real reviver to be registered from the extension host side.
|
||||
this._register(_webviewService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
const viewType = webview.state.viewType;
|
||||
if (viewType) {
|
||||
extensionService.activateByEvent(`onWebviewPanel:${viewType}`);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
reviveWebview: () => { throw new Error('not implemented'); }
|
||||
}));
|
||||
|
||||
this._register(lifecycleService.onBeforeShutdown(e => {
|
||||
e.veto(this._onBeforeShutdown());
|
||||
}, this));
|
||||
}
|
||||
$createWebviewCodeInset(handle: number, symbolId: string, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier | undefined, extensionLocation: UriComponents | undefined): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $createWebviewPanel(
|
||||
handle: WebviewPanelHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean },
|
||||
options: WebviewInputOptions,
|
||||
extensionId: ExtensionIdentifier,
|
||||
extensionLocation: UriComponents
|
||||
): void {
|
||||
const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null);
|
||||
if (showOptions) {
|
||||
mainThreadShowOptions.preserveFocus = !!showOptions.preserveFocus;
|
||||
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
|
||||
}
|
||||
|
||||
const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
|
||||
location: URI.revive(extensionLocation),
|
||||
id: extensionId
|
||||
}, this.createWebviewEventDelegate(handle));
|
||||
webview.state = {
|
||||
viewType: viewType,
|
||||
state: undefined
|
||||
};
|
||||
|
||||
this._webviews.set(handle, webview);
|
||||
|
||||
/* __GDPR__
|
||||
"webviews:createWebviewPanel" : {
|
||||
"extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value });
|
||||
}
|
||||
$disposeWebview(handle: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $disposeWebview(handle: WebviewPanelHandle): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.dispose();
|
||||
}
|
||||
$reveal(handle: string, showOptions: WebviewPanelShowOptions): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $setTitle(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setName(value);
|
||||
}
|
||||
$setTitle(handle: string, value: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
}
|
||||
$setIconPath(handle: string, value: { light: UriComponents; dark: UriComponents; } | undefined): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $setHtml(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.html = value;
|
||||
}
|
||||
$setHtml(handle: string | number, value: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */));
|
||||
}
|
||||
$setOptions(handle: string | number, options: modes.IWebviewOptions): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (webview.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn)) || this._editorGroupService.getGroup(webview.group || 0);
|
||||
if (targetGroup) {
|
||||
this._webviewService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus);
|
||||
}
|
||||
}
|
||||
$postMessage(handle: string | number, value: any): Promise<boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public async $postMessage(handle: WebviewPanelHandle, message: any): Promise<boolean> {
|
||||
const webview = this.getWebview(handle);
|
||||
const editors = this._editorService.visibleControls
|
||||
.filter(e => e instanceof WebviewEditor)
|
||||
.map(e => e as WebviewEditor)
|
||||
.filter(e => e.input!.matches(webview));
|
||||
|
||||
if (editors.length > 0) {
|
||||
editors[0].sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (webview.webview) {
|
||||
webview.webview.sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
$registerSerializer(viewType: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $registerSerializer(viewType: string): void {
|
||||
if (this._revivers.has(viewType)) {
|
||||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._revivers.set(viewType, this._webviewService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
return webview.state && webview.state.viewType === viewType;
|
||||
},
|
||||
reviveWebview: async (webview): Promise<void> => {
|
||||
const viewType = webview.state.viewType;
|
||||
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
|
||||
this._webviews.set(handle, webview);
|
||||
webview._events = this.createWebviewEventDelegate(handle);
|
||||
let state = undefined;
|
||||
if (webview.state.state) {
|
||||
try {
|
||||
state = JSON.parse(webview.state.state);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this._proxy.$deserializeWebviewPanel(handle, viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group || 0), webview.options);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
$unregisterSerializer(viewType: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
public $unregisterSerializer(viewType: string): void {
|
||||
const reviver = this._revivers.get(viewType);
|
||||
if (!reviver) {
|
||||
throw new Error(`No reviver for ${viewType} registered`);
|
||||
}
|
||||
|
||||
reviver.dispose();
|
||||
this._revivers.delete(viewType);
|
||||
}
|
||||
|
||||
private getInternalWebviewId(viewType: string): string {
|
||||
return `mainThreadWebview-${viewType}`;
|
||||
}
|
||||
|
||||
private _onBeforeShutdown(): boolean {
|
||||
this._webviews.forEach((webview) => {
|
||||
if (!webview.isDisposed() && webview.state && this._revivers.has(webview.state.viewType)) {
|
||||
webview.state.state = webview.webviewState;
|
||||
}
|
||||
});
|
||||
return false; // Don't veto shutdown
|
||||
}
|
||||
|
||||
private createWebviewEventDelegate(handle: WebviewPanelHandle) {
|
||||
return {
|
||||
onDidClickLink: (uri: URI) => this.onDidClickLink(handle, uri),
|
||||
onMessage: (message: any) => this._proxy.$onMessage(handle, message),
|
||||
onDispose: () => {
|
||||
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
|
||||
this._webviews.delete(handle);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private onActiveEditorChanged() {
|
||||
const activeEditor = this._editorService.activeControl;
|
||||
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined;
|
||||
if (activeEditor && activeEditor.input instanceof WebviewEditorInput) {
|
||||
for (const handle of map.keys(this._webviews)) {
|
||||
const input = this._webviews.get(handle)!;
|
||||
if (input.matches(activeEditor.input)) {
|
||||
newActiveWebview = { input, handle };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newActiveWebview && newActiveWebview.handle === this._activeWebview) {
|
||||
// Webview itself unchanged but position may have changed
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group || 0)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Broadcast view state update for currently active
|
||||
if (typeof this._activeWebview !== 'undefined') {
|
||||
const oldActiveWebview = this._webviews.get(this._activeWebview);
|
||||
if (oldActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, {
|
||||
active: false,
|
||||
visible: this._editorService.visibleControls.some(editor => !!editor.input && editor.input.matches(oldActiveWebview)),
|
||||
position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group || 0),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Then for newly active
|
||||
if (newActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, activeEditor ? activeEditor.group : ACTIVE_GROUP),
|
||||
});
|
||||
this._activeWebview = newActiveWebview.handle;
|
||||
} else {
|
||||
this._activeWebview = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onVisibleEditorsChanged(): void {
|
||||
this._webviews.forEach((input, handle) => {
|
||||
for (const workbenchEditor of this._editorService.visibleControls) {
|
||||
if (workbenchEditor.input && workbenchEditor.input.matches(input)) {
|
||||
const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group!);
|
||||
|
||||
input.updateGroup(workbenchEditor.group!.id);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(handle, {
|
||||
active: handle === this._activeWebview,
|
||||
visible: true,
|
||||
position: editorPosition
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onDidClickLink(handle: WebviewPanelHandle, link: URI): void {
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
const webview = this.getWebview(handle);
|
||||
if (this.isSupportedLink(webview, link)) {
|
||||
this._openerService.open(link);
|
||||
}
|
||||
}
|
||||
|
||||
private isSupportedLink(webview: WebviewEditorInput, link: URI): boolean {
|
||||
if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) {
|
||||
return true;
|
||||
}
|
||||
if (this._productService.urlProtocol === link.scheme) {
|
||||
return true;
|
||||
}
|
||||
return !!webview.options.enableCommandUris && link.scheme === 'command';
|
||||
}
|
||||
|
||||
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
|
||||
const webview = this._webviews.get(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
private static getDeserializationFailedContents(viewType: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="https://code.visualstudio.com/raw/">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: data:; media-src https:; script-src 'none'; style-src vscode-core-resource: https: 'unsafe-inline'; child-src 'none'; frame-src 'none';">
|
||||
</head>
|
||||
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)}</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function reviveWebviewOptions(options: WebviewInputOptions): WebviewInputOptions {
|
||||
return {
|
||||
...options,
|
||||
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(r => URI.revive(r)) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents } | undefined
|
||||
): { light: URI, dark: URI } | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
light: URI.revive(value.light),
|
||||
dark: URI.revive(value.dark)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
|||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { ResolvedAuthority, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset';
|
||||
import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
|
@ -333,7 +332,6 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
|||
$unregister(handle: number): void;
|
||||
$registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], label: string): void;
|
||||
$registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void;
|
||||
$registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void;
|
||||
$emitCodeLensEvent(eventHandle: number, event?: any): void;
|
||||
$registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void;
|
||||
$registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void;
|
||||
|
@ -510,9 +508,21 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
|||
$publicLog(eventName: string, data?: any): void;
|
||||
}
|
||||
|
||||
export type WebviewPanelHandle = string;
|
||||
export interface MainThreadEditorInsetsShape extends IDisposable {
|
||||
$createEditorInset(handle: number, editorId: string, document: UriComponents, range: IRange, options: modes.IWebviewOptions): Promise<void>;
|
||||
$disposeEditorInset(handle: number): void;
|
||||
|
||||
export type WebviewInsetHandle = number;
|
||||
$setHtml(handle: number, value: string): void;
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void;
|
||||
$postMessage(handle: number, value: any): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface ExtHostEditorInsetsShape {
|
||||
$onDidDispose(handle: number): void;
|
||||
$onDidReceiveMessage(handle: number, message: any): void;
|
||||
}
|
||||
|
||||
export type WebviewPanelHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
readonly viewColumn?: EditorViewColumn;
|
||||
|
@ -521,15 +531,14 @@ export interface WebviewPanelShowOptions {
|
|||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void;
|
||||
$createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier | undefined, extensionLocation: UriComponents | undefined): void;
|
||||
$disposeWebview(handle: WebviewPanelHandle): void;
|
||||
$reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void;
|
||||
$setTitle(handle: WebviewPanelHandle, value: string): void;
|
||||
$setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void;
|
||||
|
||||
$setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void;
|
||||
$setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: modes.IWebviewOptions): void;
|
||||
$postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, value: any): Promise<boolean>;
|
||||
$setHtml(handle: WebviewPanelHandle, value: string): void;
|
||||
$setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void;
|
||||
$postMessage(handle: WebviewPanelHandle, value: any): Promise<boolean>;
|
||||
|
||||
$registerSerializer(viewType: string): void;
|
||||
$unregisterSerializer(viewType: string): void;
|
||||
|
@ -1005,8 +1014,6 @@ export interface CodeLensDto extends ObjectIdentifier {
|
|||
command?: CommandDto;
|
||||
}
|
||||
|
||||
export type CodeInsetDto = ObjectIdentifier & codeInset.ICodeInsetSymbol;
|
||||
|
||||
export interface CallHierarchyDto {
|
||||
_id: number;
|
||||
kind: modes.SymbolKind;
|
||||
|
@ -1021,8 +1028,6 @@ export interface ExtHostLanguageFeaturesShape {
|
|||
$provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<modes.DocumentSymbol[] | undefined>;
|
||||
$provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<CodeLensDto[]>;
|
||||
$resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise<CodeLensDto | undefined>;
|
||||
$provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise<CodeInsetDto[] | undefined>;
|
||||
$resolveCodeInset(handle: number, resource: UriComponents, symbol: CodeInsetDto, token: CancellationToken): Promise<CodeInsetDto>;
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<DefinitionLinkDto[]>;
|
||||
$provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<DefinitionLinkDto[]>;
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<DefinitionLinkDto[]>;
|
||||
|
@ -1248,6 +1253,7 @@ export const MainContext = {
|
|||
MainThreadDocuments: createMainId<MainThreadDocumentsShape>('MainThreadDocuments'),
|
||||
MainThreadDocumentContentProviders: createMainId<MainThreadDocumentContentProvidersShape>('MainThreadDocumentContentProviders'),
|
||||
MainThreadTextEditors: createMainId<MainThreadTextEditorsShape>('MainThreadTextEditors'),
|
||||
MainThreadEditorInsets: createMainId<MainThreadEditorInsetsShape>('MainThreadEditorInsets'),
|
||||
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors'),
|
||||
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews'),
|
||||
MainThreadKeytar: createMainId<MainThreadKeytarShape>('MainThreadKeytar'),
|
||||
|
@ -1298,6 +1304,7 @@ export const ExtHostContext = {
|
|||
ExtHostWorkspace: createExtId<ExtHostWorkspaceShape>('ExtHostWorkspace'),
|
||||
ExtHostWindow: createExtId<ExtHostWindowShape>('ExtHostWindow'),
|
||||
ExtHostWebviews: createExtId<ExtHostWebviewsShape>('ExtHostWebviews'),
|
||||
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
|
||||
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
|
||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||
|
|
130
src/vs/workbench/api/common/extHostCodeInsets.ts
Normal file
130
src/vs/workbench/api/common/extHostCodeInsets.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainThreadEditorInsetsShape } from './extHost.protocol';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
|
||||
|
||||
export class ExtHostEditorInsets implements ExtHostEditorInsets {
|
||||
|
||||
private _handlePool = 0;
|
||||
private _disposables = new DisposableStore();
|
||||
private _insets = new Map<number, { editor: vscode.TextEditor, inset: vscode.WebviewEditorInset, onDidReceiveMessage: Emitter<any> }>();
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadEditorInsetsShape,
|
||||
private readonly _editors: ExtHostEditors
|
||||
) {
|
||||
|
||||
// dispose editor inset whenever the hosting editor goes away
|
||||
this._disposables.add(_editors.onDidChangeVisibleTextEditors(() => {
|
||||
const visibleEditor = _editors.getVisibleTextEditors();
|
||||
this._insets.forEach(value => {
|
||||
if (visibleEditor.indexOf(value.editor) < 0) {
|
||||
value.inset.dispose(); // will remove from `this._insets`
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._insets.forEach(value => value.inset.dispose());
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
createWebviewEditorInset(editor: vscode.TextEditor, range: vscode.Range, options?: vscode.WebviewOptions): vscode.WebviewEditorInset {
|
||||
|
||||
let apiEditor: ExtHostTextEditor | undefined;
|
||||
for (const candidate of this._editors.getVisibleTextEditors()) {
|
||||
if (candidate === editor) {
|
||||
apiEditor = <ExtHostTextEditor>candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!apiEditor) {
|
||||
throw new Error('not a visible editor');
|
||||
}
|
||||
|
||||
const that = this;
|
||||
const handle = this._handlePool++;
|
||||
const onDidReceiveMessage = new Emitter<any>();
|
||||
const onDidDispose = new Emitter<void>();
|
||||
|
||||
const webview = new class implements vscode.Webview {
|
||||
|
||||
private _html: string = '';
|
||||
private _options: vscode.WebviewOptions;
|
||||
|
||||
set options(value: vscode.WebviewOptions) {
|
||||
this._options = value;
|
||||
that._proxy.$setOptions(handle, value);
|
||||
}
|
||||
|
||||
get options(): vscode.WebviewOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
set html(value: string) {
|
||||
this._html = value;
|
||||
that._proxy.$setHtml(handle, value);
|
||||
}
|
||||
|
||||
get html(): string {
|
||||
return this._html;
|
||||
}
|
||||
|
||||
get onDidReceiveMessage(): vscode.Event<any> {
|
||||
return onDidReceiveMessage.event;
|
||||
}
|
||||
|
||||
postMessage(message: any): Thenable<boolean> {
|
||||
return that._proxy.$postMessage(handle, message);
|
||||
}
|
||||
};
|
||||
|
||||
const inset = new class implements vscode.WebviewEditorInset {
|
||||
|
||||
readonly editor: vscode.TextEditor = editor;
|
||||
readonly range: vscode.Range = range;
|
||||
readonly webview: vscode.Webview = webview;
|
||||
readonly onDidDispose: vscode.Event<void> = onDidDispose.event;
|
||||
|
||||
dispose(): void {
|
||||
if (that._insets.has(handle)) {
|
||||
that._insets.delete(handle);
|
||||
that._proxy.$disposeEditorInset(handle);
|
||||
onDidDispose.fire();
|
||||
|
||||
// final cleanup
|
||||
onDidDispose.dispose();
|
||||
onDidReceiveMessage.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, typeConverters.Range.from(range), options || {});
|
||||
this._insets.set(handle, { editor, inset, onDidReceiveMessage });
|
||||
|
||||
return inset;
|
||||
}
|
||||
|
||||
$onDidDispose(handle: number): void {
|
||||
const value = this._insets.get(handle);
|
||||
if (value) {
|
||||
value.inset.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
$onDidReceiveMessage(handle: number, message: any): void {
|
||||
const value = this._insets.get(handle);
|
||||
if (value) {
|
||||
value.onDidReceiveMessage.fire(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
|||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto, LinksListDto, ChainedCacheId } from './extHost.protocol';
|
||||
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId } from './extHost.protocol';
|
||||
import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
|
||||
|
@ -25,9 +25,6 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostWebview } from 'vs/workbench/api/common/extHostWebview';
|
||||
import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
|
@ -152,47 +149,6 @@ class CodeLensAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
class CodeInsetAdapter {
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _heapService: ExtHostHeapService,
|
||||
private readonly _provider: vscode.CodeInsetProvider
|
||||
) { }
|
||||
|
||||
provideCodeInsets(resource: URI, token: CancellationToken): Promise<CodeInsetDto[] | undefined> {
|
||||
const doc = this._documents.getDocument(resource);
|
||||
return asPromise(() => this._provider.provideCodeInsets(doc, token)).then(insets => {
|
||||
if (Array.isArray(insets)) {
|
||||
return insets.map(inset => {
|
||||
const $ident = this._heapService.keep(inset);
|
||||
const id = generateUuid();
|
||||
return {
|
||||
$ident,
|
||||
id,
|
||||
range: typeConvert.Range.from(inset.range),
|
||||
height: inset.height
|
||||
};
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
resolveCodeInset(symbol: CodeInsetDto, webview: vscode.Webview, token: CancellationToken): Promise<CodeInsetDto> {
|
||||
|
||||
const inset = this._heapService.get<vscode.CodeInset>(ObjectIdentifier.of(symbol));
|
||||
if (!inset) {
|
||||
return Promise.resolve(symbol);
|
||||
}
|
||||
|
||||
return asPromise(() => this._provider.resolveCodeInset(inset, webview, token)).then(newInset => {
|
||||
newInset = newInset || inset;
|
||||
return symbol;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function convertToLocationLinks(value: vscode.Definition): modes.LocationLink[] {
|
||||
return value ? asArray(value).map(typeConvert.DefinitionLink.from) : [];
|
||||
}
|
||||
|
@ -1040,7 +996,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
|||
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter
|
||||
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter
|
||||
| ColorProviderAdapter | FoldingProviderAdapter | CodeInsetAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter;
|
||||
| ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter;
|
||||
|
||||
class AdapterData {
|
||||
constructor(
|
||||
|
@ -1061,7 +1017,6 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
|||
private _diagnostics: ExtHostDiagnostics;
|
||||
private _adapter = new Map<number, AdapterData>();
|
||||
private readonly _logService: ILogService;
|
||||
private _webviewProxy: MainThreadWebviewsShape;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
|
@ -1079,7 +1034,6 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
|||
this._heapService = heapMonitor;
|
||||
this._diagnostics = diagnostics;
|
||||
this._logService = logService;
|
||||
this._webviewProxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
||||
private _transformDocumentSelector(selector: vscode.DocumentSelector): Array<ISerializedDocumentFilter> {
|
||||
|
@ -1202,37 +1156,6 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
|||
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined);
|
||||
}
|
||||
|
||||
// --- code insets
|
||||
|
||||
registerCodeInsetProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeInsetProvider): vscode.Disposable {
|
||||
const handle = this._nextHandle();
|
||||
const eventHandle = typeof provider.onDidChangeCodeInsets === 'function' ? this._nextHandle() : undefined;
|
||||
|
||||
this._adapter.set(handle, new AdapterData(new CodeInsetAdapter(this._documents, this._heapService, provider), extension));
|
||||
this._proxy.$registerCodeInsetSupport(handle, this._transformDocumentSelector(selector), eventHandle);
|
||||
let result = this._createDisposable(handle);
|
||||
|
||||
if (eventHandle !== undefined && provider.onDidChangeCodeInsets) {
|
||||
const subscription = provider.onDidChangeCodeInsets(_ => this._proxy.$emitCodeLensEvent(eventHandle));
|
||||
result = Disposable.from(result, subscription);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
$provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise<codeInset.ICodeInsetSymbol[] | undefined> {
|
||||
return this._withAdapter(handle, CodeInsetAdapter, adapter => adapter.provideCodeInsets(URI.revive(resource), token), undefined);
|
||||
}
|
||||
|
||||
$resolveCodeInset(handle: number, _resource: UriComponents, symbol: codeInset.ICodeInsetSymbol, token: CancellationToken): Promise<codeInset.ICodeInsetSymbol> {
|
||||
const webviewHandle = Math.random();
|
||||
const webview = new ExtHostWebview(webviewHandle, this._webviewProxy, { enableScripts: true });
|
||||
return this._withAdapter(handle, CodeInsetAdapter, async (adapter, extension) => {
|
||||
await this._webviewProxy.$createWebviewCodeInset(webviewHandle, symbol.id, { enableCommandUris: true, enableScripts: true }, extension ? extension.identifier : undefined, extension ? extension.extensionLocation : undefined);
|
||||
return adapter.resolveCodeInset(symbol, webview, token);
|
||||
}, symbol);
|
||||
}
|
||||
|
||||
// --- declaration
|
||||
|
||||
registerDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from './extHost.protocol';
|
||||
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol';
|
||||
import { Disposable } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
|
@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes';
|
|||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
export class ExtHostWebview implements vscode.Webview {
|
||||
private readonly _handle: WebviewPanelHandle | WebviewInsetHandle;
|
||||
private readonly _handle: WebviewPanelHandle;
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private _html: string;
|
||||
private _options: vscode.WebviewOptions;
|
||||
|
@ -26,7 +26,7 @@ export class ExtHostWebview implements vscode.Webview {
|
|||
public readonly onDidReceiveMessage: Event<any> = this._onMessageEmitter.event;
|
||||
|
||||
constructor(
|
||||
handle: WebviewPanelHandle | WebviewInsetHandle,
|
||||
handle: WebviewPanelHandle,
|
||||
proxy: MainThreadWebviewsShape,
|
||||
options: vscode.WebviewOptions
|
||||
) {
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import '../browser/extensionHost.contribution.common';
|
||||
import './mainThreadWebview';
|
|
@ -1,437 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as map from 'vs/base/common/map';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewInsetHandle, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
|
||||
import { CodeInsetController } from 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution';
|
||||
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
|
||||
import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
|
||||
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
||||
export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape {
|
||||
|
||||
private static readonly standardSupportedLinkSchemes = new Set([
|
||||
'http',
|
||||
'https',
|
||||
'mailto',
|
||||
product.urlProtocol,
|
||||
'vscode',
|
||||
'vscode-insiders'
|
||||
]);
|
||||
|
||||
private static revivalPool = 0;
|
||||
|
||||
|
||||
private readonly _proxy: ExtHostWebviewsShape;
|
||||
private readonly _webviews = new Map<WebviewPanelHandle, WebviewEditorInput>();
|
||||
private readonly _webviewsElements = new Map<WebviewInsetHandle, WebviewElement>();
|
||||
private readonly _revivers = new Map<string, IDisposable>();
|
||||
|
||||
private _activeWebview: WebviewPanelHandle | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
|
||||
this._register(_editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this));
|
||||
this._register(_editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this));
|
||||
|
||||
// This reviver's only job is to activate webview extensions
|
||||
// This should trigger the real reviver to be registered from the extension host side.
|
||||
this._register(_webviewService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
const viewType = webview.state.viewType;
|
||||
if (viewType) {
|
||||
extensionService.activateByEvent(`onWebviewPanel:${viewType}`);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
reviveWebview: () => { throw new Error('not implemented'); }
|
||||
}));
|
||||
|
||||
this._register(lifecycleService.onBeforeShutdown(e => {
|
||||
e.veto(this._onBeforeShutdown());
|
||||
}, this));
|
||||
}
|
||||
|
||||
public $createWebviewPanel(
|
||||
handle: WebviewPanelHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean },
|
||||
options: WebviewInputOptions,
|
||||
extensionId: ExtensionIdentifier,
|
||||
extensionLocation: UriComponents
|
||||
): void {
|
||||
const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null);
|
||||
if (showOptions) {
|
||||
mainThreadShowOptions.preserveFocus = !!showOptions.preserveFocus;
|
||||
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
|
||||
}
|
||||
|
||||
const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
|
||||
location: URI.revive(extensionLocation),
|
||||
id: extensionId
|
||||
}, this.createWebviewEventDelegate(handle));
|
||||
webview.state = {
|
||||
viewType: viewType,
|
||||
state: undefined
|
||||
};
|
||||
|
||||
this._webviews.set(handle, webview);
|
||||
|
||||
/* __GDPR__
|
||||
"webviews:createWebviewPanel" : {
|
||||
"extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value });
|
||||
}
|
||||
|
||||
$createWebviewCodeInset(
|
||||
handle: WebviewInsetHandle,
|
||||
symbolId: string,
|
||||
options: modes.IWebviewOptions,
|
||||
extensionId: ExtensionIdentifier,
|
||||
extensionLocation: UriComponents
|
||||
): void {
|
||||
// todo@joh main is for the lack of a code-inset service
|
||||
// which we maybe wanna have... this is how it now works
|
||||
// 1) create webview element
|
||||
// 2) find the code inset controller that request it
|
||||
// 3) let the controller adopt the widget
|
||||
// 4) continue to forward messages to the webview
|
||||
const webview = this._instantiationService.createInstance(
|
||||
WebviewElement,
|
||||
{
|
||||
extension: {
|
||||
location: URI.revive(extensionLocation),
|
||||
id: extensionId
|
||||
},
|
||||
enableFindWidget: false,
|
||||
},
|
||||
{
|
||||
allowScripts: options.enableScripts,
|
||||
}
|
||||
);
|
||||
|
||||
let found = false;
|
||||
for (const editor of this._codeEditorService.listCodeEditors()) {
|
||||
const ctrl = CodeInsetController.get(editor);
|
||||
if (ctrl && ctrl.acceptWebview(symbolId, webview)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
webview.dispose();
|
||||
return;
|
||||
}
|
||||
// this will leak... the adopted webview will be disposed by the
|
||||
// code inset controller. we might need a dispose-event here so that
|
||||
// we can clean up things.
|
||||
this._webviewsElements.set(handle, webview);
|
||||
}
|
||||
|
||||
public $disposeWebview(handle: WebviewPanelHandle): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.dispose();
|
||||
}
|
||||
|
||||
public $setTitle(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setName(value);
|
||||
}
|
||||
|
||||
public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
}
|
||||
|
||||
public $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void {
|
||||
if (typeof handle === 'number') {
|
||||
this.getWebviewElement(handle).html = value;
|
||||
} else {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.html = value;
|
||||
}
|
||||
}
|
||||
|
||||
public $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: modes.IWebviewOptions): void {
|
||||
if (typeof handle === 'number') {
|
||||
this.getWebviewElement(handle).options = reviveWebviewOptions(options as any /*todo@mat */);
|
||||
} else {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */));
|
||||
}
|
||||
}
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (webview.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn)) || this._editorGroupService.getGroup(webview.group || 0);
|
||||
if (targetGroup) {
|
||||
this._webviewService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public async $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise<boolean> {
|
||||
if (typeof handle === 'number') {
|
||||
this.getWebviewElement(handle).sendMessage(message);
|
||||
return true;
|
||||
} else {
|
||||
const webview = this.getWebview(handle);
|
||||
const editors = this._editorService.visibleControls
|
||||
.filter(e => e instanceof WebviewEditor)
|
||||
.map(e => e as WebviewEditor)
|
||||
.filter(e => e.input!.matches(webview));
|
||||
|
||||
if (editors.length > 0) {
|
||||
editors[0].sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (webview.webview) {
|
||||
webview.webview.sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public $registerSerializer(viewType: string): void {
|
||||
if (this._revivers.has(viewType)) {
|
||||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._revivers.set(viewType, this._webviewService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
return webview.state && webview.state.viewType === viewType;
|
||||
},
|
||||
reviveWebview: async (webview): Promise<void> => {
|
||||
const viewType = webview.state.viewType;
|
||||
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
|
||||
this._webviews.set(handle, webview);
|
||||
webview._events = this.createWebviewEventDelegate(handle);
|
||||
let state = undefined;
|
||||
if (webview.state.state) {
|
||||
try {
|
||||
state = JSON.parse(webview.state.state);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this._proxy.$deserializeWebviewPanel(handle, viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group || 0), webview.options);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public $unregisterSerializer(viewType: string): void {
|
||||
const reviver = this._revivers.get(viewType);
|
||||
if (!reviver) {
|
||||
throw new Error(`No reviver for ${viewType} registered`);
|
||||
}
|
||||
|
||||
reviver.dispose();
|
||||
this._revivers.delete(viewType);
|
||||
}
|
||||
|
||||
private getInternalWebviewId(viewType: string): string {
|
||||
return `mainThreadWebview-${viewType}`;
|
||||
}
|
||||
|
||||
private _onBeforeShutdown(): boolean {
|
||||
this._webviews.forEach((webview) => {
|
||||
if (!webview.isDisposed() && webview.state && this._revivers.has(webview.state.viewType)) {
|
||||
webview.state.state = webview.webviewState;
|
||||
}
|
||||
});
|
||||
return false; // Don't veto shutdown
|
||||
}
|
||||
|
||||
private createWebviewEventDelegate(handle: WebviewPanelHandle) {
|
||||
return {
|
||||
onDidClickLink: (uri: URI) => this.onDidClickLink(handle, uri),
|
||||
onMessage: (message: any) => this._proxy.$onMessage(handle, message),
|
||||
onDispose: () => {
|
||||
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
|
||||
this._webviews.delete(handle);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private onActiveEditorChanged() {
|
||||
const activeEditor = this._editorService.activeControl;
|
||||
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined;
|
||||
if (activeEditor && activeEditor.input instanceof WebviewEditorInput) {
|
||||
for (const handle of map.keys(this._webviews)) {
|
||||
const input = this._webviews.get(handle)!;
|
||||
if (input.matches(activeEditor.input)) {
|
||||
newActiveWebview = { input, handle };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newActiveWebview && newActiveWebview.handle === this._activeWebview) {
|
||||
// Webview itself unchanged but position may have changed
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group || 0)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Broadcast view state update for currently active
|
||||
if (typeof this._activeWebview !== 'undefined') {
|
||||
const oldActiveWebview = this._webviews.get(this._activeWebview);
|
||||
if (oldActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, {
|
||||
active: false,
|
||||
visible: this._editorService.visibleControls.some(editor => !!editor.input && editor.input.matches(oldActiveWebview)),
|
||||
position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group || 0),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Then for newly active
|
||||
if (newActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, activeEditor ? activeEditor.group : ACTIVE_GROUP),
|
||||
});
|
||||
this._activeWebview = newActiveWebview.handle;
|
||||
} else {
|
||||
this._activeWebview = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onVisibleEditorsChanged(): void {
|
||||
this._webviews.forEach((input, handle) => {
|
||||
for (const workbenchEditor of this._editorService.visibleControls) {
|
||||
if (workbenchEditor.input && workbenchEditor.input.matches(input)) {
|
||||
const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group!);
|
||||
|
||||
input.updateGroup(workbenchEditor.group!.id);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(handle, {
|
||||
active: handle === this._activeWebview,
|
||||
visible: true,
|
||||
position: editorPosition
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onDidClickLink(handle: WebviewPanelHandle, link: URI): void {
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
const webview = this.getWebview(handle);
|
||||
if (this.isSupportedLink(webview, link)) {
|
||||
this._openerService.open(link);
|
||||
}
|
||||
}
|
||||
|
||||
private isSupportedLink(webview: WebviewEditorInput, link: URI): boolean {
|
||||
if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) {
|
||||
return true;
|
||||
}
|
||||
return !!webview.options.enableCommandUris && link.scheme === 'command';
|
||||
}
|
||||
|
||||
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
|
||||
const webview = this._webviews.get(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
private getWebviewElement(handle: number): WebviewElement {
|
||||
const webview = this._webviewsElements.get(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
private static getDeserializationFailedContents(viewType: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="https://code.visualstudio.com/raw/">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: data:; media-src https:; script-src 'none'; style-src vscode-core-resource: https: 'unsafe-inline'; child-src 'none'; frame-src 'none';">
|
||||
</head>
|
||||
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)}</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function reviveWebviewOptions(options: WebviewInputOptions): WebviewInputOptions {
|
||||
return {
|
||||
...options,
|
||||
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(r => URI.revive(r)) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents } | undefined
|
||||
): { light: URI, dark: URI } | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
light: URI.revive(value.light),
|
||||
dark: URI.revive(value.dark)
|
||||
};
|
||||
}
|
|
@ -67,6 +67,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
|||
import { values } from 'vs/base/common/collections';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
|
@ -109,6 +110,7 @@ export function createApiFactory(
|
|||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors));
|
||||
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics, extHostLogService));
|
||||
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
|
||||
|
@ -306,10 +308,6 @@ export function createApiFactory(
|
|||
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
registerCodeInsetProvider(selector: vscode.DocumentSelector, provider: vscode.CodeInsetProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguageFeatures.registerCodeInsetProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
|
@ -486,6 +484,10 @@ export function createApiFactory(
|
|||
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
|
||||
return extHostWebviews.createWebviewPanel(extension, viewType, title, showOptions, options);
|
||||
},
|
||||
createWebviewTextEditorInset(editor: vscode.TextEditor, range: vscode.Range, options: vscode.WebviewOptions): vscode.WebviewEditorInset {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostEditorInsets.createWebviewEditorInset(editor, range, options);
|
||||
},
|
||||
createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
|
||||
if (typeof nameOrOptions === 'object') {
|
||||
nameOrOptions.runInBackground = nameOrOptions.runInBackground && extension.enableProposedApi;
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
|
||||
import { ProviderResult } from 'vs/editor/common/modes';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
||||
export interface ICodeInsetSymbol {
|
||||
id: string;
|
||||
range: IRange;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export interface CodeInsetProvider {
|
||||
onDidChange?: Event<this>;
|
||||
provideCodeInsets(model: ITextModel, token: CancellationToken): ProviderResult<ICodeInsetSymbol[]>;
|
||||
resolveCodeInset(model: ITextModel, codeInset: ICodeInsetSymbol, token: CancellationToken): ProviderResult<ICodeInsetSymbol>;
|
||||
}
|
||||
|
||||
export const CodeInsetProviderRegistry = new LanguageFeatureRegistry<CodeInsetProvider>();
|
||||
|
||||
export interface ICodeInsetData {
|
||||
symbol: ICodeInsetSymbol;
|
||||
provider: CodeInsetProvider;
|
||||
resolved?: boolean;
|
||||
}
|
||||
|
||||
export function getCodeInsetData(model: ITextModel, token: CancellationToken): Promise<ICodeInsetData[]> {
|
||||
|
||||
const symbols: ICodeInsetData[] = [];
|
||||
const providers = CodeInsetProviderRegistry.ordered(model);
|
||||
|
||||
const promises = providers.map(provider =>
|
||||
Promise.resolve(provider.provideCodeInsets(model, token)).then(result => {
|
||||
if (Array.isArray(result)) {
|
||||
for (let symbol of result) {
|
||||
symbols.push({ symbol, provider });
|
||||
}
|
||||
}
|
||||
}).catch(onUnexpectedExternalError));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
|
||||
return mergeSort(symbols, (a, b) => {
|
||||
// sort by lineNumber, provider-rank, and column
|
||||
if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) {
|
||||
return -1;
|
||||
} else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) {
|
||||
return 1;
|
||||
} else if (providers.indexOf(a.provider) < providers.indexOf(b.provider)) {
|
||||
return -1;
|
||||
} else if (providers.indexOf(a.provider) > providers.indexOf(b.provider)) {
|
||||
return 1;
|
||||
} else if (a.symbol.range.startColumn < b.symbol.range.startColumn) {
|
||||
return -1;
|
||||
} else if (a.symbol.range.startColumn > b.symbol.range.startColumn) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,353 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { StableEditorScrollState } from 'vs/editor/browser/core/editorState';
|
||||
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
import { CodeInsetProviderRegistry, getCodeInsetData, ICodeInsetData } from '../common/codeInset';
|
||||
import { CodeInsetWidget, CodeInsetHelper } from './codeInsetWidget';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
|
||||
// import { localize } from 'vs/nls';
|
||||
|
||||
export class CodeInsetController implements editorCommon.IEditorContribution {
|
||||
|
||||
static get(editor: editorBrowser.ICodeEditor): CodeInsetController {
|
||||
return editor.getContribution(CodeInsetController.ID);
|
||||
}
|
||||
|
||||
private static readonly ID: string = 'css.editor.codeInset';
|
||||
|
||||
private _isEnabled: boolean;
|
||||
|
||||
private _globalToDispose: IDisposable[];
|
||||
private _localToDispose: IDisposable[];
|
||||
private _insetWidgets: CodeInsetWidget[];
|
||||
private _pendingWebviews = new Map<string, (element: WebviewElement) => any>();
|
||||
private _currentFindCodeInsetSymbolsPromise: CancelablePromise<ICodeInsetData[]> | null;
|
||||
private _modelChangeCounter: number;
|
||||
private _currentResolveCodeInsetSymbolsPromise: CancelablePromise<any> | null;
|
||||
private _detectVisibleInsets: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
private _editor: editorBrowser.ICodeEditor,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
) {
|
||||
this._isEnabled = this._configService.getValue<boolean>('editor.codeInsets');
|
||||
|
||||
this._globalToDispose = [];
|
||||
this._localToDispose = [];
|
||||
this._insetWidgets = [];
|
||||
this._currentFindCodeInsetSymbolsPromise = null;
|
||||
this._modelChangeCounter = 0;
|
||||
|
||||
this._globalToDispose.push(this._editor.onDidChangeModel(() => this._onModelChange()));
|
||||
this._globalToDispose.push(this._editor.onDidChangeModelLanguage(() => this._onModelChange()));
|
||||
this._globalToDispose.push(this._configService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('editor.codeInsets')) {
|
||||
let prevIsEnabled = this._isEnabled;
|
||||
this._isEnabled = this._configService.getValue<boolean>('editor.codeInsets');
|
||||
if (prevIsEnabled !== this._isEnabled) {
|
||||
this._onModelChange();
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._globalToDispose.push(CodeInsetProviderRegistry.onDidChange(this._onModelChange, this));
|
||||
this._onModelChange();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._localDispose();
|
||||
this._globalToDispose = dispose(this._globalToDispose);
|
||||
}
|
||||
|
||||
acceptWebview(symbolId: string, webviewElement: WebviewElement): boolean {
|
||||
const pendingWebview = this._pendingWebviews.get(symbolId);
|
||||
if (pendingWebview) {
|
||||
pendingWebview(webviewElement);
|
||||
this._pendingWebviews.delete(symbolId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _localDispose(): void {
|
||||
if (this._currentFindCodeInsetSymbolsPromise) {
|
||||
this._currentFindCodeInsetSymbolsPromise.cancel();
|
||||
this._currentFindCodeInsetSymbolsPromise = null;
|
||||
this._modelChangeCounter++;
|
||||
}
|
||||
if (this._currentResolveCodeInsetSymbolsPromise) {
|
||||
this._currentResolveCodeInsetSymbolsPromise.cancel();
|
||||
this._currentResolveCodeInsetSymbolsPromise = null;
|
||||
}
|
||||
this._localToDispose = dispose(this._localToDispose);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return CodeInsetController.ID;
|
||||
}
|
||||
|
||||
private _onModelChange(): void {
|
||||
this._localDispose();
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (!model || !this._isEnabled || !CodeInsetProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const provider of CodeInsetProviderRegistry.all(model)) {
|
||||
if (typeof provider.onDidChange === 'function') {
|
||||
let registration = provider.onDidChange(() => scheduler.schedule());
|
||||
this._localToDispose.push(registration);
|
||||
}
|
||||
}
|
||||
|
||||
this._detectVisibleInsets = new RunOnceScheduler(() => {
|
||||
this._onViewportChanged();
|
||||
}, 500);
|
||||
|
||||
const scheduler = new RunOnceScheduler(() => {
|
||||
const counterValue = ++this._modelChangeCounter;
|
||||
if (this._currentFindCodeInsetSymbolsPromise) {
|
||||
this._currentFindCodeInsetSymbolsPromise.cancel();
|
||||
}
|
||||
|
||||
this._currentFindCodeInsetSymbolsPromise = createCancelablePromise(token => getCodeInsetData(model, token));
|
||||
|
||||
this._currentFindCodeInsetSymbolsPromise.then(codeInsetData => {
|
||||
if (counterValue === this._modelChangeCounter) { // only the last one wins
|
||||
this._renderCodeInsetSymbols(codeInsetData);
|
||||
this._detectVisibleInsets.schedule();
|
||||
}
|
||||
}, onUnexpectedError);
|
||||
}, 250);
|
||||
|
||||
this._localToDispose.push(scheduler);
|
||||
|
||||
this._localToDispose.push(this._detectVisibleInsets);
|
||||
|
||||
this._localToDispose.push(this._editor.onDidChangeModelContent(() => {
|
||||
this._editor.changeDecorations(changeAccessor => {
|
||||
this._editor.changeViewZones(viewAccessor => {
|
||||
let toDispose: CodeInsetWidget[] = [];
|
||||
let lastInsetLineNumber: number = -1;
|
||||
this._insetWidgets.forEach(inset => {
|
||||
if (!inset.isValid() || lastInsetLineNumber === inset.getLineNumber()) {
|
||||
// invalid -> Inset collapsed, attach range doesn't exist anymore
|
||||
// line_number -> insets should never be on the same line
|
||||
toDispose.push(inset);
|
||||
}
|
||||
else {
|
||||
inset.reposition(viewAccessor);
|
||||
lastInsetLineNumber = inset.getLineNumber();
|
||||
}
|
||||
});
|
||||
let helper = new CodeInsetHelper();
|
||||
toDispose.forEach((l) => {
|
||||
l.dispose(helper, viewAccessor);
|
||||
this._insetWidgets.splice(this._insetWidgets.indexOf(l), 1);
|
||||
});
|
||||
helper.commit(changeAccessor);
|
||||
});
|
||||
});
|
||||
// Compute new `visible` code insets
|
||||
this._detectVisibleInsets.schedule();
|
||||
// Ask for all references again
|
||||
scheduler.schedule();
|
||||
}));
|
||||
|
||||
this._localToDispose.push(this._editor.onDidScrollChange(e => {
|
||||
if (e.scrollTopChanged && this._insetWidgets.length > 0) {
|
||||
this._detectVisibleInsets.schedule();
|
||||
}
|
||||
}));
|
||||
|
||||
this._localToDispose.push(this._editor.onDidLayoutChange(() => {
|
||||
this._detectVisibleInsets.schedule();
|
||||
}));
|
||||
|
||||
this._localToDispose.push(toDisposable(() => {
|
||||
if (this._editor.getModel()) {
|
||||
const scrollState = StableEditorScrollState.capture(this._editor);
|
||||
this._editor.changeDecorations((changeAccessor) => {
|
||||
this._editor.changeViewZones((accessor) => {
|
||||
this._disposeAllInsets(changeAccessor, accessor);
|
||||
});
|
||||
});
|
||||
scrollState.restore(this._editor);
|
||||
} else {
|
||||
// No accessors available
|
||||
this._disposeAllInsets(null, null);
|
||||
}
|
||||
}));
|
||||
|
||||
scheduler.schedule();
|
||||
}
|
||||
|
||||
private _disposeAllInsets(decChangeAccessor: IModelDecorationsChangeAccessor | null, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void {
|
||||
let helper = new CodeInsetHelper();
|
||||
this._insetWidgets.forEach((Inset) => Inset.dispose(helper, viewZoneChangeAccessor));
|
||||
if (decChangeAccessor) {
|
||||
helper.commit(decChangeAccessor);
|
||||
}
|
||||
this._insetWidgets = [];
|
||||
}
|
||||
|
||||
private _renderCodeInsetSymbols(symbols: ICodeInsetData[]): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let maxLineNumber = this._editor.getModel().getLineCount();
|
||||
let groups: ICodeInsetData[][] = [];
|
||||
let lastGroup: ICodeInsetData[] | undefined;
|
||||
|
||||
for (let symbol of symbols) {
|
||||
let line = symbol.symbol.range.startLineNumber;
|
||||
if (line < 1 || line > maxLineNumber) {
|
||||
// invalid code Inset
|
||||
continue;
|
||||
} else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) {
|
||||
// on same line as previous
|
||||
lastGroup.push(symbol);
|
||||
} else {
|
||||
// on later line as previous
|
||||
lastGroup = [symbol];
|
||||
groups.push(lastGroup);
|
||||
}
|
||||
}
|
||||
|
||||
const scrollState = StableEditorScrollState.capture(this._editor);
|
||||
|
||||
this._editor.changeDecorations(changeAccessor => {
|
||||
this._editor.changeViewZones(accessor => {
|
||||
|
||||
let codeInsetIndex = 0, groupsIndex = 0, helper = new CodeInsetHelper();
|
||||
|
||||
while (groupsIndex < groups.length && codeInsetIndex < this._insetWidgets.length) {
|
||||
|
||||
let symbolsLineNumber = groups[groupsIndex][0].symbol.range.startLineNumber;
|
||||
let codeInsetLineNumber = this._insetWidgets[codeInsetIndex].getLineNumber();
|
||||
|
||||
if (codeInsetLineNumber < symbolsLineNumber) {
|
||||
this._insetWidgets[codeInsetIndex].dispose(helper, accessor);
|
||||
this._insetWidgets.splice(codeInsetIndex, 1);
|
||||
} else if (codeInsetLineNumber === symbolsLineNumber) {
|
||||
this._insetWidgets[codeInsetIndex].updateCodeInsetSymbols(groups[groupsIndex], helper);
|
||||
groupsIndex++;
|
||||
codeInsetIndex++;
|
||||
} else {
|
||||
this._insetWidgets.splice(
|
||||
codeInsetIndex,
|
||||
0,
|
||||
new CodeInsetWidget(groups[groupsIndex], this._editor, helper)
|
||||
);
|
||||
codeInsetIndex++;
|
||||
groupsIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete extra code insets
|
||||
while (codeInsetIndex < this._insetWidgets.length) {
|
||||
this._insetWidgets[codeInsetIndex].dispose(helper, accessor);
|
||||
this._insetWidgets.splice(codeInsetIndex, 1);
|
||||
}
|
||||
|
||||
// Create extra symbols
|
||||
while (groupsIndex < groups.length) {
|
||||
this._insetWidgets.push(new CodeInsetWidget(
|
||||
groups[groupsIndex],
|
||||
this._editor, helper
|
||||
));
|
||||
groupsIndex++;
|
||||
}
|
||||
|
||||
helper.commit(changeAccessor);
|
||||
});
|
||||
});
|
||||
|
||||
scrollState.restore(this._editor);
|
||||
}
|
||||
|
||||
private _onViewportChanged(): void {
|
||||
if (this._currentResolveCodeInsetSymbolsPromise) {
|
||||
this._currentResolveCodeInsetSymbolsPromise.cancel();
|
||||
this._currentResolveCodeInsetSymbolsPromise = null;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allWidgetRequests: ICodeInsetData[][] = [];
|
||||
const insetWidgets: CodeInsetWidget[] = [];
|
||||
this._insetWidgets.forEach(inset => {
|
||||
const widgetRequests = inset.computeIfNecessary(model);
|
||||
if (widgetRequests) {
|
||||
allWidgetRequests.push(widgetRequests);
|
||||
insetWidgets.push(inset);
|
||||
}
|
||||
});
|
||||
|
||||
if (allWidgetRequests.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentResolveCodeInsetSymbolsPromise = createCancelablePromise(token => {
|
||||
|
||||
const allPromises = allWidgetRequests.map((widgetRequests, r) => {
|
||||
|
||||
const widgetPromises = widgetRequests.map(request => {
|
||||
if (request.resolved) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
let a = new Promise(resolve => {
|
||||
this._pendingWebviews.set(request.symbol.id, element => {
|
||||
request.resolved = true;
|
||||
insetWidgets[r].adoptWebview(element);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
let b = request.provider.resolveCodeInset(model, request.symbol, token);
|
||||
return Promise.all([a, b]);
|
||||
});
|
||||
|
||||
return Promise.all(widgetPromises);
|
||||
});
|
||||
|
||||
return Promise.all(allPromises);
|
||||
});
|
||||
|
||||
this._currentResolveCodeInsetSymbolsPromise.then(() => {
|
||||
this._currentResolveCodeInsetSymbolsPromise = null;
|
||||
}).catch(err => {
|
||||
this._currentResolveCodeInsetSymbolsPromise = null;
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(CodeInsetController);
|
||||
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
|
||||
id: 'editor',
|
||||
properties: {
|
||||
// ['editor.codeInsets']: {
|
||||
// description: localize('editor.codeInsets', "Enable/disable editor code insets"),
|
||||
// type: 'boolean',
|
||||
// default: false
|
||||
// }
|
||||
}
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .code-inset {
|
||||
z-index: 10;
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./codeInsetWidget';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeInsetData } from '../common/codeInset';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { IModelDeltaDecoration, IModelDecorationsChangeAccessor, ITextModel } from 'vs/editor/common/model';
|
||||
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
|
||||
|
||||
|
||||
export interface IDecorationIdCallback {
|
||||
(decorationId: string): void;
|
||||
}
|
||||
|
||||
export class CodeInsetHelper {
|
||||
|
||||
private _removeDecorations: string[];
|
||||
private _addDecorations: IModelDeltaDecoration[];
|
||||
private _addDecorationsCallbacks: IDecorationIdCallback[];
|
||||
|
||||
constructor() {
|
||||
this._removeDecorations = [];
|
||||
this._addDecorations = [];
|
||||
this._addDecorationsCallbacks = [];
|
||||
}
|
||||
|
||||
addDecoration(decoration: IModelDeltaDecoration, callback: IDecorationIdCallback): void {
|
||||
this._addDecorations.push(decoration);
|
||||
this._addDecorationsCallbacks.push(callback);
|
||||
}
|
||||
|
||||
removeDecoration(decorationId: string): void {
|
||||
this._removeDecorations.push(decorationId);
|
||||
}
|
||||
|
||||
commit(changeAccessor: IModelDecorationsChangeAccessor): void {
|
||||
let resultingDecorations = changeAccessor.deltaDecorations(this._removeDecorations, this._addDecorations);
|
||||
for (let i = 0, len = resultingDecorations.length; i < len; i++) {
|
||||
this._addDecorationsCallbacks[i](resultingDecorations[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeInsetWidget {
|
||||
|
||||
private readonly _editor: editorBrowser.ICodeEditor;
|
||||
private _webview: WebviewElement;
|
||||
private _viewZone: editorBrowser.IViewZone;
|
||||
private _viewZoneId?: number = undefined;
|
||||
private _decorationIds: string[];
|
||||
private _data: ICodeInsetData[];
|
||||
private _range: Range;
|
||||
|
||||
constructor(
|
||||
data: ICodeInsetData[], // all the insets on the same line (often just one)
|
||||
editor: editorBrowser.ICodeEditor,
|
||||
helper: CodeInsetHelper
|
||||
) {
|
||||
this._editor = editor;
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
|
||||
this._data.forEach((codeInsetData, i) => {
|
||||
|
||||
helper.addDecoration({
|
||||
range: codeInsetData.symbol.range,
|
||||
options: ModelDecorationOptions.EMPTY
|
||||
}, id => this._decorationIds[i] = id);
|
||||
|
||||
// the range contains all insets on this line
|
||||
if (!this._range) {
|
||||
this._range = Range.lift(codeInsetData.symbol.range);
|
||||
} else {
|
||||
this._range = Range.plusRange(this._range, codeInsetData.symbol.range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(helper: CodeInsetHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void {
|
||||
while (this._decorationIds.length) {
|
||||
const decoration = this._decorationIds.pop();
|
||||
if (decoration) {
|
||||
helper.removeDecoration(decoration);
|
||||
}
|
||||
}
|
||||
if (viewZoneChangeAccessor) {
|
||||
if (typeof this._viewZoneId !== 'undefined') {
|
||||
viewZoneChangeAccessor.removeZone(this._viewZoneId);
|
||||
}
|
||||
this._viewZone = undefined!;
|
||||
}
|
||||
if (this._webview) {
|
||||
this._webview.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this._editor.hasModel() && this._decorationIds.some((id, i) => {
|
||||
const range = this._editor.getModel()!.getDecorationRange(id);
|
||||
const symbol = this._data[i].symbol;
|
||||
return !!range && Range.isEmpty(symbol.range) === range.isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
public updateCodeInsetSymbols(data: ICodeInsetData[], helper: CodeInsetHelper): void {
|
||||
while (this._decorationIds.length) {
|
||||
const decoration = this._decorationIds.pop();
|
||||
if (decoration) {
|
||||
helper.removeDecoration(decoration);
|
||||
}
|
||||
}
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
this._data.forEach((codeInsetData, i) => {
|
||||
helper.addDecoration({
|
||||
range: codeInsetData.symbol.range,
|
||||
options: ModelDecorationOptions.EMPTY
|
||||
}, id => this._decorationIds[i] = id);
|
||||
});
|
||||
}
|
||||
|
||||
public computeIfNecessary(model: ITextModel): ICodeInsetData[] {
|
||||
// Read editor current state
|
||||
for (let i = 0; i < this._decorationIds.length; i++) {
|
||||
const range = model.getDecorationRange(this._decorationIds[i]);
|
||||
if (range) {
|
||||
this._data[i].symbol.range = range;
|
||||
}
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public getLineNumber(): number {
|
||||
if (this._editor.hasModel()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
return range.startLineNumber;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public adoptWebview(webview: WebviewElement): void {
|
||||
|
||||
const lineNumber = this._range.endLineNumber;
|
||||
this._editor.changeViewZones(accessor => {
|
||||
|
||||
if (this._viewZoneId) {
|
||||
accessor.removeZone(this._viewZoneId);
|
||||
this._webview.dispose();
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'code-inset';
|
||||
webview.mountTo(div);
|
||||
webview.onMessage((e: { type: string, payload: any }) => {
|
||||
// The webview contents can use a "size-info" message to report its size.
|
||||
if (e && e.type === 'size-info') {
|
||||
const margin = e.payload.height > 0 ? 5 : 0;
|
||||
this._viewZone.heightInPx = e.payload.height + margin;
|
||||
this._editor.changeViewZones(accessor => {
|
||||
if (this._viewZoneId) {
|
||||
accessor.layoutZone(this._viewZoneId);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this._viewZone = {
|
||||
afterLineNumber: lineNumber,
|
||||
heightInPx: 50,
|
||||
domNode: div
|
||||
};
|
||||
this._viewZoneId = accessor.addZone(this._viewZone);
|
||||
this._webview = webview;
|
||||
});
|
||||
}
|
||||
|
||||
public reposition(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
if (this.isValid() && this._editor.hasModel()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
this._viewZone.afterLineNumber = range.endLineNumber;
|
||||
}
|
||||
if (this._viewZoneId) {
|
||||
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -327,4 +327,4 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
|
|||
}
|
||||
return super.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { MainThreadWebviews } from 'vs/workbench/api/electron-browser/mainThreadWebview';
|
||||
import { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview';
|
||||
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
||||
import { mock } from 'vs/workbench/test/electron-browser/api/mock';
|
||||
import * as vscode from 'vscode';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import 'vs/editor/editor.all';
|
||||
|
||||
import 'vs/workbench/api/electron-browser/extensionHost.contribution';
|
||||
import 'vs/workbench/api/browser/extensionHost.contribution';
|
||||
|
||||
import 'vs/workbench/electron-browser/main.contribution';
|
||||
import 'vs/workbench/browser/workbench.contribution';
|
||||
|
@ -324,9 +324,6 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
|
|||
// Experiments
|
||||
import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution';
|
||||
|
||||
// Code Insets
|
||||
import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution';
|
||||
|
||||
// Issues
|
||||
import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
|
||||
|
||||
|
|
|
@ -322,9 +322,6 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
|
|||
// Experiments
|
||||
// import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution';
|
||||
|
||||
// Code Insets
|
||||
// import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution';
|
||||
|
||||
// Issues
|
||||
// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
|
||||
|
||||
|
|
Loading…
Reference in a new issue