'use strict' ; import * as path from 'path' ; import * as assert from 'assert' ; import { Uri , commands , Disposable , workspace , window , QuickPickItem } from 'vscode' ; import { Model , HistoryViewContext } from './model' ; import { HistoryViewProvider } from './historyViewProvider' ; import { InfoViewProvider } from './infoViewProvider' ; import { GitService , GitRepo , GitRefType , GitCommittedFile } from './gitService' ; import { Tracer } from './tracer' ; function toGitUri ( uri , ref ) { return uri . with ( { scheme : 'git' , path : uri . path , query : JSON . stringify ( { path : uri . fsPath , ref } ) } ) ; } async function selectBranch ( gitService , repo , allowEnterSha ? ) { const refs = await gitService . getRefs ( repo ) ; const items = refs . map ( ref => { let description ; if ( ref . type === GitRefType . Head ) { description = ref . commit ; } else if ( ref . type === GitRefType . Tag ) { description = `Tag at ${ ref . commit }` ; } else if ( ref . type === GitRefType . RemoteHead ) { description = `Remote branch at ${ ref . commit }` ; } return { label : ref . name || ref . commit , description } ; } ) ; if ( allowEnterSha ) items . unshift ( new EnterShaPickItem ) ; return items ; } async function branchCombination ( gitService , repo ) { const refs = ( await gitService . getRefs ( repo ) ) . filter ( ref => { return ref . type != GitRefType . Tag ; } ) ; const localRefs = refs . filter ( ref => { return ref . type != GitRefType . RemoteHead ; } ) ; let items = [ ] ; localRefs . forEach ( source => { refs . forEach ( target => { if ( source . name != target . name && source . commit != target . commit ) { items . push ( { label : `${ source . name || source . commit } .. ${ target . name || target . commit }` } ) ; } } ) ; } ) ; return items ; } interface RepoPickItem extends QuickPickItem { repo : GitRepo ; } class EnterShaPickItem implements QuickPickItem { label = "Enter commit SHA" ; description = "" ; openShaTextBox = true ; } function selectGitRepo ( gitService ) { const repos = gitService . getGitRepos ( ) ; if ( repos . length === 0 ) { return null ; } if ( repos . length === 1 ) { return Promise . resolve ( repos [ 0 ] ) ; } const pickItems = repos . map ( repo => { let label = '' ; return { label : path . basename ( repo . root ) , description : repo . root , repo } ; } ) ; return window . showQuickPick ( pickItems , { placeHolder : 'Select the git repo' } ) . then < GitRepo > ( item => { if ( item ) { return item . repo ; } return null ; } ) ; } async function getRefFromQuickPickItem ( item , inputBoxTitle ) { return ( < EnterShaPickItem > item ) . openShaTextBox ? await window . showInputBox ( { prompt : inputBoxTitle } ) : item . label ; } async function selectAuthor ( gitService , repo ) { let authors = await gitService . getAuthors ( repo ) ; authors . unshift ( { name : 'All' , email : '' } ) ; return authors . map ( author => { return { label : author . name , description : author . email } ; } ) ; } interface Command { id : string ; method : Function ; } const Commands = [ ] ; function command ( id ) { return function ( _target , _key , descriptor ) { if ( ! ( typeof descriptor . value === 'function' ) ) { throw new Error ( 'not supported' ) ; } Commands . push ( { id , method : descriptor . value } ) ; } ; } export class CommandCenter { private _disposables : Disposable [ ] ; constructor ( private _model , private _gitService , private _historyView , private _infoView ) { this . _disposables = Commands . map ( ( { id , method } ) => { return commands . registerCommand ( id , ( ... args ) => { Promise . resolve ( method . apply ( this , args ) ) ; } ) ; } ) ; } dispose ( ) { this . _disposables . forEach ( d => d . dispose ( ) ) ; } @ command ( 'githd.clear' ) async clear ( ) { Tracer . verbose ( 'Command: githd.clear' ) ; this . _model . filesViewContext = { leftRef : null , rightRef : null , specifiedPath : null , repo : null } ; } @ command ( 'githd.viewHistory' ) async viewHistory ( ) { Tracer . verbose ( 'Command: githd.viewHistory' ) ; selectGitRepo ( this . _gitService ) . then ( repo => { if ( repo ) { this . _viewHistory ( { repo } ) ; } } ) ; } @ command ( 'githd.viewFileHistory' ) async viewFileHistory ( specifiedPath = window . activeTextEditor ? window . activeTextEditor . document . uri : undefined ) { Tracer . verbose ( 'Command: githd.viewFileHistory' ) ; if ( ! specifiedPath ) { return ; } return this . _viewHistory ( { specifiedPath , repo : await this . _gitService . getGitRepo ( specifiedPath ) } ) ; } @ command ( 'githd.viewFolderHistory' ) async viewFolderHistory ( specifiedPath ) { Tracer . verbose ( 'Command: githd.viewFolderHistory' ) ; return this . viewFileHistory ( specifiedPath ) ; } @ command ( 'githd.viewLineHistory' ) async viewLineHistory ( file = window . activeTextEditor ? window . activeTextEditor . document . uri : undefined ) { Tracer . verbose ( 'Command: githd.viewLineHistory' ) ; if ( ! file ) { return ; } const line = window . activeTextEditor && window . activeTextEditor . selection . active . line + 1 ; if ( ! line ) { return ; } return this . _viewHistory ( { specifiedPath : file , line , repo : await this . _gitService . getGitRepo ( file ) } ) ; } @ command ( 'githd.viewAllHistory' ) async viewAllHistory ( ) { Tracer . verbose ( 'Command: githd.viewAllHistory' ) ; let context = this . _model . historyViewContext ? this . _model . historyViewContext : { repo : this . _gitService . getGitRepos ( ) [ 0 ] } ; context . isStash = false ; return this . _viewHistory ( context , true ) ; } @ command ( 'githd.viewBranchHistory' ) async viewBranchHistory ( context ? ) { Tracer . verbose ( 'Command: githd.viewBranchHistory' ) ; let placeHolder = `Select a ref to see it's history` ; let repo ; if ( context ) { repo = context . repo ; const specifiedPath = this . _model . historyViewContext . specifiedPath ; if ( specifiedPath ) { placeHolder += ` of ${ path . basename ( specifiedPath . fsPath ) }` ; } } else { repo = await Promise . resolve ( selectGitRepo ( this . _gitService ) ) ; if ( ! repo ) { return ; } } placeHolder += ` (${ repo . root })` ; window . showQuickPick ( selectBranch ( this . _gitService , repo ) , { placeHolder } ) . then ( item => { if ( item ) { if ( context ) { context . branch = item . label ; this . _viewHistory ( context ) ; } else { this . _viewHistory ( { branch : item . label , repo } ) ; } } } ) ; } @ command ( 'githd.viewAuthorHistory' ) async viewAuthorHistory ( ) { Tracer . verbose ( 'Command: githd.viewAuthorHistory' ) ; assert ( this . _model . historyViewContext , 'history view context should exist' ) ; const context = this . _model . historyViewContext ; let placeHolder = `Select a author to see his/her commits` ; window . showQuickPick ( selectAuthor ( this . _gitService , context . repo ) , { placeHolder } ) . then ( item => { if ( item ) { const email = item . description ; let context = this . _model . historyViewContext ; if ( context ) { context . author = email ; } this . _viewHistory ( context ) ; } } ) ; } @ command ( 'githd.viewStashes' ) async viewStashes ( ) { Tracer . verbose ( 'Command: githd.viewStashes' ) ; selectGitRepo ( this . _gitService ) . then ( repo => { if ( repo ) { this . _viewHistory ( { repo , isStash : true } ) ; } } ) ; } @ command ( 'githd.diffBranch' ) async diffBranch ( ) { Tracer . verbose ( 'Command: githd.diffBranch' ) ; selectGitRepo ( this . _gitService ) . then ( async ( repo ) => { if ( ! repo ) { return ; } this . _diffSelections ( { repo } ) ; } ) ; } @ command ( 'githd.diffFile' ) async diffFile ( specifiedPath ) { Tracer . verbose ( 'Command: githd.diffFile' ) ; return this . _diffPath ( specifiedPath ) ; } @ command ( 'githd.diffFolder' ) async diffFolder ( specifiedPath ) { Tracer . verbose ( 'Command: githd.diffFolder' ) ; return this . _diffPath ( specifiedPath ) ; } @ command ( 'githd.inputRef' ) async inputRef ( ) { Tracer . verbose ( 'Command: githd.inputRef' ) ; selectGitRepo ( this . _gitService ) . then ( repo => { if ( ! repo ) { return ; } window . showInputBox ( { placeHolder : `Input a ref(sha1) to see it's committed files` } ) . then ( ref => this . _model . filesViewContext = { rightRef : ref . trim ( ) , specifiedPath : null , repo } ) ; } ) ; } @ command ( 'githd.openCommit' ) async openCommit ( repo , ref , specifiedPath ) { Tracer . verbose ( 'Command: githd.openCommit' ) ; this . _model . filesViewContext = { rightRef : ref , repo , specifiedPath } ; } @ command ( 'githd.openCommittedFile' ) async openCommittedFile ( file ) { Tracer . verbose ( 'Command: githd.openCommittedFile' ) ; let rightRef = this . _model . filesViewContext . rightRef ; let leftRef = rightRef + '~' ; let title = rightRef ; if ( this . _model . filesViewContext . leftRef ) { leftRef = this . _model . filesViewContext . leftRef ; title = `${ leftRef } .. ${ rightRef }` ; } commands . executeCommand < void > ( 'vscode.diff' , toGitUri ( file . oldFileUri , leftRef ) , toGitUri ( file . fileUri , rightRef ) , title + ' | ' + path . basename ( file . gitRelativePath ) , { preview : true } ) ; } @ command ( 'githd.openCommitInfo' ) async openCommitInfo ( content ) { Tracer . verbose ( 'Command: githd.openCommitInfo' ) ; this . _infoView . update ( content ) ; workspace . openTextDocument ( InfoViewProvider . defaultUri ) . then ( doc => window . showTextDocument ( doc , { preview : true , preserveFocus : true } ) . then ( ( ) => commands . executeCommand ( 'cursorTop' ) ) ) ; } @ command ( 'githd.diffUncommittedFile' ) async diffUncommittedFile ( file = window . activeTextEditor ? window . activeTextEditor . document . uri : undefined ) { if ( ! file ) { return ; } Tracer . verbose ( 'Command: githd.diffUncommittedFile' ) ; const repo = await this . _gitService . getGitRepo ( file ) ; window . showQuickPick ( selectBranch ( this . _gitService , repo ) , { placeHolder : `Select a ref to see the diff with local copy of ${ path . basename ( file . path ) }` } ) . then ( async ( item ) => { if ( item ) { return await commands . executeCommand < void > ( 'vscode.diff' , toGitUri ( file , item . label ) , file , `${ item . label } .. Uncommitted (${ path . basename ( file . path ) })` , { preview : true } ) ; } } ) ; } @ command ( 'githd.setExpressMode' ) async setExpressMode ( ) { Tracer . verbose ( 'Command: githd.setExpressMode' ) ; this . _historyView . express = ! this . _historyView . express ; } private async _viewHistory ( context , all = false ) { this . _historyView . loadAll = all ; await this . _model . setHistoryViewContext ( context ) ; } private async _diffPath ( specifiedPath ) { if ( specifiedPath ) { const repo = await this . _gitService . getGitRepo ( specifiedPath ) ; return this . _diffSelections ( { repo , specifiedPath } ) ; } } private async _diffSelections ( { repo , specifiedPath } ) { const branches = await selectBranch ( this . _gitService , repo , true ) ; const branchWithCombination = await branchCombination ( this . _gitService , repo ) ; const items = [ ... branches , ... branchWithCombination ] ; const currentRef = await this . _gitService . getCurrentBranch ( repo ) ; const placeHolder = `Select a ref to see it's diff with ${ currentRef } or select two refs to see their diffs` ; window . showQuickPick ( items , { placeHolder : placeHolder } ) . then ( async ( item ) => { if ( ! item ) { return ; } let leftRef = await getRefFromQuickPickItem ( item , `Input a ref(sha1) to compare with ${ currentRef } or ` + `'ref(sha1) .. ref(sha2)' to compare with two commits` ) ; let rightRef = currentRef ; if ( ! leftRef ) { return ; } if ( leftRef . indexOf ( '..' ) != - 1 ) { const diffBranch = leftRef . split ( '..' ) ; leftRef = diffBranch [ 0 ] . trim ( ) ; rightRef = diffBranch [ 1 ] . trim ( ) ; } this . _model . filesViewContext = { repo , leftRef , rightRef , specifiedPath } ; } ) ; } }