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 ) { } public register ( ) { const provider = vscode . window . registerCustomEditorProvider ( AbcTextEditorProvider . viewType , this ) ; const commands = [ ] ; commands . push ( vscode . commands . registerCommand ( Testing . abcEditorTypeCommand , ( content ) => { this . activeEditor ?. testing_fakeInput ( content ) ; } ) ) ; return vscode . Disposable . from ( provider , ... commands ) ; } public async resolveCustomTextEditor ( document , panel ) { 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 , private readonly _extensionPath , private readonly panel ) { 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 ) { this . panel . webview . postMessage ( { type : 'fakeInput' , value : value , } ) ; } private async doEdit ( value ) { 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 override 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 = getNonce ( ) ; return `<!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 ; } } function getNonce ( ) { let text = '' ; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ; for ( let i = 0 ; i < 64 ; i ++ ) { text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ; } return text ; }