1/*--------------------------------------------------------------------------------------------- 2 * Copyright (c) Microsoft Corporation. All rights reserved. 3 * Licensed under the MIT License. See License.txt in the project root for license information. 4 *--------------------------------------------------------------------------------------------*/ 5 6import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode'; 7import { Repository, Operation } from './repository'; 8import { anyEvent, dispose, filterEvent } from './util'; 9import * as nls from 'vscode-nls'; 10import { Branch } from './api/git'; 11 12const localize = nls.loadMessageBundle(); 13 14class CheckoutStatusBar { 15 16 private _onDidChange = new EventEmitter<void>(); 17 get onDidChange(): Event<void> { return this._onDidChange.event; } 18 private disposables: Disposable[] = []; 19 20 constructor(private repository: Repository) { 21 repository.onDidRunGitStatus(this._onDidChange.fire, this._onDidChange, this.disposables); 22 } 23 24 get command(): Command | undefined { 25 const rebasing = !!this.repository.rebaseCommit; 26 const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`; 27 28 return { 29 command: 'git.checkout', 30 tooltip: `${this.repository.headLabel}`, 31 title, 32 arguments: [this.repository.sourceControl] 33 }; 34 } 35 36 dispose(): void { 37 this.disposables.forEach(d => d.dispose()); 38 } 39} 40 41interface SyncStatusBarState { 42 enabled: boolean; 43 isSyncRunning: boolean; 44 hasRemotes: boolean; 45 HEAD: Branch | undefined; 46} 47 48class SyncStatusBar { 49 50 private static StartState: SyncStatusBarState = { 51 enabled: true, 52 isSyncRunning: false, 53 hasRemotes: false, 54 HEAD: undefined 55 }; 56 57 private _onDidChange = new EventEmitter<void>(); 58 get onDidChange(): Event<void> { return this._onDidChange.event; } 59 private disposables: Disposable[] = []; 60 61 private _state: SyncStatusBarState = SyncStatusBar.StartState; 62 private get state() { return this._state; } 63 private set state(state: SyncStatusBarState) { 64 this._state = state; 65 this._onDidChange.fire(); 66 } 67 68 constructor(private repository: Repository) { 69 repository.onDidRunGitStatus(this.onModelChange, this, this.disposables); 70 repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables); 71 72 const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync')); 73 onEnablementChange(this.updateEnablement, this, this.disposables); 74 75 this._onDidChange.fire(); 76 } 77 78 private updateEnablement(): void { 79 const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); 80 const enabled = config.get<boolean>('enableStatusBarSync', true); 81 82 this.state = { ... this.state, enabled }; 83 } 84 85 private onOperationsChange(): void { 86 const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) || 87 this.repository.operations.isRunning(Operation.Push) || 88 this.repository.operations.isRunning(Operation.Pull); 89 90 this.state = { ...this.state, isSyncRunning }; 91 } 92 93 private onModelChange(): void { 94 this.state = { 95 ...this.state, 96 hasRemotes: this.repository.remotes.length > 0, 97 HEAD: this.repository.HEAD 98 }; 99 } 100 101 get command(): Command | undefined { 102 if (!this.state.enabled || !this.state.hasRemotes) { 103 return undefined; 104 } 105 106 const HEAD = this.state.HEAD; 107 let icon = '$(sync)'; 108 let text = ''; 109 let command = ''; 110 let tooltip = ''; 111 112 if (HEAD && HEAD.name && HEAD.commit) { 113 if (HEAD.upstream) { 114 if (HEAD.ahead || HEAD.behind) { 115 text += this.repository.syncLabel; 116 } 117 118 const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); 119 const rebaseWhenSync = config.get<string>('rebaseWhenSync'); 120 121 command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync'; 122 tooltip = localize('sync changes', "Synchronize Changes"); 123 } else { 124 icon = '$(cloud-upload)'; 125 command = 'git.publish'; 126 tooltip = localize('publish changes', "Publish Changes"); 127 } 128 } else { 129 command = ''; 130 tooltip = ''; 131 } 132 133 if (this.state.isSyncRunning) { 134 icon = '$(sync~spin)'; 135 command = ''; 136 tooltip = localize('syncing changes', "Synchronizing Changes..."); 137 } 138 139 return { 140 command, 141 title: [icon, text].join(' ').trim(), 142 tooltip, 143 arguments: [this.repository.sourceControl] 144 }; 145 } 146 147 dispose(): void { 148 this.disposables.forEach(d => d.dispose()); 149 } 150} 151 152export class StatusBarCommands { 153 154 private syncStatusBar: SyncStatusBar; 155 private checkoutStatusBar: CheckoutStatusBar; 156 private disposables: Disposable[] = []; 157 158 constructor(repository: Repository) { 159 this.syncStatusBar = new SyncStatusBar(repository); 160 this.checkoutStatusBar = new CheckoutStatusBar(repository); 161 } 162 163 get onDidChange(): Event<void> { 164 return anyEvent( 165 this.syncStatusBar.onDidChange, 166 this.checkoutStatusBar.onDidChange 167 ); 168 } 169 170 get commands(): Command[] { 171 const result: Command[] = []; 172 173 const checkout = this.checkoutStatusBar.command; 174 175 if (checkout) { 176 result.push(checkout); 177 } 178 179 const sync = this.syncStatusBar.command; 180 181 if (sync) { 182 result.push(sync); 183 } 184 185 return result; 186 } 187 188 dispose(): void { 189 this.syncStatusBar.dispose(); 190 this.checkoutStatusBar.dispose(); 191 this.disposables = dispose(this.disposables); 192 } 193} 194