Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working sets - add preserveFocus #213397

Merged
merged 5 commits into from
May 26, 2024
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
9 changes: 9 additions & 0 deletions src/vs/workbench/browser/parts/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ export interface IEditorGroupTitleHeight {
readonly offset: number;
}

export interface IEditorGroupViewOptions {

/**
* Whether the editor group should receive keyboard focus
* after creation or not.
*/
readonly preserveFocus?: boolean;
}

/**
* A helper to access and mutate an editor group within an editor part.
*/
Expand Down
21 changes: 11 additions & 10 deletions src/vs/workbench/browser/parts/editor/editorGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async';
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions, IEditorPartsView, IEditorGroupViewOptions } from 'vs/workbench/browser/parts/editor/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IAction, SubmenuAction } from 'vs/base/common/actions';
Expand Down Expand Up @@ -63,16 +63,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {

//#region factory

static createNew(editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, null, editorPartsView, groupsView, groupsLabel, groupIndex);
static createNew(editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService, options?: IEditorGroupViewOptions): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, null, editorPartsView, groupsView, groupsLabel, groupIndex, options);
}

static createFromSerialized(serialized: ISerializedEditorGroupModel, editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, serialized, editorPartsView, groupsView, groupsLabel, groupIndex);
static createFromSerialized(serialized: ISerializedEditorGroupModel, editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService, options?: IEditorGroupViewOptions): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, serialized, editorPartsView, groupsView, groupsLabel, groupIndex, options);
}

static createCopy(copyFrom: IEditorGroupView, editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, copyFrom, editorPartsView, groupsView, groupsLabel, groupIndex);
static createCopy(copyFrom: IEditorGroupView, editorPartsView: IEditorPartsView, groupsView: IEditorGroupsView, groupsLabel: string, groupIndex: number, instantiationService: IInstantiationService, options?: IEditorGroupViewOptions): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, copyFrom, editorPartsView, groupsView, groupsLabel, groupIndex, options);
}

//#endregion
Expand Down Expand Up @@ -145,6 +145,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
readonly groupsView: IEditorGroupsView,
private groupsLabel: string,
private _index: number,
options: IEditorGroupViewOptions | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
Expand Down Expand Up @@ -236,7 +237,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion

// Restore editors if provided
const restoreEditorsPromise = this.restoreEditors(from) ?? Promise.resolve();
const restoreEditorsPromise = this.restoreEditors(from, options) ?? Promise.resolve();

// Signal restored once editors have restored
restoreEditorsPromise.finally(() => {
Expand Down Expand Up @@ -534,7 +535,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.titleContainer.classList.toggle('show-file-icons', this.groupsView.partOptions.showIcons);
}

private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise<void> | undefined {
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null, groupViewOptions?: IEditorGroupViewOptions): Promise<void> | undefined {
if (this.count === 0) {
return; // nothing to show
}
Expand Down Expand Up @@ -571,7 +572,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// stolen accidentally on startup when the user already
// clicked somewhere.

if (this.groupsView.activeGroup === this && activeElement && isActiveElement(activeElement)) {
if (this.groupsView.activeGroup === this && activeElement && isActiveElement(activeElement) && !groupViewOptions?.preserveFocus) {
this.focus();
}
});
Expand Down
26 changes: 13 additions & 13 deletions src/vs/workbench/browser/parts/editor/editorPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGr
import { GroupIdentifier, EditorInputWithOptions, IEditorPartOptions, IEditorPartOptionsChangeEvent, GroupModelChangeKind } from 'vs/workbench/common/editor';
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
import { distinct, coalesce } from 'vs/base/common/arrays';
import { IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions, IEditorPartsView, IEditorGroupsView } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions, IEditorPartsView, IEditorGroupsView, IEditorGroupViewOptions } from 'vs/workbench/browser/parts/editor/editor';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
Expand Down Expand Up @@ -615,16 +615,16 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
}
}

