mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 09:35:37 -05:00
Merge from vscode cfc1ab4c5f816765b91fb7ead3c3427a7c8581a3
This commit is contained in:
@@ -3,32 +3,38 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, IReference, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
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 { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
|
||||
import { IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { CustomEditorInput, ModelType } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
|
||||
import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
|
||||
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
||||
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel';
|
||||
import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { extHostNamedCustomer } from '../common/extHostCustomers';
|
||||
|
||||
/**
|
||||
@@ -74,12 +80,17 @@ class WebviewViewTypeTransformer {
|
||||
}
|
||||
|
||||
public toExternal(viewType: string): string | undefined {
|
||||
return startsWith(viewType, this.prefix)
|
||||
return viewType.startsWith(this.prefix)
|
||||
? viewType.substr(this.prefix.length)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const enum ModelType {
|
||||
Custom,
|
||||
Text,
|
||||
}
|
||||
|
||||
const webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-');
|
||||
|
||||
@extHostNamedCustomer(extHostProtocol.MainContext.MainThreadWebviews)
|
||||
@@ -97,7 +108,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
private readonly _webviewInputs = new WebviewInputStore();
|
||||
private readonly _revivers = new Map<string, IDisposable>();
|
||||
private readonly _editorProviders = new Map<string, IDisposable>();
|
||||
private readonly _customEditorModels = new Map<string, { referenceCount: number }>();
|
||||
private readonly _webviewFromDiffEditorHandles = new Set<string>();
|
||||
|
||||
constructor(
|
||||
context: extHostProtocol.IExtHostContext,
|
||||
@@ -109,13 +120,24 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews);
|
||||
this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this));
|
||||
this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this));
|
||||
|
||||
this._register(_editorService.onDidActiveEditorChange(() => {
|
||||
const activeInput = this._editorService.activeEditor;
|
||||
if (activeInput instanceof DiffEditorInput && activeInput.master instanceof WebviewInput && activeInput.details instanceof WebviewInput) {
|
||||
this.registerWebviewFromDiffEditorListeners(activeInput);
|
||||
}
|
||||
|
||||
this.updateWebviewViewStates(activeInput);
|
||||
}));
|
||||
|
||||
this._register(_editorService.onDidVisibleEditorsChange(() => {
|
||||
this.updateWebviewViewStates(this._editorService.activeEditor);
|
||||
}));
|
||||
|
||||
// This reviver's only job is to activate webview panel extensions
|
||||
// This should trigger the real reviver to be registered from the extension host side.
|
||||
@@ -264,7 +286,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options);
|
||||
}
|
||||
|
||||
public registerEditorProvider(
|
||||
private registerEditorProvider(
|
||||
modelType: ModelType,
|
||||
extensionData: extHostProtocol.WebviewExtensionDescription,
|
||||
viewType: string,
|
||||
@@ -287,16 +309,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
|
||||
webviewInput.webview.options = options;
|
||||
webviewInput.webview.extension = extension;
|
||||
webviewInput.modelType = modelType;
|
||||
|
||||
const resource = webviewInput.resource;
|
||||
|
||||
if (modelType === ModelType.Custom) {
|
||||
const model = await this.retainCustomEditorModel(webviewInput, resource, viewType);
|
||||
webviewInput.onDisposeWebview(() => {
|
||||
this.releaseCustomEditorModel(model);
|
||||
});
|
||||
}
|
||||
const modelRef = await this.getOrCreateCustomEditorModel(modelType, webviewInput, resource, viewType);
|
||||
webviewInput.webview.onDispose(() => {
|
||||
modelRef.dispose();
|
||||
});
|
||||
|
||||
try {
|
||||
await this._proxy.$resolveWebviewEditor(
|
||||
@@ -328,71 +347,27 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
this._customEditorService.models.disposeAllModelsForView(viewType);
|
||||
}
|
||||
|
||||
private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string) {
|
||||
const model = await this._customEditorService.models.resolve(webviewInput.resource, webviewInput.viewType);
|
||||
|
||||
const key = viewType + resource.toString();
|
||||
const existingEntry = this._customEditorModels.get(key);
|
||||
if (existingEntry) {
|
||||
++existingEntry.referenceCount;
|
||||
// no need to hook up listeners again
|
||||
return model;
|
||||
}
|
||||
this._customEditorModels.set(key, { referenceCount: 1 });
|
||||
const { editable } = await this._proxy.$createWebviewCustomEditorDocument(resource, viewType);
|
||||
|
||||
if (editable) {
|
||||
model.onUndo(() => {
|
||||
this._proxy.$undo(resource, viewType);
|
||||
});
|
||||
|
||||
model.onRedo(() => {
|
||||
this._proxy.$redo(resource, viewType);
|
||||
});
|
||||
|
||||
model.onWillSave(e => {
|
||||
e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType));
|
||||
});
|
||||
private async getOrCreateCustomEditorModel(
|
||||
modelType: ModelType,
|
||||
webviewInput: WebviewInput,
|
||||
resource: URI,
|
||||
viewType: string,
|
||||
): Promise<IReference<ICustomEditorModel>> {
|
||||
const existingModel = this._customEditorService.models.tryRetain(webviewInput.resource, webviewInput.viewType);
|
||||
if (existingModel) {
|
||||
return existingModel;
|
||||
}
|
||||
|
||||
// Save as should always be implemented even if the model is readonly
|
||||
model.onWillSaveAs(e => {
|
||||
if (editable) {
|
||||
e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON()));
|
||||
} else {
|
||||
// Since the editor is readonly, just copy the file over
|
||||
e.waitUntil(this._fileService.copy(e.resource, e.targetResource, false /* overwrite */));
|
||||
}
|
||||
});
|
||||
const model = modelType === ModelType.Text
|
||||
? CustomTextEditorModel.create(this._instantiationService, viewType, resource)
|
||||
: MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource);
|
||||
|
||||
model.onBackup(() => {
|
||||
return createCancelablePromise(token =>
|
||||
this._proxy.$backup(model.resource.toJSON(), viewType, token));
|
||||
});
|
||||
|
||||
return model;
|
||||
return this._customEditorService.models.add(resource, viewType, model);
|
||||
}
|
||||
|
||||
private async releaseCustomEditorModel(model: ICustomEditorModel) {
|
||||
const key = model.viewType + model.resource;
|
||||
const entry = this._customEditorModels.get(key);
|
||||
if (!entry) {
|
||||
throw new Error('Model not found');
|
||||
}
|
||||
|
||||
--entry.referenceCount;
|
||||
if (entry.referenceCount <= 0) {
|
||||
this._proxy.$disposeWebviewCustomEditorDocument(model.resource, model.viewType);
|
||||
this._customEditorService.models.disposeModel(model);
|
||||
this._customEditorModels.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) {
|
||||
const model = this._customEditorService.models.get(URI.revive(resource), viewType);
|
||||
if (!model) {
|
||||
public async $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) {
|
||||
const model = await this._customEditorService.models.get(URI.revive(resource), viewType);
|
||||
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
|
||||
throw new Error('Could not find model for webview editor');
|
||||
}
|
||||
model.setDirty(state.dirty);
|
||||
@@ -408,19 +383,39 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
input.onDispose(() => {
|
||||
disposables.dispose();
|
||||
});
|
||||
input.onDisposeWebview(() => {
|
||||
input.webview.onDispose(() => {
|
||||
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
|
||||
this._webviewInputs.delete(handle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private updateWebviewViewStates() {
|
||||
private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void {
|
||||
const master = diffEditorInput.master as WebviewInput;
|
||||
const details = diffEditorInput.details as WebviewInput;
|
||||
|
||||
if (this._webviewFromDiffEditorHandles.has(master.id) || this._webviewFromDiffEditorHandles.has(details.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._webviewFromDiffEditorHandles.add(master.id);
|
||||
this._webviewFromDiffEditorHandles.add(details.id);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(master.webview.onDidFocus(() => this.updateWebviewViewStates(master)));
|
||||
disposables.add(details.webview.onDidFocus(() => this.updateWebviewViewStates(details)));
|
||||
disposables.add(diffEditorInput.onDispose(() => {
|
||||
this._webviewFromDiffEditorHandles.delete(master.id);
|
||||
this._webviewFromDiffEditorHandles.delete(details.id);
|
||||
dispose(disposables);
|
||||
}));
|
||||
}
|
||||
|
||||
private updateWebviewViewStates(activeEditorInput: IEditorInput | undefined) {
|
||||
if (!this._webviewInputs.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeInput = this._editorService.activeControl && this._editorService.activeControl.input;
|
||||
const viewStates: extHostProtocol.WebviewPanelViewStateData = {};
|
||||
|
||||
const updateViewStatesForInput = (group: IEditorGroup, topLevelInput: IEditorInput, editorInput: IEditorInput) => {
|
||||
@@ -434,7 +429,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
if (handle) {
|
||||
viewStates[handle] = {
|
||||
visible: topLevelInput === group.activeEditor,
|
||||
active: topLevelInput === activeInput,
|
||||
active: editorInput === activeEditorInput,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, group.id),
|
||||
};
|
||||
}
|
||||
@@ -476,7 +471,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput {
|
||||
const webview = this.tryGetWebviewInput(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
throw new Error(`Unknown webview handle:${handle}`);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
@@ -492,7 +487,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none';">
|
||||
</head>
|
||||
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)}</body>
|
||||
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", escape(viewType))}</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
@@ -511,13 +506,179 @@ function reviveWebviewOptions(options: modes.IWebviewOptions): WebviewInputOptio
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents; } | undefined
|
||||
): { light: URI, dark: URI; } | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
): WebviewIcons | undefined {
|
||||
return value
|
||||
? { light: URI.revive(value.light), dark: URI.revive(value.dark) }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
namespace HotExitState {
|
||||
export const enum Type {
|
||||
Allowed,
|
||||
NotAllowed,
|
||||
Pending,
|
||||
}
|
||||
|
||||
return {
|
||||
light: URI.revive(value.light),
|
||||
dark: URI.revive(value.dark)
|
||||
};
|
||||
export const Allowed = Object.freeze({ type: Type.Allowed } as const);
|
||||
export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const);
|
||||
|
||||
export class Pending {
|
||||
readonly type = Type.Pending;
|
||||
|
||||
constructor(
|
||||
public readonly operation: CancelablePromise<void>,
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State = typeof Allowed | typeof NotAllowed | Pending;
|
||||
}
|
||||
|
||||
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
|
||||
|
||||
private _hotExitState: HotExitState.State = HotExitState.Allowed;
|
||||
private _dirty = false;
|
||||
|
||||
public static async create(
|
||||
instantiationService: IInstantiationService,
|
||||
proxy: extHostProtocol.ExtHostWebviewsShape,
|
||||
viewType: string,
|
||||
resource: URI
|
||||
) {
|
||||
const { editable } = await proxy.$createWebviewCustomEditorDocument(resource, viewType);
|
||||
return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape,
|
||||
private readonly _viewType: string,
|
||||
private readonly _resource: URI,
|
||||
private readonly _editable: boolean,
|
||||
@IWorkingCopyService workingCopyService: IWorkingCopyService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
) {
|
||||
super();
|
||||
this._register(workingCopyService.registerWorkingCopy(this));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
//#region IWorkingCopy
|
||||
|
||||
public get resource() { return this._resource; }
|
||||
|
||||
public get name() {
|
||||
return basename(this._labelService.getUriLabel(this._resource));
|
||||
}
|
||||
|
||||
public get capabilities(): WorkingCopyCapabilities {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return this._dirty;
|
||||
}
|
||||
|
||||
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDirty: Event<void> = this._onDidChangeDirty.event;
|
||||
|
||||
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
|
||||
|
||||
//#endregion
|
||||
|
||||
public get viewType() {
|
||||
return this._viewType;
|
||||
}
|
||||
|
||||
public setDirty(dirty: boolean): void {
|
||||
this._onDidChangeContent.fire();
|
||||
|
||||
if (this._dirty !== dirty) {
|
||||
this._dirty = dirty;
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
}
|
||||
|
||||
public async revert(_options?: IRevertOptions) {
|
||||
if (this._editable) {
|
||||
this._proxy.$revert(this.resource, this.viewType);
|
||||
}
|
||||
}
|
||||
|
||||
public undo() {
|
||||
if (this._editable) {
|
||||
this._proxy.$undo(this.resource, this.viewType);
|
||||
}
|
||||
}
|
||||
|
||||
public redo() {
|
||||
if (this._editable) {
|
||||
this._proxy.$redo(this.resource, this.viewType);
|
||||
}
|
||||
}
|
||||
|
||||
public async save(_options?: ISaveOptions): Promise<boolean> {
|
||||
if (!this._editable) {
|
||||
return false;
|
||||
}
|
||||
await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token));
|
||||
this.setDirty(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise<boolean> {
|
||||
if (this._editable) {
|
||||
await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource);
|
||||
this.setDirty(false);
|
||||
return true;
|
||||
} else {
|
||||
// Since the editor is readonly, just copy the file over
|
||||
await this._fileService.copy(resource, targetResource, false /* overwrite */);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async backup(): Promise<IWorkingCopyBackup> {
|
||||
const backupData: IWorkingCopyBackup = {
|
||||
meta: {
|
||||
viewType: this.viewType,
|
||||
}
|
||||
};
|
||||
|
||||
if (!this._editable) {
|
||||
return backupData;
|
||||
}
|
||||
|
||||
if (this._hotExitState.type === HotExitState.Type.Pending) {
|
||||
this._hotExitState.operation.cancel();
|
||||
}
|
||||
|
||||
const pendingState = new HotExitState.Pending(
|
||||
createCancelablePromise(token =>
|
||||
this._proxy.$backup(this.resource.toJSON(), this.viewType, token)));
|
||||
this._hotExitState = pendingState;
|
||||
|
||||
try {
|
||||
await pendingState.operation;
|
||||
// Make sure state has not changed in the meantime
|
||||
if (this._hotExitState === pendingState) {
|
||||
this._hotExitState = HotExitState.Allowed;
|
||||
}
|
||||
} catch (e) {
|
||||
// Make sure state has not changed in the meantime
|
||||
if (this._hotExitState === pendingState) {
|
||||
this._hotExitState = HotExitState.NotAllowed;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._hotExitState === HotExitState.Allowed) {
|
||||
return backupData;
|
||||
}
|
||||
|
||||
throw new Error('Cannot back up in this state');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user