SCM - add commands to focus next/previous input (#213337)

* SCM - refactor commands to focus input

* Ensure that the input node is expanded in the tree

* getParent() should handle expanding the repository node

* Use ArrayNavigator instead

* Add support for looping in the array navigator

* 💄

* use rot instead of array navigator

* 💄

* more 💄

---------

Co-authored-by: João Moreno <joao.moreno@microsoft.com>
This commit is contained in:
Ladislau Szomoru 2024-05-24 06:15:12 +00:00 committed by GitHub
parent e82e806f17
commit 994893cbba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 35 deletions

View file

@ -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<SCMViewPane>(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<SCMViewPane>(VIEW_PANE_ID);
if (scmView) {
scmView.focusNextInput();
}
}
});
registerSingleton(ISCMService, SCMService, InstantiationType.Delayed);
registerSingleton(ISCMViewService, SCMViewService, InstantiationType.Delayed);
registerSingleton(IQuickDiffService, QuickDiffService, InstantiationType.Delayed);

View file

@ -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<ISCMResource, ISCMResourceGroup>;
// type SCMHistoryItemChangeResourceTreeNode = IResourceNode<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>;
@ -1462,7 +1461,7 @@ const Menus = {
ChangesSettings: new MenuId('SCMChangesSettings'),
};
const ContextKeys = {
export const ContextKeys = {
SCMViewMode: new RawContextKey<ViewMode>('scmViewMode', ViewMode.List),
SCMViewSortKey: new RawContextKey<ViewSortKey>('scmViewSortKey', ViewSortKey.Path),
SCMViewAreAllRepositoriesCollapsed: new RawContextKey<boolean>('scmViewAreAllRepositoriesCollapsed', false),
@ -1958,26 +1957,6 @@ class ExpandAllRepositoriesAction extends ViewAction<SCMViewPane> {
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<SCMViewPane>(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<void>(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<void> {
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<ISCMViewService, TreeElement
}
return result;
} else if (isSCMInput(element)) {
return element.repository;
} else {
throw new Error('Unexpected call to getParent');
}