private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroupModel | null): IEditorGroupView {
private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroupModel | null, options?: IEditorGroupViewOptions): IEditorGroupView {

// Create group view
let groupView: IEditorGroupView;
if (from instanceof EditorGroupView) {
groupView = EditorGroupView.createCopy(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService,);
groupView = EditorGroupView.createCopy(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService, options);
} else if (isSerializedEditorGroupModel(from)) {
groupView = EditorGroupView.createFromSerialized(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService);
groupView = EditorGroupView.createFromSerialized(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService, options);
} else {
groupView = EditorGroupView.createNew(this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService);
groupView = EditorGroupView.createNew(this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService, options);
}

// Keep in map
Expand Down Expand Up @@ -1192,7 +1192,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
return true; // success
}

private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[], options?: IEditorGroupViewOptions): void {

// Determine group views to reuse if any
let reuseGroupViews: IEditorGroupView[];
Expand All @@ -1210,7 +1210,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
if (reuseGroupViews.length > 0) {
groupView = reuseGroupViews.shift()!;
} else {
groupView = this.doCreateGroupView(serializedEditorGroup);
groupView = this.doCreateGroupView(serializedEditorGroup, options);
}

groupViews.push(groupView);
Expand Down Expand Up @@ -1342,15 +1342,15 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
};
}

