diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 70892dfb7991a..bceac6846e098 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -24,7 +24,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ModesRegistry } from 'vs/editor/common/languages/modesRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane'; +import { ContextKeys, SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane'; import { SCMViewService } from 'vs/workbench/contrib/scm/browser/scmViewService'; import { SCMRepositoriesViewPane } from 'vs/workbench/contrib/scm/browser/scmRepositoriesViewPane'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -34,6 +34,7 @@ import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; import { getActiveElement } from 'vs/base/browser/dom'; import { SCMWorkingSetController } from 'vs/workbench/contrib/scm/browser/workingSet'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -475,6 +476,32 @@ MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { when: ContextKeyExpr.and(ContextKeyExpr.equals('scmProviderHasRootUri', true), ContextKeyExpr.or(ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'integrated'), ContextKeyExpr.equals('config.terminal.sourceControlRepositoriesKind', 'both'))) }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.scm.action.focusPreviousInput', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeys.RepositoryVisibilityCount.notEqualsTo(0), + handler: async accessor => { + const viewsService = accessor.get(IViewsService); + const scmView = await viewsService.openView(VIEW_PANE_ID); + if (scmView) { + scmView.focusPreviousInput(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.scm.action.focusNextInput', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeys.RepositoryVisibilityCount.notEqualsTo(0), + handler: async accessor => { + const viewsService = accessor.get(IViewsService); + const scmView = await viewsService.openView(VIEW_PANE_ID); + if (scmView) { + scmView.focusNextInput(); + } + } +}); + registerSingleton(ISCMService, SCMService, InstantiationType.Delayed); registerSingleton(ISCMViewService, SCMViewService, InstantiationType.Delayed); registerSingleton(IQuickDiffService, QuickDiffService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 185aa10316fc5..d1f301eda14cf 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -38,7 +38,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { compareFileNames, comparePaths } from 'vs/base/common/comparers'; import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { localize, localize2 } from 'vs/nls'; +import { localize } from 'vs/nls'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; @@ -100,7 +100,7 @@ import { foreground, listActiveSelectionForeground, registerColor, transparent } import { IMenuWorkbenchToolBarOptions, MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; -import { clamp } from 'vs/base/common/numbers'; +import { clamp, rot } from 'vs/base/common/numbers'; import { ILogService } from 'vs/platform/log/common/log'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { MarkdownString } from 'vs/base/common/htmlContent'; @@ -109,7 +109,6 @@ import { IHoverService } from 'vs/platform/hover/browser/hover'; import { OpenScmGroupAction } from 'vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver'; import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; import { ITextModel } from 'vs/editor/common/model'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; // type SCMResourceTreeNode = IResourceNode; // type SCMHistoryItemChangeResourceTreeNode = IResourceNode; @@ -1462,7 +1461,7 @@ const Menus = { ChangesSettings: new MenuId('SCMChangesSettings'), }; -const ContextKeys = { +export const ContextKeys = { SCMViewMode: new RawContextKey('scmViewMode', ViewMode.List), SCMViewSortKey: new RawContextKey('scmViewSortKey', ViewSortKey.Path), SCMViewAreAllRepositoriesCollapsed: new RawContextKey('scmViewAreAllRepositoriesCollapsed', false), @@ -1958,26 +1957,6 @@ class ExpandAllRepositoriesAction extends ViewAction { registerAction2(CollapseAllRepositoriesAction); registerAction2(ExpandAllRepositoriesAction); -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.scm.action.focusInput', - title: { ...localize2('focusInput', "Focus Input") }, - category: localize2('source control', "Source Control"), - precondition: ContextKeys.RepositoryCount.notEqualsTo(0), - f1: true - }); - } - - override async run(accessor: ServicesAccessor) { - const viewsService = accessor.get(IViewsService); - const scmView = await viewsService.openView(VIEW_PANE_ID); - if (scmView) { - scmView.focusInput(); - } - } -}); - const enum SCMInputWidgetCommandId { CancelAction = 'scm.input.cancelAction' } @@ -3453,17 +3432,39 @@ export class SCMViewPane extends ViewPane { } } - focusInput(): void { - this.treeOperationSequencer.queue(() => { - return new Promise(resolve => { - if (this.scmViewService.focusedRepository) { - this.tree.reveal(this.scmViewService.focusedRepository.input, 0.5); - this.inputRenderer.getRenderedInputWidget(this.scmViewService.focusedRepository.input)?.focus(); - } + focusPreviousInput(): void { + this.treeOperationSequencer.queue(() => this.focusInput(-1)); + } - resolve(); - }); - }); + focusNextInput(): void { + this.treeOperationSequencer.queue(() => this.focusInput(1)); + } + + private async focusInput(delta: number): Promise { + if (!this.scmViewService.focusedRepository || + this.scmViewService.visibleRepositories.length === 0) { + return; + } + + let input = this.scmViewService.focusedRepository.input; + const repositories = this.scmViewService.visibleRepositories; + + // One visible repository and the input is already focused + if (repositories.length === 1 && this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === true) { + return; + } + + // Multiple visible repositories and the input already focused + if (repositories.length > 1 && this.inputRenderer.getRenderedInputWidget(input)?.hasFocus() === true) { + const focusedRepositoryIndex = repositories.indexOf(this.scmViewService.focusedRepository); + const newFocusedRepositoryIndex = rot(focusedRepositoryIndex + delta, repositories.length); + input = repositories[newFocusedRepositoryIndex].input; + } + + await this.tree.expandTo(input); + + this.tree.reveal(input); + this.inputRenderer.getRenderedInputWidget(input)?.focus(); } override shouldShowWelcome(): boolean { @@ -3847,6 +3848,8 @@ class SCMTreeDataSource implements IAsyncDataSource