import path from 'path' ; import fs from 'fs' ; import util from 'util' ; import { ExecOptions , exec , execFile , spawn } from 'child_process' ; import { window , workspace } from 'coc.nvim' ; import which from 'which' ; import { configDir } from './config' ; import { compareVersions } from './versions' ; const runExec = util . promisify ( exec ) ; const isWin = process . platform === 'win32' ; interface GetVersionFunction { ( ) : string | Promise < string > ; } export async function installGoBin ( source , force = false , getVersion ? ) { const name = goBinName ( source ) ; if ( ! force && await goBinExists ( name ) ) { return true ; } const statusItem = window . createStatusBarItem ( 90 , { progress : true } ) ; statusItem . text = `Installing '${ name }'` ; statusItem . show ( ) ; const success = await goInstall ( source ) && await goBinExists ( name ) ; if ( success ) { const vname = getVersion ? `${ name }@${ await getVersion ( ) }` : name ; window . showMessage ( `Installed '${ vname }'` ) ; } else { window . showMessage ( `Failed to install '${ name }'` , 'error' ) ; } statusItem . hide ( ) ; return success ; } async function goInstall ( source ) { return await goVersionOrLater ( '1.17.0' ) ? goRun ( `install ${ source }@latest` ) : goRun ( `get ${ source }@latest` ) ; } async function goVersionOrLater ( version ) { try { return compareVersions ( version , await getGoVersion ( ) ) < 0 ; } catch ( err ) { } return false ; } async function getGoVersion ( ) { try { const [ , out ] = await runBin ( 'go' , [ 'version' ] ) ; return out . trim ( ) . match ( /^go version go(\S+) .*$/ ) [ 1 ] ; } catch ( err ) { } return '' ; } export async function goBinPath ( source ) { const name = goBinName ( source ) ; return path . join ( await configDir ( 'bin' ) , name + ( isWin ? ".exe" : "" ) ) ; } export async function runGoTool ( name , args = [ ] ) { return runBin ( await goBinPath ( name ) , args ) ; } export async function runBin ( bin , args = [ ] ) { return new Promise ( ( resolve ) : void => { const p = spawn ( bin , args ) ; let out = "" ; p . stdout . on ( 'data' , ( data ) => out += data ) ; p . on ( "close" , code => resolve ( [ code , out ] ) ) ; } ) ; } export async function commandExists ( command ) { if ( path . isAbsolute ( command ) ) { return fileExists ( command ) ; } return new Promise ( ( resolve ) : void => { which ( command , ( err ) => resolve ( err == null ) ) ; } ) ; } async function goBinExists ( source ) { const name = goBinName ( source ) ; const bin = await goBinPath ( name ) ; return fileExists ( bin ) ; } async function fileExists ( path ) { return new Promise ( ( resolve ) : void => fs . open ( path , 'r' , ( err ) => resolve ( err === null ) ) ) ; } async function goRun ( args ) { const gopath = await configDir ( 'tools' ) ; const gobin = await configDir ( 'bin' ) ; const env = { GO111MODULE : 'on' , GOBIN : gobin , GOPATH : gopath , GOROOT : '' , GOTOOLDIR : '' , } ; const cmd = isWin ? `go ${ args }` : `env GOBIN="${ gobin }" go ${ args }` ; const opts = { env : Object . assign ( { } , process . env , env ) , cwd : gopath , shell : isWin ? undefined : process . env . SHELL , windowsHide : true , } ; try { await runExec ( cmd , opts ) ; } catch ( ex ) { window . showMessage ( ex , 'error' ) ; return false ; } return true ; } interface execError { code ? : string ; } export async function execTool ( source , args , input ? ) { const [ bin , name ] = await Promise . all ( [ goBinPath ( source ) , goBinName ( source ) , ] ) ; if ( ! await commandExists ( bin ) ) { await installGoBin ( source ) ; } return new Promise ( ( resolve , reject ) => { const p = execFile ( bin , args , { cwd : workspace . cwd } , async ( err , stdout , stderr ) => { if ( err && ( err as execError ) . code === "ENOENT" ) { return reject ( `Error: Command ${ name } not found! Run "CocCommand go.install.${ name }" to install it and try again.` ) ; } if ( err ) { return reject ( stderr . toString ( ) ) ; } return resolve ( stdout . toString ( ) ) ; } ) ; if ( p . pid ) { p . stdin . end ( input ) ; } } ) ; } function goBinName ( source ) { return source . replace ( /\/\.\.\.$/ , '' ) . split ( '/' ) . pop ( ) ; }