import path from 'path' ; import fs from 'fs' ; import { WebviewPanel , Disposable , window , ViewColumn , Uri , ExtensionContext , workspace , EventEmitter , Selection , TextEditorRevealType } from 'vscode' ; import { EXT_EDITOR_ID } from '~/meta' ; import { Protocol } from '~/protocol' ; import i18n from '~/i18n' ; import { CurrentFile , Global , KeyInDocument , KeyDetector , Config , Telemetry , TelemetryKey , ActionSource } from '~/core' ; export class EditorContext { filepath ? : string ; keys ? : KeyInDocument [ ] ; } export class EditorPanel { /**
     * Track the currently panel. Only allow a single panel to exist at a time.
     */ public static currentPanel : EditorPanel | undefined ; private static _onDidChanged = new EventEmitter < boolean > ( ) ; public static onDidChange = EditorPanel . _onDidChanged . event ; private readonly _panel : WebviewPanel ; private readonly _protocol : Protocol ; private readonly _ctx : ExtensionContext ; private _disposables : Disposable [ ] = [ ] ; private _editing_key : string | undefined ; private _mode : 'standalone' | 'currentFile' = 'standalone' ; get mode ( ) { return this . _mode ; } set mode ( v ) { if ( this . _mode !== v ) this . _mode = v ; } public static createOrShow ( ctx , column ? ) { const panel = this . revive ( ctx , undefined , column ) ; panel . reveal ( column || panel . _panel . viewColumn ) ; return panel ; } public static revive ( ctx , panel ? , column ? ) { if ( ! EditorPanel . currentPanel ) { EditorPanel . currentPanel = new EditorPanel ( ctx , panel , column ) ; EditorPanel . _onDidChanged . fire ( true ) ; } return EditorPanel . currentPanel ; } private constructor ( ctx , panel ? , column ? ) { this . _ctx = ctx ; this . _panel = panel || window . createWebviewPanel ( EXT_EDITOR_ID , i18n . t ( 'editor.title' ) , column || ViewColumn . Active , { enableScripts : true , localResourceRoots : [ Uri . file ( path . join ( this . _ctx . extensionPath , 'res' ) ) , ] , retainContextWhenHidden : true , } ) ; const webview = this . _panel . webview ; this . _protocol = new Protocol ( async ( message ) => { if ( message . type === 'switch-to' ) this . openKey ( message . keypath ! ) ; else this . _panel . webview . postMessage ( message ) ; } , async ( message ) => { switch ( message . type ) { case 'webview.refresh' : this . init ( ) ; break ; case 'navigate-key' : this . navigateKey ( message . data ) ; break ; } return undefined ; } , { get extendConfig ( ) { return { extensionRoot : webview . asWebviewUri ( Uri . file ( Config . extensionPath ! ) ) . toString ( ) , } ; } , } ) ; this . _panel . onDidDispose ( ( ) => this . dispose ( ) , null , this . _disposables ) ; this . _panel . webview . onDidReceiveMessage ( msg => this . _protocol . handleMessages ( msg ) , null , this . _disposables ) ; CurrentFile . loader . onDidChange ( ( ) => { if ( this . _editing_key ) this . openKey ( this . _editing_key ) ; } , null , this . _disposables ) ; workspace . onDidChangeConfiguration ( ( ) => this . _protocol . updateConfig ( ) , null , this . _disposables ) ; Global . reviews . onDidChange ( ( keypath ? ) => { if ( this . _editing_key && ( ! keypath || this . _editing_key === keypath ) ) this . openKey ( this . _editing_key ) ; } , null , this . _disposables ) ; this . init ( ) ; } private reveal ( column ? ) { column = column || ( window . activeTextEditor ? window . activeTextEditor . viewColumn : undefined ) ; this . _panel . reveal ( column ) ; } public setContext ( context = { } ) { this . _protocol . postMessage ( { type : 'context' , data : context , } ) ; } public sendCurrentFileContext ( ) { if ( this . mode === 'standalone' ) this . setContext ( { } ) ; const doc = window . activeTextEditor ?. document ; if ( ! doc || ! Global . isLanguageIdSupported ( doc . languageId ) ) return false ; let keys = KeyDetector . getKeys ( doc ) || [ ] ; if ( ! keys . length ) return false ; keys = keys . map ( k => ( { ... k , value : CurrentFile . loader . getValueByKey ( k . key ) , } ) ) ; const context = { filepath : doc . uri . fsPath , keys , } ; this . setContext ( context ) ; return true ; } public openKey ( keypath , locale ? , index ? ) { const node = CurrentFile . loader . getNodeByKey ( keypath , true ) ; if ( node ) { this . _editing_key = keypath ; this . _protocol . postMessage ( { type : 'route' , route : 'open-key' , data : { locale , keypath , records : CurrentFile . loader . getShadowLocales ( node ) , reviews : Global . reviews . getReviews ( keypath ) , keyIndex : index , } , } ) ; this . sendCurrentFileContext ( ) ; } else { } } async navigateKey ( data ) { Telemetry . track ( TelemetryKey . GoToKey , { source : ActionSource . UiEditor } ) ; if ( ! data . filepath ) return ; this . openKey ( data . key , undefined , data . keyIndex ) ; const doc = await workspace . openTextDocument ( Uri . file ( data . filepath ) ) ; const editor = await window . showTextDocument ( doc , ViewColumn . One ) ; editor . selection = new Selection ( doc . positionAt ( data . end ) , doc . positionAt ( data . start ) ) ; editor . revealRange ( editor . selection , TextEditorRevealType . InCenter ) ; } public dispose ( ) { EditorPanel . currentPanel = undefined ; this . _panel . dispose ( ) ; Disposable . from ( ... this . _disposables ) . dispose ( ) ; EditorPanel . _onDidChanged . fire ( false ) ; } init ( ) { this . _panel . iconPath = Uri . file ( path . join ( this . _ctx . extensionPath , 'res/logo.svg' ) ) ; this . _panel . webview . html = fs . readFileSync ( path . join ( this . _ctx . extensionPath , 'dist/editor/index.html' ) , 'utf-8' ) ; this . _protocol . updateI18nMessages ( ) ; } get visible ( ) { return this . _panel . visible ; } }