applyState(state: IEditorPartUIState | 'empty'): Promise<void> {
applyState(state: IEditorPartUIState | 'empty', options?: IEditorGroupViewOptions): Promise<void> {
if (state === 'empty') {
return this.doApplyEmptyState();
} else {
return this.doApplyState(state);
return this.doApplyState(state, options);
}
}

private async doApplyState(state: IEditorPartUIState): Promise<void> {
private async doApplyState(state: IEditorPartUIState, options?: IEditorGroupViewOptions): Promise<void> {
const groups = await this.doPrepareApplyState();
const resumeEvents = this.disposeGroups(true /* suspress events for the duration of applying state */);

Expand All @@ -1359,7 +1359,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {

// Grid Widget
try {
this.doApplyGridState(state.serializedGrid, state.activeGroup);
this.doApplyGridState(state.serializedGrid, state.activeGroup, undefined, options);
} finally {
resumeEvents();
}
Expand Down Expand Up @@ -1396,10 +1396,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
return groups;
}

private doApplyGridState(gridState: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
private doApplyGridState(gridState: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[], options?: IEditorGroupViewOptions): void {

// Recreate grid widget from state
this.doCreateGridControlWithState(gridState, activeGroupId, editorGroupViewsToReuse);
this.doCreateGridControlWithState(gridState, activeGroupId, editorGroupViewsToReuse, options);

// Layout
this.doLayout(this._contentDimension);
Expand Down
20 changes: 11 additions & 9 deletions src/vs/workbench/browser/parts/editor/editorParts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { localize } from 'vs/nls';
import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IAuxiliaryEditorPartCreateEvent, IEditorGroupContextKeyProvider, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IEditorWorkingSet, IFindGroupScope, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IAuxiliaryEditorPartCreateEvent, IEditorGroupContextKeyProvider, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IEditorWorkingSet, IFindGroupScope, IMergeGroupOptions, IEditorWorkingSetOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { Emitter } from 'vs/base/common/event';
import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { GroupIdentifier } from 'vs/workbench/common/editor';
Expand Down Expand Up @@ -375,7 +375,7 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
}
}

async applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> {
async applyWorkingSet(workingSet: IEditorWorkingSet | 'empty', options?: IEditorWorkingSetOptions): Promise<boolean> {
let workingSetState: IEditorWorkingSetState | 'empty' | undefined;
if (workingSet === 'empty') {
workingSetState = 'empty';
Expand All @@ -395,13 +395,15 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
if (!applied) {
return false;
}
await this.mainPart.applyState(workingSetState === 'empty' ? workingSetState : workingSetState.main);

// Restore Focus
const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts);
if (mostRecentActivePart) {
await mostRecentActivePart.whenReady;
mostRecentActivePart.activeGroup.focus();
await this.mainPart.applyState(workingSetState === 'empty' ? workingSetState : workingSetState.main, options);

// Restore Focus unless instructed otherwise
if (!options?.preserveFocus) {
const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts);
if (mostRecentActivePart) {
await mostRecentActivePart.whenReady;
mostRecentActivePart.activeGroup.focus();
}
}

return true;
Expand Down
12 changes: 10 additions & 2 deletions src/vs/workbench/contrib/scm/browser/workingSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { getProviderKey } from 'vs/workbench/contrib/scm/browser/util';
import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
import { IEditorGroupsService, IEditorWorkingSet } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';

type ISCMSerializedWorkingSet = {
readonly providerKey: string;
Expand All @@ -35,7 +36,8 @@ export class SCMWorkingSetController implements IWorkbenchContribution {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@ISCMService private readonly scmService: ISCMService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.workingSets.enabled'), this._disposables);
this._disposables.add(Event.runAndSubscribe(onDidChangeConfiguration, () => this._onDidChangeConfiguration()));
Expand Down Expand Up @@ -147,7 +149,13 @@ export class SCMWorkingSetController implements IWorkbenchContribution {
}

if (editorWorkingSetId) {
await this.editorGroupsService.applyWorkingSet(editorWorkingSetId);
// Applying a working set can be the result of a user action that has been
// initiated from the terminal (ex: switching branches). As such, we want
// to preserve the focus in the terminal. This does not cover the scenario
// in which the terminal is in the editor part.
const preserveFocus = this.layoutService.hasFocus(Parts.PANEL_PART);

await this.editorGroupsService.applyWorkingSet(editorWorkingSetId, { preserveFocus });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ export interface IEditorWorkingSet {
readonly name: string;
}

export interface IEditorWorkingSetOptions {
readonly preserveFocus?: boolean;
}

export interface IEditorGroupContextKeyProvider<T extends ContextKeyValue> {

/**
Expand Down Expand Up @@ -573,7 +577,7 @@ export interface IEditorGroupsService extends IEditorGroupsContainer {
*
* @returns `true` when the working set as applied.
*/
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean>;
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty', options?: IEditorWorkingSetOptions): Promise<boolean>;

/**
* Deletes a working set.
Expand Down
6 changes: 3 additions & 3 deletions src/vs/workbench/test/browser/workbenchTestServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/common/decorations';
import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IMergeGroupOptions, IEditorReplacement, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter, IEditorDropTargetDelegate, IEditorPart, IAuxiliaryEditorPart, IEditorGroupsContainer, IAuxiliaryEditorPartCreateEvent, IEditorWorkingSet, IEditorGroupContextKeyProvider } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IMergeGroupOptions, IEditorReplacement, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter, IEditorDropTargetDelegate, IEditorPart, IAuxiliaryEditorPart, IEditorGroupsContainer, IAuxiliaryEditorPartCreateEvent, IEditorWorkingSet, IEditorGroupContextKeyProvider, IEditorWorkingSetOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, ISaveEditorsOptions, IRevertAllEditorsOptions, PreferredGroup, IEditorsChangeEvent, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor';
Expand Down Expand Up @@ -841,7 +841,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
getPart(group: number | IEditorGroup): IEditorPart { return this; }
saveWorkingSet(name: string): IEditorWorkingSet { throw new Error('Method not implemented.'); }
getWorkingSets(): IEditorWorkingSet[] { throw new Error('Method not implemented.'); }
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> { throw new Error('Method not implemented.'); }
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty', options?: IEditorWorkingSetOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
deleteWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }
getGroups(_order?: GroupsOrder): readonly IEditorGroup[] { return this.groups; }
getGroup(identifier: number): IEditorGroup | undefined { return this.groups.find(group => group.id === identifier); }
Expand Down Expand Up @@ -1841,7 +1841,7 @@ export class TestEditorPart extends MainEditorPart implements IEditorGroupsServi

saveWorkingSet(name: string): IEditorWorkingSet { throw new Error('Method not implemented.'); }
getWorkingSets(): IEditorWorkingSet[] { throw new Error('Method not implemented.'); }
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> { throw new Error('Method not implemented.'); }
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty', options?: IEditorWorkingSetOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
deleteWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }

registerContextKeyProvider<T extends ContextKeyValue>(provider: IEditorGroupContextKeyProvider<T>): IDisposable { throw new Error('Method not implemented.'); }
Expand Down
Loading