import * as vscode from 'vscode' ; import * as path from 'path' ; import { exec } from 'child_process' ; export function activate ( context ) { var extension = new RunOnSaveExtension ( context ) ; extension . showOutputMessage ( ) ; vscode . workspace . onDidChangeConfiguration ( ( ) => { let disposeStatus = extension . showStatusMessage ( 'Run On Save: Reloading config.' ) ; extension . loadConfig ( ) ; disposeStatus . dispose ( ) ; } ) ; vscode . commands . registerCommand ( 'extension.emeraldwalk.enableRunOnSave' , ( ) => { extension . isEnabled = true ; } ) ; vscode . commands . registerCommand ( 'extension.emeraldwalk.disableRunOnSave' , ( ) => { extension . isEnabled = false ; } ) ; vscode . workspace . onDidSaveTextDocument ( ( document ) => { extension . runCommands ( document ) ; } ) ; } interface ICommand { match ? : string ; notMatch ? : string ; cmd : string ; isAsync : boolean ; } interface IConfig { shell : string ; autoClearConsole : boolean ; commands : Array < ICommand > ; } class RunOnSaveExtension { private _outputChannel : vscode . OutputChannel ; private _context : vscode . ExtensionContext ; private _config : IConfig ; constructor ( context ) { this . _context = context ; this . _outputChannel = vscode . window . createOutputChannel ( 'Run On Save' ) ; this . loadConfig ( ) ; } /** Recursive call to run commands. */ private _runCommands ( commands , document ) { if ( commands . length ) { var cfg = commands . shift ( ) ; this . showOutputMessage ( `*** cmd start: ${ cfg . cmd }` ) ; var child = exec ( cfg . cmd , this . _getExecOption ( document ) ) ; child . stdout . on ( 'data' , data => this . _outputChannel . append ( data ) ) ; child . stderr . on ( 'data' , data => this . _outputChannel . append ( data ) ) ; child . on ( 'error' , ( e ) => { this . showOutputMessage ( e . message ) ; } ) ; child . on ( 'exit' , ( e ) => { if ( ! cfg . isAsync ) { this . _runCommands ( commands , document ) ; } } ) ; if ( cfg . isAsync ) { this . _runCommands ( commands , document ) ; } } else { this . showStatusMessage ( 'Run on Save done.' ) ; } } private _getExecOption ( document ) { return { shell : this . shell , cwd : this . _getWorkspaceFolderPath ( document . uri ) , } ; } private _getWorkspaceFolderPath ( uri ) { const workspaceFolder = vscode . workspace . getWorkspaceFolder ( uri ) ; return workspaceFolder ? workspaceFolder . uri . fsPath : vscode . workspace . rootPath ; } public get isEnabled ( ) : boolean { return ! ! this . _context . globalState . get ( 'isEnabled' , true ) ; } public set isEnabled ( value ) { this . _context . globalState . update ( 'isEnabled' , value ) ; this . showOutputMessage ( ) ; } public get shell ( ) : string { return this . _config . shell ; } public get autoClearConsole ( ) : boolean { return ! ! this . _config . autoClearConsole ; } public get commands ( ) : Array < ICommand > { return this . _config . commands || [ ] ; } public loadConfig ( ) { this . _config = < IConfig > < any > vscode . workspace . getConfiguration ( 'emeraldwalk.runonsave' ) ; } /**
     * Show message in output channel
     */ public showOutputMessage ( message ? ) { message = message || `Run On Save ${ this . isEnabled ? 'enabled' : 'disabled' }.` ; this . _outputChannel . appendLine ( message ) ; } /**
     * Show message in status bar and output channel.
     * Return a disposable to remove status bar message.
     */ public showStatusMessage ( message ) { this . showOutputMessage ( message ) ; return vscode . window . setStatusBarMessage ( message ) ; } public runCommands ( document ) { if ( this . autoClearConsole ) { this . _outputChannel . clear ( ) ; } if ( ! this . isEnabled || this . commands . length === 0 ) { this . showOutputMessage ( ) ; return ; } var match = ( pattern ) => pattern && pattern . length > 0 && new RegExp ( pattern ) . test ( document . fileName ) ; var commandConfigs = this . commands . filter ( cfg => { var matchPattern = cfg . match || '' ; var negatePattern = cfg . notMatch || '' ; var isMatch = matchPattern . length === 0 || match ( matchPattern ) ; var isNegate = negatePattern . length > 0 && match ( negatePattern ) ; return ! isNegate && isMatch ; } ) ; if ( commandConfigs . length === 0 ) { return ; } this . showStatusMessage ( 'Running on save commands...' ) ; const commands = [ ] ; for ( const cfg of commandConfigs ) { let cmdStr = cfg . cmd ; const extName = path . extname ( document . fileName ) ; const workspaceFolderPath = this . _getWorkspaceFolderPath ( document . uri ) ; const relativeFile = path . relative ( workspaceFolderPath , document . uri . fsPath ) ; cmdStr = cmdStr . replace ( /\${file}/g , `${ document . fileName }` ) ; cmdStr = cmdStr . replace ( /\${workspaceRoot}/g , workspaceFolderPath ) ; cmdStr = cmdStr . replace ( /\${workspaceFolder}/g , workspaceFolderPath ) ; cmdStr = cmdStr . replace ( /\${fileBasename}/g , path . basename ( document . fileName ) ) ; cmdStr = cmdStr . replace ( /\${fileDirname}/g , path . dirname ( document . fileName ) ) ; cmdStr = cmdStr . replace ( /\${fileExtname}/g , extName ) ; cmdStr = cmdStr . replace ( /\${fileBasenameNoExt}/g , path . basename ( document . fileName , extName ) ) ; cmdStr = cmdStr . replace ( /\${relativeFile}/g , relativeFile ) ; cmdStr = cmdStr . replace ( /\${cwd}/g , process . cwd ( ) ) ; cmdStr = cmdStr . replace ( /\${env\.([^}]+)}/g , ( sub , envName ) => { return process . env [ envName ] ; } ) ; commands . push ( { cmd : cmdStr , isAsync : ! ! cfg . isAsync } ) ; } this . _runCommands ( commands , document ) ; } }