166 lines
4.7 KiB
TypeScript
166 lines
4.7 KiB
TypeScript
|
/*---------------------------------------------------------------------------------------------
|
||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||
|
*--------------------------------------------------------------------------------------------*/
|
||
|
|
||
|
import * as pLimit from 'p-limit';
|
||
|
import * as path from 'path';
|
||
|
import * as vscode from 'vscode';
|
||
|
import { Disposable } from './dispose';
|
||
|
|
||
|
export namespace Testing {
|
||
|
export const abcEditorContentChangeCommand = '_abcEditor.contentChange';
|
||
|
export const abcEditorTypeCommand = '_abcEditor.type';
|
||
|
|
||
|
export interface CustomEditorContentChangeEvent {
|
||
|
readonly content: string;
|
||
|
readonly source: vscode.Uri;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class AbcTextEditorProvider implements vscode.CustomTextEditorProvider {
|
||
|
|
||
|
public static readonly viewType = 'testWebviewEditor.abc';
|
||
|
|
||
|
private activeEditor?: AbcEditor;
|
||
|
|
||
|
public constructor(
|
||
|
private readonly context: vscode.ExtensionContext,
|
||
|
) { }
|
||
|
|
||
|
public register(): vscode.Disposable {
|
||
|
const provider = vscode.window.registerCustomEditorProvider(AbcTextEditorProvider.viewType, this);
|
||
|
|
||
|
const commands: vscode.Disposable[] = [];
|
||
|
commands.push(vscode.commands.registerCommand(Testing.abcEditorTypeCommand, (content: string) => {
|
||
|
this.activeEditor?.testing_fakeInput(content);
|
||
|
}));
|
||
|
|
||
|
return vscode.Disposable.from(provider, ...commands);
|
||
|
}
|
||
|
|
||
|
public async resolveCustomTextEditor(document: vscode.TextDocument, panel: vscode.WebviewPanel) {
|
||
|
const editor = new AbcEditor(document, this.context.extensionPath, panel);
|
||
|
|
||
|
this.activeEditor = editor;
|
||
|
|
||
|
panel.onDidChangeViewState(({ webviewPanel }) => {
|
||
|
if (this.activeEditor === editor && !webviewPanel.active) {
|
||
|
this.activeEditor = undefined;
|
||
|
}
|
||
|
if (webviewPanel.active) {
|
||
|
this.activeEditor = editor;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class AbcEditor extends Disposable {
|
||
|
|
||
|
public readonly _onDispose = this._register(new vscode.EventEmitter<void>());
|
||
|
public readonly onDispose = this._onDispose.event;
|
||
|
|
||
|
private readonly limit = pLimit(1);
|
||
|
private syncedVersion: number = -1;
|
||
|
private currentWorkspaceEdit?: Thenable<void>;
|
||
|
|
||
|
constructor(
|
||
|
private readonly document: vscode.TextDocument,
|
||
|
private readonly _extensionPath: string,
|
||
|
private readonly panel: vscode.WebviewPanel,
|
||
|
) {
|
||
|
super();
|
||
|
|
||
|
panel.webview.options = {
|
||
|
enableScripts: true,
|
||
|
};
|
||
|
panel.webview.html = this.html;
|
||
|
|
||
|
this._register(vscode.workspace.onDidChangeTextDocument(e => {
|
||
|
if (e.document === this.document) {
|
||
|
this.update();
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
this._register(panel.webview.onDidReceiveMessage(message => {
|
||
|
switch (message.type) {
|
||
|
case 'edit':
|
||
|
this.doEdit(message.value);
|
||
|
break;
|
||
|
|
||
|
case 'didChangeContent':
|
||
|
vscode.commands.executeCommand(Testing.abcEditorContentChangeCommand, {
|
||
|
content: message.value,
|
||
|
source: document.uri,
|
||
|
} as Testing.CustomEditorContentChangeEvent);
|
||
|
break;
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
this._register(panel.onDidDispose(() => { this.dispose(); }));
|
||
|
|
||
|
this.update();
|
||
|
}
|
||
|
|
||
|
public testing_fakeInput(value: string) {
|
||
|
this.panel.webview.postMessage({
|
||
|
type: 'fakeInput',
|
||
|
value: value,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private async doEdit(value: string) {
|
||
|
const edit = new vscode.WorkspaceEdit();
|
||
|
edit.replace(this.document.uri, this.document.validateRange(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(999999, 999999))), value);
|
||
|
this.limit(() => {
|
||
|
this.currentWorkspaceEdit = vscode.workspace.applyEdit(edit).then(() => {
|
||
|
this.syncedVersion = this.document.version;
|
||
|
this.currentWorkspaceEdit = undefined;
|
||
|
});
|
||
|
return this.currentWorkspaceEdit;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public dispose() {
|
||
|
if (this.isDisposed) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this._onDispose.fire();
|
||
|
super.dispose();
|
||
|
}
|
||
|
|
||
|
private get html() {
|
||
|
const contentRoot = path.join(this._extensionPath, 'customEditorMedia');
|
||
|
const scriptUri = vscode.Uri.file(path.join(contentRoot, 'textEditor.js'));
|
||
|
const nonce = Date.now() + '';
|
||
|
return /* html */`<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline';">
|
||
|
<title>Document</title>
|
||
|
</head>
|
||
|
<body>
|
||
|
<textarea style="width: 300px; height: 300px;"></textarea>
|
||
|
<script nonce=${nonce} src="${this.panel.webview.asWebviewUri(scriptUri)}"></script>
|
||
|
</body>
|
||
|
</html>`;
|
||
|
}
|
||
|
|
||
|
public async update() {
|
||
|
await this.currentWorkspaceEdit;
|
||
|
|
||
|
if (this.isDisposed || this.syncedVersion >= this.document.version) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.panel.webview.postMessage({
|
||
|
type: 'setValue',
|
||
|
value: this.document.getText(),
|
||
|
});
|
||
|
this.syncedVersion = this.document.version;
|
||
|
}
|
||
|
}
|