import { ConfigurationChangeEvent , Disposable , disposeAll , events , Neovim , window , workspace } from 'coc.nvim' ; import semver from 'semver' ; import debounce from 'debounce' ; import GitBuffer from './model/buffer' ; import Git from './model/git' ; import Service from './model/service' ; import GitStatus from './model/status' ; import { ConflictPart , Diff , GitConfiguration } from './types' ; export default class DocumentManager { private buffers : Map < number , GitBuffer > = new Map ( ) ; private gitStatus : GitStatus ; private config : GitConfiguration ; private disposables : Disposable [ ] = [ ] ; constructor ( private nvim , private service , private virtualTextSrcId , private conflictSrcId = 0 ) { this . loadConfiguration ( ) ; workspace . onDidChangeConfiguration ( this . loadConfiguration , this , this . disposables ) ; this . gitStatus = new GitStatus ( service ) ; if ( this . enableGutters ) { this . defineSigns ( ) . catch ( e => { console . error ( e . message ) ; } ) ; } for ( let doc of workspace . documents ) { let { uri } = doc ; service . createBuffer ( doc , this . config ) . then ( buf => { if ( ! buf || workspace . getDocument ( uri ) == null ) return ; this . buffers . set ( doc . bufnr , buf ) ; } ) ; } workspace . onDidOpenTextDocument ( async ( e ) => { let doc = workspace . getDocument ( e . bufnr ) ; let buf = await service . createBuffer ( doc , this . config ) ; if ( ! buf || ! workspace . getDocument ( e . uri ) ) return ; this . buffers . set ( e . bufnr , buf ) ; } , null , this . disposables ) ; workspace . onDidChangeTextDocument ( async ( e ) => { let buf = this . buffers . get ( e . bufnr ) ; if ( buf ) buf . refresh ( ) ; } , null , this . disposables ) ; workspace . onDidCloseTextDocument ( e => { let buf = this . buffers . get ( e . bufnr ) ; if ( buf ) buf . dispose ( ) ; this . buffers . delete ( e . bufnr ) ; this . service . resolver . delete ( e . uri ) ; } , null , this . disposables ) ; events . on ( 'CursorMoved' , debounce ( async ( bufnr , cursor ) => { let buf = this . buffers . get ( bufnr ) ; if ( buf ) await buf . showBlameInfo ( cursor [ 0 ] ) ; } , 100 ) , null , this . disposables ) ; events . on ( 'BufWritePre' , bufnr => { if ( ! this . enableGutters || this . config . realtimeGutters ) return ; let buf = this . buffers . get ( bufnr ) ; if ( buf ) buf . updateGutters ( ) ; } , null , this . disposables ) ; events . on ( 'FocusGained' , async ( ) => { let bufnr = await nvim . call ( 'bufnr' , [ '%' ] ) ; let buf = this . buffers . get ( bufnr ) ; if ( buf ) buf . refresh ( ) ; } , null , this . disposables ) ; events . on ( 'BufEnter' , bufnr => { let buf = this . buffers . get ( bufnr ) ; if ( buf ) buf . refresh ( ) ; } , null , this . disposables ) ; } private async defineSigns ( ) { let { nvim } = this ; const config = workspace . getConfiguration ( 'git' ) ; let items = [ 'Changed' , 'Added' , 'Removed' , 'TopRemoved' , 'ChangeRemoved' ] ; nvim . pauseNotification ( ) ; for ( let item of items ) { let section = item [ 0 ] . toLowerCase ( ) + item . slice ( 1 ) + 'Sign' ; let text = config . get < string > ( `${ section }.text` , '' ) ; let hlGroup = config . get < string > ( `${ section }.hlGroup` , '' ) ; nvim . command ( `sign define CocGit${ item } text=${ text } texthl=CocGit${ item }Sign` , true ) ; nvim . command ( `hi default link CocGit${ item }Sign ${ hlGroup }` , true ) ; } await nvim . resumeNotification ( ) ; } private loadConfiguration ( e ? ) { if ( e && ! e . affectsConfiguration ( 'git' ) ) return ; let config = workspace . getConfiguration ( 'git' ) ; let obj = { remoteName : config . get < string > ( 'remoteName' , 'origin' ) , diffRevision : config . get < string > ( 'diffRevision' , '' ) , issueFormat : config . get < string > ( 'issueFormat' , '#%i' ) , virtualTextPrefix : config . get < string > ( 'virtualTextPrefix' , '     ' ) , addGBlameToVirtualText : workspace . nvim . hasFunction ( 'nvim_buf_set_virtual_text' ) && config . get < boolean > ( 'addGBlameToVirtualText' , false ) , addGBlameToBufferVar : config . get < boolean > ( 'addGBlameToBufferVar' , false ) , blameUseRealTime : config . get < boolean > ( 'blameUseRealTime' , false ) , enableGutters : config . get < boolean > ( 'enableGutters' , true ) , realtimeGutters : config . get < boolean > ( 'realtimeGutters' , true ) , showCommitInFloating : config . get < boolean > ( 'showCommitInFloating' , false ) , signPriority : config . get < number > ( 'signPriority' , 10 ) , pushArguments : config . get < string [ ] > ( 'pushArguments' , [ ] ) , splitWindowCommand : config . get < string > ( 'splitWindowCommand' , 'above sp' ) , changedSign : { text : config . get < string > ( 'changedSign.text' , '~' ) , hlGroup : config . get < string > ( 'changedSign.hlGroup' , 'DiffChange' ) } , addedSign : { text : config . get < string > ( 'addedSign.text' , '+' ) , hlGroup : config . get < string > ( 'addedSign.hlGroup' , 'DiffAdd' ) } , removedSign : { text : config . get < string > ( 'removedSign.text' , '_' ) , hlGroup : config . get < string > ( 'removedSign.hlGroup' , 'DiffDelete' ) } , topRemovedSign : { text : config . get < string > ( 'topRemovedSign.text' , '‾' ) , hlGroup : config . get < string > ( 'topRemovedSign.hlGroup' , 'DiffDelete' ) } , changeRemovedSign : { text : config . get < string > ( 'changeRemovedSign.text' , '≃' ) , hlGroup : config . get < string > ( 'changeRemovedSign.hlGroup' , 'DiffChange' ) } , conflict : { enabled : config . get < boolean > ( 'conflict.enabled' , true ) , currentHlGroup : config . get < string > ( 'conflict.current.hlGroup' , 'DiffChange' ) , incomingHlGroup : config . get < string > ( 'conflict.incoming.hlGroup' , 'DiffAdd' ) } , virtualTextSrcId : this . virtualTextSrcId , conflictSrcId : this . conflictSrcId } ; this . config = Object . assign ( this . config || { } , obj ) ; } private get enableGutters ( ) : boolean { return this . config . enableGutters ; } public get git ( ) : Git { return this . service . git ; } public async toggleGutters ( ) { let enabled = this . enableGutters ; let config = workspace . getConfiguration ( 'git' ) ; config . update ( 'enableGutters' , ! enabled , true ) ; for ( let buf of this . buffers . values ( ) ) { await buf . toggleGutters ( ! enabled ) ; } } public async toggleFold ( ) { let buf = await this . buffer ; if ( buf ) await buf . toggleFold ( ) ; } public async resolveGitRootFromBufferOrCwd ( bufnr ) { let doc = workspace . getDocument ( bufnr ) ; let root ; let { resolver } = this . service ; if ( doc ) { root = await resolver . resolveGitRoot ( doc ) ; } if ( ! root ) { root = await resolver . resolveRootFromCwd ( ) ; } return root ; } public async getCurrentChunk ( ) { const { nvim } = this ; let buf = await this . buffer ; if ( ! buf ) return ; let line = await nvim . call ( 'line' , '.' ) ; return buf . getChunk ( line ) ; } public async chunkInfo ( ) { let buf = await this . buffer ; if ( buf ) await buf . chunkInfo ( ) ; } public async nextChunk ( ) { let buf = await this . buffer ; if ( buf ) await buf . nextChunk ( ) ; } public async prevChunk ( ) { let buf = await this . buffer ; if ( buf ) await buf . prevChunk ( ) ; } public async nextConflict ( ) { let buf = await this . buffer ; if ( buf ) await buf . nextConflict ( ) ; } public async prevConflict ( ) { let buf = await this . buffer ; if ( buf ) await buf . prevConflict ( ) ; } public async keepCurrent ( ) { return this . conflictKeepPart ( ConflictPart . Current ) ; } public async keepIncoming ( ) { return this . conflictKeepPart ( ConflictPart . Incoming ) ; } public async keepBoth ( ) { return this . conflictKeepPart ( ConflictPart . Both ) ; } private async conflictKeepPart ( part ) { let buf = await this . buffer ; if ( buf ) await buf . conflictKeepPart ( part ) ; } public async chunkStage ( ) { let buf = await this . buffer ; if ( ! buf ) return ; await buf . chunkStage ( ) ; } public async chunkUndo ( ) { let buf = await this . buffer ; if ( buf ) await buf . chunkUndo ( ) ; } public async showCommit ( ) { let buf = await this . buffer ; if ( buf ) await buf . showCommit ( ) ; } public async browser ( action = 'open' , range ? ) { let buf = await this . buffer ; if ( buf ) await buf . browser ( action , range ) ; } public async diffCached ( ) { let buf = await this . buffer ; if ( buf ) await buf . diffCached ( ) ; } public refresh ( ) { for ( let buf of this . buffers . values ( ) ) { buf . refresh ( ) ; } } public async push ( args ) { let bufnr = await workspace . nvim . call ( 'bufnr' , '%' ) ; let root = await this . resolveGitRootFromBufferOrCwd ( bufnr ) ; let extra = this . config . pushArguments ; if ( ! root ) { window . showMessage ( `not belongs to git repository.` , 'warning' ) ; return ; } if ( args && args . length ) { await window . runTerminalCommand ( `git push ${ [ ... args , ... extra ] . join ( ' ' ) }` , root , true ) ; return ; } let repo = this . service . getRepoFromRoot ( root ) ; let output = await repo . safeRun ( [ 'remote' ] ) ; let remote = output . trim ( ) . split ( /\r?\n/ ) [ 0 ] ; if ( ! remote ) { window . showMessage ( `remote not found` , 'warning' ) ; return ; } output = await repo . safeRun ( [ 'rev-parse' , '--abbrev-ref' , 'HEAD' ] ) ; if ( ! output ) { window . showMessage ( `current branch not found` , 'warning' ) ; return ; } await window . runTerminalCommand ( `git push ${ remote } ${ output }${ extra . length ? ' ' + extra . join ( ' ' ) : '' }` , root , true ) ; } private get buffer ( ) : Promise < GitBuffer > { return workspace . nvim . call ( 'bufnr' , '%' ) . then ( bufnr => { let buf = this . buffers . get ( bufnr ) ; if ( ! buf ) window . showMessage ( `Cant't resolve git repository for current buffer.` , 'warning' ) ; return buf ; } ) ; } public getBuffer ( bufnr ) { return this . buffers . get ( bufnr ) ; } public dispose ( ) { this . gitStatus . dispose ( ) ; this . service . dispose ( ) ; for ( let buf of this . buffers . values ( ) ) { buf . dispose ( ) ; } this . buffers . clear ( ) ; disposeAll ( this . disposables ) ; } }