Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions src/vs/sessions/contrib/chat/browser/syncIndicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as dom from '../../../../base/browser/dom.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { autorun } from '../../../../base/common/observable.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
import { localize } from '../../../../nls.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
Expand All @@ -24,6 +25,7 @@ export class SyncIndicator extends Disposable {
private _repository: IGitRepository | undefined;
private _selectedBranch: string | undefined;
private _visible = true;
private _syncing = false;

private readonly _renderDisposables = this._register(new DisposableStore());
private readonly _stateDisposables = this._register(new DisposableStore());
Expand Down Expand Up @@ -81,13 +83,13 @@ export class SyncIndicator extends Disposable {

this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.CLICK, (e) => {
dom.EventHelper.stop(e, true);
this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri);
this._executeSyncCommand();
}));

this._renderDisposables.add(dom.addDisposableListener(button, dom.EventType.KEY_DOWN, (e) => {
if (e.key === 'Enter' || e.key === ' ') {
dom.EventHelper.stop(e, true);
this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri);
this._executeSyncCommand();
}
}));

Expand All @@ -102,6 +104,20 @@ export class SyncIndicator extends Disposable {
this._update();
}

private async _executeSyncCommand(): Promise<void> {
if (this._syncing) {
return;
}
this._syncing = true;
this._update();
try {
await this.commandService.executeCommand(GIT_SYNC_COMMAND, this._repository?.rootUri);
Comment thread
benibenj marked this conversation as resolved.
} finally {
this._syncing = false;
this._update();
}
}

private _getAheadBehind(): { ahead: number; behind: number } | undefined {
if (!this._repository) {
return undefined;
Expand Down Expand Up @@ -132,32 +148,34 @@ export class SyncIndicator extends Disposable {
}

const counts = this._getAheadBehind();
if (!counts || !this._visible) {
if ((!counts && !this._syncing) || !this._visible) {
this._slotElement.style.display = 'none';
return;
}

this._slotElement.style.display = '';

dom.clearNode(this._buttonElement);
dom.append(this._buttonElement, renderIcon(Codicon.sync));
dom.append(this._buttonElement, renderIcon(this._syncing ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.sync));

const parts: string[] = [];
if (counts.behind > 0) {
parts.push(`${counts.behind}↓`);
}
if (counts.ahead > 0) {
parts.push(`${counts.ahead}↑`);
}
if (counts) {
const parts: string[] = [];
if (counts.behind > 0) {
parts.push(`${counts.behind}↓`);
}
if (counts.ahead > 0) {
parts.push(`${counts.ahead}↑`);
}

const label = dom.append(this._buttonElement, dom.$('span.sessions-chat-dropdown-label'));
label.textContent = parts.join('\u00a0');
const label = dom.append(this._buttonElement, dom.$('span.sessions-chat-dropdown-label'));
label.textContent = parts.join('\u00a0');
}

Comment thread
benibenj marked this conversation as resolved.
this._buttonElement.title = localize(
'syncIndicator.tooltip',
"Synchronize Changes ({0} to pull, {1} to push)",
counts.behind,
counts.ahead,
counts?.behind ?? 0,
counts?.ahead ?? 0,
);
}
}
22 changes: 17 additions & 5 deletions src/vs/sessions/contrib/gitSync/browser/gitSync.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/

import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { autorun } from '../../../../base/common/observable.js';
import { autorun, observableValue } from '../../../../base/common/observable.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { localize } from '../../../../nls.js';
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
Expand All @@ -27,6 +28,7 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution {

private readonly _syncActionDisposable = this._register(new MutableDisposable());
private readonly _gitRepoDisposables = this._register(new DisposableStore());
private readonly _isSyncing = observableValue<boolean>(this, false);

constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService,
Expand Down Expand Up @@ -60,6 +62,7 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution {
}
repoDisposables.add(autorun(innerReader => {
const state = repository.state.read(innerReader);
const isSyncing = this._isSyncing.read(innerReader);
const head = state.HEAD;
if (!head?.upstream) {
this._syncActionDisposable.clear();
Expand All @@ -70,14 +73,16 @@ class GitSyncContribution extends Disposable implements IWorkbenchContribution {
const behind = head.behind ?? 0;
const hasSyncChanges = ahead > 0 || behind > 0;
contextKey.set(hasSyncChanges);
this._syncActionDisposable.value = registerSyncAction(behind, ahead);
this._syncActionDisposable.value = registerSyncAction(behind, ahead, isSyncing, (syncing) => {
this._isSyncing.set(syncing, undefined);
});
}));
});
}));
}
}

function registerSyncAction(behind: number, ahead: number): IDisposable {
function registerSyncAction(behind: number, ahead: number, isSyncing: boolean, setSyncing: (syncing: boolean) => void): IDisposable {
if (behind === 0 && ahead === 0) {
return Disposable.None;
}
Expand All @@ -89,6 +94,8 @@ function registerSyncAction(behind: number, ahead: number): IDisposable {
title += `${ahead}↑`;
}

const icon = isSyncing ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.sync;

class SynchronizeChangesAction extends Action2 {
static readonly ID = 'chatEditing.synchronizeChanges';

Expand All @@ -97,7 +104,7 @@ function registerSyncAction(behind: number, ahead: number): IDisposable {
id: SynchronizeChangesAction.ID,
title,
tooltip: localize('synchronizeChanges', "Synchronize Changes with Git (Behind {0}, Ahead {1})", behind, ahead),
icon: Codicon.sync,
icon,
category: CHAT_CATEGORY,
menu: [
{
Expand All @@ -114,7 +121,12 @@ function registerSyncAction(behind: number, ahead: number): IDisposable {
const commandService = accessor.get(ICommandService);
const sessionManagementService = accessor.get(ISessionsManagementService);
const worktreeUri = sessionManagementService.getActiveSession()?.worktree;
await commandService.executeCommand('git.sync', worktreeUri);
setSyncing(true);
try {
await commandService.executeCommand('git.sync', worktreeUri);
} finally {
setSyncing(false);
}
}
}
return registerAction2(SynchronizeChangesAction);
Expand Down
Loading