import { exec , ExecOptions , spawn } from 'child_process' ; import { Event , window } from 'coc.nvim' ; import path from 'path' ; import which from 'which' ; export interface IGit { path : string ; version : string ; } export function wait ( ms ) { return new Promise ( resolve => { setTimeout ( ( ) => { resolve ( undefined ) ; } , ms ) ; } ) ; } export function shellescape ( s ) { if ( process . platform == 'win32' ) { return `"${ s . replace ( /"/g , '\\"' ) }"` ; } if ( /[^A-Za-z0-9_\/:=-]/ . test ( s ) ) { s = "'" + s . replace ( /'/g , "'\\''" ) + "'" ; s = s . replace ( /^(?:'')+/g , '' ) . replace ( /\\'''/g , "\\'" ) ; return s ; } return s ; } export function toUnixSlash ( fsPath ) { if ( process . platform == 'win32' ) { return fsPath . replace ( /\\/g , '/' ) ; } return fsPath ; } export async function safeRun ( cmd , opts = { } ) { try { return await runCommand ( cmd , opts , 5000 ) ; } catch ( e ) { console . error ( e . message ) ; return null ; } } export function spawnCommand ( cmd , args , cwd ) { const cp = spawn ( cmd , args , { cwd } ) ; let res = '' ; return new Promise ( ( resolve , reject ) => { cp . stdout . on ( 'data' , data => { res += data . toString ( ) ; } ) ; cp . stderr . on ( 'data' , data => { window . showMessage ( `"${ cmd } ${ args . join ( ' ' ) }" error: ${ data . toString ( ) }` , 'error' ) ; } ) ; cp . on ( 'close' , code => { if ( code != 0 ) { return reject ( new Error ( `${ cmd } exited with code ${ code }` ) ) ; } resolve ( res ) ; } ) ; } ) ; } export function runCommand ( cmd , opts = { } , timeout ? ) { opts . maxBuffer = 5 * 1024 * 1024 ; return new Promise < string > ( ( resolve , reject ) => { let timer ; if ( timeout ) { timer = setTimeout ( ( ) => { reject ( new Error ( `timeout after ${ timeout }s` ) ) ; } , timeout * 1000 ) ; } exec ( cmd , opts , ( err , stdout , stderr ) => { if ( timer ) clearTimeout ( timer ) ; if ( err ) { reject ( new Error ( `exited with ${ err . code }\n${ stderr }` ) ) ; return ; } resolve ( stdout ) ; } ) ; } ) ; } export function getStdout ( cmd , opts = { } , timeout ? ) { return new Promise < string > ( ( resolve , reject ) => { let timer ; if ( timeout ) { timer = setTimeout ( ( ) => { reject ( new Error ( `timeout after ${ timeout }s` ) ) ; } , timeout * 1000 ) ; } opts . maxBuffer = 5 * 1024 * 1024 ; exec ( cmd , opts , ( _err , stdout ) => { if ( timer ) clearTimeout ( timer ) ; if ( stdout ) { resolve ( stdout ) ; return ; } resolve ( undefined ) ; } ) ; } ) ; } export function equals ( one , other ) { if ( one === other ) { return true ; } if ( one === null || one === undefined || other === null || other === undefined ) { return false ; } if ( typeof one !== typeof other ) { return false ; } if ( typeof one !== 'object' ) { return false ; } if ( Array . isArray ( one ) !== Array . isArray ( other ) ) { return false ; } let i ; let key ; if ( Array . isArray ( one ) ) { if ( one . length !== other . length ) { return false ; } for ( i = 0 ; i < one . length ; i ++ ) { if ( ! equals ( one [ i ] , other [ i ] ) ) { return false ; } } } else { const oneKeys = [ ] ; for ( key in one ) { oneKeys . push ( key ) ; } oneKeys . sort ( ) ; const otherKeys = [ ] ; for ( key in other ) { otherKeys . push ( key ) ; } otherKeys . sort ( ) ; if ( ! equals ( oneKeys , otherKeys ) ) { return false ; } for ( i = 0 ; i < oneKeys . length ; i ++ ) { if ( ! equals ( one [ oneKeys [ i ] ] , other [ oneKeys [ i ] ] ) ) { return false ; } } } return true ; } export function getRepoUrl ( remote ) { let url = remote . replace ( /\s+$/ , '' ) . replace ( /\.git$/ , '' ) ; if ( url . startsWith ( 'git@' ) ) { let str = url . slice ( 4 ) ; let parts = str . split ( ':' , 2 ) ; url = `https://${ parts [ 0 ] }/${ parts [ 1 ] }` ; } return url ; } export function getUrl ( fix , repoURL , name , filepath , lines ? ) { let anchor = '' ; if ( lines && Array . isArray ( lines ) ) { anchor = lines ? lines . map ( l => `L${ l }` ) . join ( '-' ) : '' ; } else if ( typeof lines == 'string' ) { anchor = lines ; } let url = repoURL + '/blob/' + name + '/' + filepath + ( anchor ? '#' + anchor : '' ) ; let parts = fix . split ( '|' ) ; let match = RegExp ( parts [ 0 ] ) , result = parts [ 1 ] ; return url . replace ( match , result ) ; } function parseVersion ( raw ) { return raw . replace ( /^git version / , '' ) ; } function findSystemGitWin32 ( base , onLookup ) { if ( ! base ) { return Promise . reject < IGit > ( 'Not found' ) ; } return findSpecificGit ( path . join ( base , 'Git' , 'cmd' , 'git.exe' ) , onLookup ) ; } function findGitWin32InPath ( onLookup ) { const whichPromise = new Promise < string > ( ( c , e ) => which ( 'git.exe' , ( err , path ) => err ? e ( err ) : c ( path ) ) ) ; return whichPromise . then ( path => findSpecificGit ( path , onLookup ) ) ; } function findGitWin32 ( onLookup ) { return findSystemGitWin32 ( process . env [ 'ProgramW6432' ] as string , onLookup ) . then ( undefined , ( ) => findSystemGitWin32 ( process . env [ 'ProgramFiles(x86)' ] as string , onLookup ) ) . then ( undefined , ( ) => findSystemGitWin32 ( process . env [ 'ProgramFiles' ] as string , onLookup ) ) . then ( undefined , ( ) => findSystemGitWin32 ( path . join ( process . env [ 'LocalAppData' ] as string , 'Programs' ) , onLookup ) ) . then ( undefined , ( ) => findGitWin32InPath ( onLookup ) ) ; } function findSpecificGit ( path , onLookup ) { return new Promise < IGit > ( ( c , e ) => { onLookup ( path ) ; const buffers = [ ] ; const child = spawn ( path , [ '--version' ] ) ; child . stdout . on ( 'data' , ( b ) => buffers . push ( b ) ) ; child . on ( 'error' , cpErrorHandler ( e ) ) ; child . on ( 'exit' , code => code ? e ( new Error ( 'Not found' ) ) : c ( { path , version : parseVersion ( Buffer . concat ( buffers ) . toString ( 'utf8' ) . trim ( ) ) } ) ) ; } ) ; } export function cpErrorHandler ( cb ) { return err => { if ( /ENOENT/ . test ( err . message ) ) { err = new Error ( 'Failed to execute git (ENOENT)' ) ; } cb ( err ) ; } ; } function findGitDarwin ( onLookup ) { return new Promise < IGit > ( ( c , e ) => { exec ( 'which git' , ( err , gitPathBuffer ) => { if ( err ) { return e ( 'git not found' ) ; } const path = gitPathBuffer . toString ( ) . replace ( /^\s+|\s+$/g , '' ) ; function getVersion ( path ) { onLookup ( path ) ; exec ( 'git --version' , ( err , stdout ) => { if ( err ) { return e ( 'git not found' ) ; } return c ( { path , version : parseVersion ( stdout . trim ( ) ) } ) ; } ) ; } if ( path !== '/usr/bin/git' ) { return getVersion ( path ) ; } getVersion ( path ) ; exec ( 'xcode-select -p' , ( err ) => { if ( err && err . code === 2 ) { return e ( 'git not found' ) ; } } ) ; } ) ; } ) ; } export function findGit ( hint , onLookup ) { const first = hint ? findSpecificGit ( hint , onLookup ) : Promise . reject < IGit > ( null ) ; return first . then ( undefined , ( ) => { switch ( process . platform ) { case 'darwin' : return findGitDarwin ( onLookup ) ; case 'win32' : return findGitWin32 ( onLookup ) ; default : return findSpecificGit ( 'git' , onLookup ) ; } } ) . then ( null , ( ) => Promise . reject ( new Error ( 'Git installation not found.' ) ) ) ; } export function onceEvent < T > ( event ) { return ( listener , thisArgs = null , disposables ? ) => { const result = event ( e => { result . dispose ( ) ; return listener . call ( thisArgs , e ) ; } , null , disposables ) ; return result ; } ; }