import React from 'react' ; import { basename , dirname , join } from 'path' ; import { Icons , ViewLevel , TextWithIconWidget } from '@kui-shell/plugin-client-common' ; import { Capabilities , Events , getCurrentTab , i18n , encodeComponent , pexecInCurrentTab , CodedError } from '@kui-shell/core' ; const strings = i18n ( 'plugin-bash-like' ) ; const strings2 = i18n ( 'plugin-git' ) ; interface Props { className ? : string ; } interface State { text : string ; status : string ; viewLevel : ViewLevel ; } export default class CurrentGitBranch extends React . PureComponent < Props , State > { private readonly handler = this . reportCurrentBranch . bind ( this ) ; public constructor ( props ) { super ( props ) ; this . state = { text : '' , status : '' , viewLevel : 'hidden' } ; } /** So we don't handle events after unmounting */ private _unmounted = true ; private get unmounted ( ) { return this . _unmounted ; } private set unmounted ( umm ) { this . _unmounted = umm ; } /** Avoid recomputation for a flurry of events */ private last : number ; private debounce ( ) { const now = Date . now ( ) ; const last = this . last ; this . last = now ; return last && now - last < 250 ; } /**
     * Check the current branch, and the dirtiness thereof.
     *
     */ private async reportCurrentBranch ( ) { if ( this . unmounted ) { return ; } const tab = getCurrentTab ( ) ; if ( ! tab || ! tab . REPL ) { return ; } else if ( this . debounce ( ) ) { return ; } try { const [ isDirty , branch ] = await Promise . all ( [ tab . REPL . qexec ( 'git diff-index --quiet HEAD --' ) . then ( ( ) => false ) . catch ( ( ) => true ) , tab . REPL . qexec < string > ( 'git rev-parse --abbrev-ref HEAD' ) ] ) ; tab . REPL . qexec < true | string > ( 'git status -s' ) . then ( status => this . setState ( { status : status === true ? '' : status } ) ) ; this . setState ( { text : branch , viewLevel : isDirty ? 'warn' : 'normal' } ) ; } catch ( error ) { const err = error as CodedError ; this . last = undefined ; if ( err . code !== 128 && ! /ambiguous argument 'HEAD'/ . test ( err . message ) && ! /not a git repo/ . test ( err . message ) ) { console . error ( 'unable to determine git branch' , err . code , typeof err . code , err ) ; } this . setState ( { text : strings ( 'not a repo' ) , viewLevel : 'hidden' } ) ; } } /**
     * Once we have mounted, we immediately check the current branch,
     * and schedule an update based on standard REPL events.
     *
     */ public componentDidMount ( ) { this . unmounted = false ; if ( Capabilities . inBrowser ( ) ) { Events . eventBus . once ( '/tab/new' , this . handler ) ; } else { this . handler ( ) ; } Events . wireToStandardEvents ( this . handler ) ; } /** Make sure to unsubscribe! */ public componentWillUnmount ( ) { this . unmounted = true ; Events . unwireToStandardEvents ( this . handler ) ; } return private popoverHeader ( ) { return ( < React . Fragment > < div > { strings ( 'Git Branch' ) } < /div> < div > < strong > { this . state . text } < /strong> < /div> < div className = "sub-text even-smaller-text" > { this . changeBranch ( ) } < /div> < /React.Fragment>); } return private popoverBody ( ) { const statusModel = this . statusModel ( ) ; return ( < div className = "top-pad bottom-pad" > { this . summary ( statusModel ) } { this . changes ( statusModel ) } < /div>); } return private statusModel ( ) { return ! this . state . status ? [ ] : this . state . status . split ( /\n/ ) . map ( line => { const match = line . match ( /(.+)\s+(.+)/ ) ; if ( match ) { return { M : match [ 1 ] , file : match [ 2 ] } ; } } ) . filter ( _ => _ ) ; } return private summary ( statusModel ) { return ( < span className = "sub-text" > { statusModel . length > 0 ? strings ( 'You have made the following changes to this branch.' ) : strings ( 'You have made no changes to this branch.' ) } < /span>); } return private changes ( statusModel ) { return ( < div className = "small-top-pad monospace even-smaller-text pre-wrap" > { statusModel . map ( ( { M , file } ) => ( < div key = { `${ M }-${ file }` } className = "tiny-top-pad" > { [ ... M ] . map ( ( m , idx ) => ( < strong key = { `${ m }-${ idx }` } className = { / M / . test ( m ) ? 'red-text' : /D/ . test ( m ) ? 'red-text' : /A/ . test ( m ) ? 'cyan-text' : '' } > { m } < /strong>))} < span title = { file } className = "small-left-pad clickable" onClick = { ( ) => pexecInCurrentTab ( `git diff ${ encodeComponent ( file ) }` ) } > { join ( basename ( dirname ( file ) ) , basename ( file ) ) } < /span> < /div>))} < /div>); } private popover ( ) { return { bodyContent : this . popoverBody ( ) , headerContent : this . popoverHeader ( ) } ; } private changeBranch ( ) { return ( < a href = "#" onClick = { ( ) => pexecInCurrentTab ( 'git branch' ) } > { strings2 ( 'Switch branch' ) } < /a>); } public render ( ) { return ( < TextWithIconWidget className = { this . props . className } text = { this . state . text } viewLevel = { this . state . viewLevel } id = "kui--plugin-git--current-git-branch" title = { strings2 ( 'Your current git branch' ) } popover = { this . popover ( ) } > < Icons icon = "CodeBranch" / > < /TextWithIconWidget>); } }