SCM - extract activity count badge into its own workbench contribution (#214610)

This commit is contained in:
Ladislau Szomoru 2024-06-07 16:39:20 +02:00 committed by GitHub
parent d08886ba68
commit 88f50e0809
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 107 additions and 42 deletions

View file

@ -230,10 +230,9 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider {
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
get actionButton(): ISCMActionButtonDescriptor | undefined { return this.features.actionButton ?? undefined; }
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
get count(): number | undefined { return this.features.count; }
private readonly _countObs = observableValue<number | undefined>(this, undefined);
get countObs() { return this._countObs; }
private readonly _count = observableValue<number | undefined>(this, undefined);
get count() { return this._count; }
private readonly _statusBarCommandsObs = observableValue<readonly Command[] | undefined>(this, undefined);
get statusBarCommandsObs() { return this._statusBarCommandsObs; }
@ -288,7 +287,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider {
}
if (typeof features.count !== 'undefined') {
this._countObs.set(features.count, undefined);
this._count.set(features.count, undefined);
}
if (typeof features.statusBarCommands !== 'undefined') {

View file

@ -22,9 +22,103 @@ import { ITitleService } from 'vs/workbench/services/title/browser/titleService'
import { IEditorGroupContextKeyProvider, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { getRepositoryResourceCount } from 'vs/workbench/contrib/scm/browser/util';
import { autorunWithStore, derived, derivedObservableWithCache, IObservable, observableFromEvent } from 'vs/base/common/observable';
import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils';
function getCount(repository: ISCMRepository): number {
return repository.provider.count ?? getRepositoryResourceCount(repository.provider);
export class SCMActivityCountBadgeController extends Disposable implements IWorkbenchContribution {
private readonly _countBadgeConfig = observableConfigValue<'all' | 'focused' | 'off'>('scm.countBadge', 'all', this.configurationService);
private readonly _repositories = observableFromEvent(
Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository),
() => this.scmService.repositories);
private readonly _focusedRepository = observableFromEvent(
this.scmViewService.onDidFocusRepository,
() => this.scmViewService.focusedRepository ? Object.create(this.scmViewService.focusedRepository) : undefined);
private readonly _activeEditor = observableFromEvent(
this.editorService.onDidActiveEditorChange,
() => this.editorService.activeEditor);
private readonly _activeEditorRepository = derived(reader => {
const activeResource = EditorResourceAccessor.getOriginalUri(this._activeEditor.read(reader));
if (!activeResource) {
return undefined;
}
return this.scmService.getRepository(activeResource);
});
private readonly _activeRepository = derivedObservableWithCache<ISCMRepository | undefined>(this, (reader, lastValue) => {
const focusedRepository = this._focusedRepository.read(reader);
if (focusedRepository && focusedRepository.id !== lastValue?.id) {
return focusedRepository;
}
const activeEditorRepository = this._activeEditorRepository.read(reader);
if (activeEditorRepository && activeEditorRepository.id !== lastValue?.id) {
return activeEditorRepository;
}
return lastValue;
});
private readonly _countBadgeRepositories = derived(reader => {
switch (this._countBadgeConfig.read(reader)) {
case 'all': {
const repositories = this._repositories.read(reader);
return [...Iterable.map(repositories, r => ({ ...r.provider, resourceCount: this._getRepositoryResourceCount(r) }))];
}
case 'focused': {
const repository = this._activeRepository.read(reader);
return repository ? [{ ...repository.provider, resourceCount: this._getRepositoryResourceCount(repository) }] : [];
}
case 'off':
return [];
default:
throw new Error('Invalid countBadge setting');
}
});
private readonly _countBadge = derived(reader => {
let total = 0;
for (const repository of this._countBadgeRepositories.read(reader)) {
const count = repository.count?.read(reader);
const resourceCount = repository.resourceCount.read(reader);
total = total + (count ?? resourceCount);
}
return total;
});
constructor(
@IActivityService private readonly activityService: IActivityService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@ISCMService private readonly scmService: ISCMService,
@ISCMViewService private readonly scmViewService: ISCMViewService
) {
super();
this._register(autorunWithStore((reader, store) => {
this._renderActivityCount(this._countBadge.read(reader), store);
}));
}
private _getRepositoryResourceCount(repository: ISCMRepository): IObservable<number> {
return observableFromEvent(repository.provider.onDidChangeResources, () => getRepositoryResourceCount(repository.provider));
}
private _renderActivityCount(count: number, store: DisposableStore): void {
if (count === 0) {
return;
}
const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num));
store.add(this.activityService.showViewActivity(VIEW_PANE_ID, { badge }));
}
}
export class SCMStatusController implements IWorkbenchContribution {
@ -40,16 +134,11 @@ export class SCMStatusController implements IWorkbenchContribution {
@ISCMService private readonly scmService: ISCMService,
@ISCMViewService private readonly scmViewService: ISCMViewService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IActivityService private readonly activityService: IActivityService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IEditorService private readonly editorService: IEditorService
) {
this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
const onDidChangeSCMCountBadge = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.countBadge'));
onDidChangeSCMCountBadge(this.renderActivityCount, this, this.disposables);
for (const repository of this.scmService.repositories) {
this.onDidAddRepository(repository);
}
@ -58,7 +147,6 @@ export class SCMStatusController implements IWorkbenchContribution {
this.focusRepository(this.scmViewService.focusedRepository);
editorService.onDidActiveEditorChange(() => this.tryFocusRepositoryBasedOnActiveEditor(), this, this.disposables);
this.renderActivityCount();
}
private tryFocusRepositoryBasedOnActiveEditor(repositories: Iterable<ISCMRepository> = this.scmService.repositories): boolean {
@ -78,17 +166,13 @@ export class SCMStatusController implements IWorkbenchContribution {
}
private onDidAddRepository(repository: ISCMRepository): void {
const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources);
const changeDisposable = onDidChange(() => this.renderActivityCount());
const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository);
const removeDisposable = onDidRemove(() => {
disposable.dispose();
this.repositoryDisposables.delete(disposable);
this.renderActivityCount();
});
const disposable = combinedDisposable(changeDisposable, removeDisposable);
const disposable = combinedDisposable(removeDisposable);
this.repositoryDisposables.add(disposable);
this.tryFocusRepositoryBasedOnActiveEditor(Iterable.single(repository));
@ -115,7 +199,6 @@ export class SCMStatusController implements IWorkbenchContribution {
}
this.renderStatusBar(repository);
this.renderActivityCount();
}
private renderStatusBar(repository: ISCMRepository | undefined): void {
@ -166,25 +249,6 @@ export class SCMStatusController implements IWorkbenchContribution {
this.statusBarDisposable = disposables;
}
private renderActivityCount(): void {
const countBadgeType = this.configurationService.getValue<'all' | 'focused' | 'off'>('scm.countBadge');
let count = 0;
if (countBadgeType === 'all') {
count = Iterable.reduce(this.scmService.repositories, (r, repository) => r + getCount(repository), 0);
} else if (countBadgeType === 'focused' && this.focusedRepository) {
count = getCount(this.focusedRepository);
}
if (count > 0) {
const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num));
this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge });
} else {
this.badgeDisposable.value = undefined;
}
}
dispose(): void {
this.focusDisposable.dispose();
this.statusBarDisposable.dispose();

View file

@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMStatusController } from './activity';
import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMActivityCountBadgeController, SCMStatusController } from './activity';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@ -112,6 +112,9 @@ viewsRegistry.registerViews([{
containerIcon: sourceControlViewIcon
}], viewContainer);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(SCMActivityCountBadgeController, LifecyclePhase.Restored);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored);

View file

@ -117,7 +117,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer<ISCMReposit
}));
templateData.elementDisposables.add(autorun(reader => {
const count = repository.provider.countObs.read(reader) ?? getRepositoryResourceCount(repository.provider);
const count = repository.provider.count.read(reader) ?? getRepositoryResourceCount(repository.provider);
templateData.countContainer.setAttribute('data-count', String(count));
templateData.count.setCount(count);
}));

View file

@ -73,8 +73,7 @@ export interface ISCMProvider extends IDisposable {
readonly rootUri?: URI;
readonly inputBoxTextModel: ITextModel;
readonly count?: number;
readonly countObs: IObservable<number | undefined>;
readonly count: IObservable<number | undefined>;
readonly commitTemplate: IObservable<string>;
readonly historyProvider?: ISCMHistoryProvider;
readonly onDidChangeHistoryProvider: Event<void>;