mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Benibenj/registerContextKeyHandler (#213135)
* cleanup editor group context keys
* Update src/vs/workbench/browser/parts/editor/editorPart.ts
Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
* context key on parts
* Update global context keys
* remove scoped keys on group removal
* cleanup
* first draft contexkt key registration
* Make it a provider
* Use group instead of active editor
* getGroupContextKeyValue
* doc
* Fix merge error
* 💄
---------
Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
This commit is contained in:
parent
343a048566
commit
ca45e3d78e
|
@ -4,9 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IAuxiliaryEditorPartCreateEvent, 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 } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { EditorPart, IEditorPartUIState, MainEditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
|
@ -46,7 +46,7 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
private mostRecentActiveParts = [this.mainPart];
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IInstantiationService protected readonly instantiationService: IInstantiationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService,
|
||||
|
@ -62,6 +62,7 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
|
||||
private registerListeners(): void {
|
||||
this._register(this.onDidChangeMementoValue(StorageScope.WORKSPACE, this._store)(e => this.onDidChangeMementoState(e)));
|
||||
this.whenReady.then(() => this.registerGroupsContextKeyListeners());
|
||||
}
|
||||
|
||||
protected createMainEditorPart(): MainEditorPart {
|
||||
|
@ -123,15 +124,9 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
}));
|
||||
disposables.add(toDisposable(() => this.doUpdateMostRecentActive(part)));
|
||||
|
||||
disposables.add(part.onDidChangeActiveGroup(group => {
|
||||
this.updateGlobalContextKeys();
|
||||
this._onDidActiveGroupChange.fire(group);
|
||||
}));
|
||||
disposables.add(part.onDidChangeActiveGroup(group => this._onDidActiveGroupChange.fire(group)));
|
||||
disposables.add(part.onDidAddGroup(group => this._onDidAddGroup.fire(group)));
|
||||
disposables.add(part.onDidRemoveGroup(group => {
|
||||
this.removeGroupScopedContextKeys(group);
|
||||
this._onDidRemoveGroup.fire(group);
|
||||
}));
|
||||
disposables.add(part.onDidRemoveGroup(group => this._onDidRemoveGroup.fire(group)));
|
||||
disposables.add(part.onDidMoveGroup(group => this._onDidMoveGroup.fire(group)));
|
||||
disposables.add(part.onDidActivateGroup(group => this._onDidActivateGroup.fire(group)));
|
||||
disposables.add(part.onDidChangeGroupMaximized(maximized => this._onDidChangeGroupMaximized.fire(maximized)));
|
||||
|
@ -456,7 +451,7 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region Editor Groups Service
|
||||
//#region Group Management
|
||||
|
||||
get activeGroup(): IEditorGroupView {
|
||||
return this.activePart.activeGroup;
|
||||
|
@ -635,9 +630,40 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
return this.getPart(container).createEditorDropTarget(container, delegate);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Editor Group Context Key Handling
|
||||
|
||||
private readonly globalContextKeys = new Map<string, IContextKey<ContextKeyValue>>();
|
||||
private readonly scopedContextKeys = new Map<GroupIdentifier, Map<string, IContextKey<ContextKeyValue>>>();
|
||||
|
||||
private registerGroupsContextKeyListeners(): void {
|
||||
this._register(this.onDidChangeActiveGroup(() => this.updateGlobalContextKeys()));
|
||||
this.groups.forEach(group => this.registerGroupContextKeyProvidersListeners(group));
|
||||
this._register(this.onDidAddGroup(group => this.registerGroupContextKeyProvidersListeners(group)));
|
||||
this._register(this.onDidRemoveGroup(group => {
|
||||
this.scopedContextKeys.delete(group.id);
|
||||
this.registeredContextKeys.delete(group.id);
|
||||
this.contextKeyProviderDisposables.deleteAndDispose(group.id);
|
||||
}));
|
||||
}
|
||||
|
||||
private updateGlobalContextKeys(): void {
|
||||
const activeGroupScopedContextKeys = this.scopedContextKeys.get(this.activeGroup.id);
|
||||
if (!activeGroupScopedContextKeys) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, globalContextKey] of this.globalContextKeys) {
|
||||
const scopedContextKey = activeGroupScopedContextKeys.get(key);
|
||||
if (scopedContextKey) {
|
||||
globalContextKey.set(scopedContextKey.get());
|
||||
} else {
|
||||
globalContextKey.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bind<T extends ContextKeyValue>(contextKey: RawContextKey<T>, group: IEditorGroupView): IContextKey<T> {
|
||||
|
||||
// Ensure we only bind to the same context key once globaly
|
||||
|
@ -679,27 +705,70 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
|
|||
};
|
||||
}
|
||||
|
||||
private updateGlobalContextKeys(): void {
|
||||
const activeGroupScopedContextKeys = this.scopedContextKeys.get(this.activeGroup.id);
|
||||
if (!activeGroupScopedContextKeys) {
|
||||
return;
|
||||
private readonly contextKeyProviders = new Map<string, IEditorGroupContextKeyProvider<ContextKeyValue>>();
|
||||
private readonly registeredContextKeys = new Map<GroupIdentifier, Map<string, IContextKey>>();
|
||||
|
||||
registerContextKeyProvider<T extends ContextKeyValue>(provider: IEditorGroupContextKeyProvider<T>): IDisposable {
|
||||
if (this.contextKeyProviders.has(provider.contextKey.key) || this.globalContextKeys.has(provider.contextKey.key)) {
|
||||
throw new Error(`A context key provider for key ${provider.contextKey.key} already exists.`);
|
||||
}
|
||||
|
||||
for (const [key, globalContextKey] of this.globalContextKeys) {
|
||||
const scopedContextKey = activeGroupScopedContextKeys.get(key);
|
||||
if (scopedContextKey) {
|
||||
globalContextKey.set(scopedContextKey.get());
|
||||
} else {
|
||||
globalContextKey.reset();
|
||||
this.contextKeyProviders.set(provider.contextKey.key, provider);
|
||||
|
||||
const setContextKeyForGroups = () => {
|
||||
for (const group of this.groups) {
|
||||
this.updateRegisteredContextKey(group, provider);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run initially and on change
|
||||
setContextKeyForGroups();
|
||||
const onDidChange = provider.onDidChange?.(() => setContextKeyForGroups());
|
||||
|
||||
return toDisposable(() => {
|
||||
onDidChange?.dispose();
|
||||
|
||||
this.globalContextKeys.delete(provider.contextKey.key);
|
||||
this.scopedContextKeys.forEach(scopedContextKeys => scopedContextKeys.delete(provider.contextKey.key));
|
||||
|
||||
this.contextKeyProviders.delete(provider.contextKey.key);
|
||||
this.registeredContextKeys.forEach(registeredContextKeys => registeredContextKeys.delete(provider.contextKey.key));
|
||||
});
|
||||
}
|
||||
|
||||
private removeGroupScopedContextKeys(group: IEditorGroupView): void {
|
||||
const groupScopedContextKeys = this.scopedContextKeys.get(group.id);
|
||||
if (groupScopedContextKeys) {
|
||||
this.scopedContextKeys.delete(group.id);
|
||||
private readonly contextKeyProviderDisposables = this._register(new DisposableMap<GroupIdentifier, IDisposable>());
|
||||
private registerGroupContextKeyProvidersListeners(group: IEditorGroupView): void {
|
||||
|
||||
// Update context keys from providers for the group when its active editor changes
|
||||
const disposable = group.onDidActiveEditorChange(() => {
|
||||
for (const contextKeyProvider of this.contextKeyProviders.values()) {
|
||||
this.updateRegisteredContextKey(group, contextKeyProvider);
|
||||
}
|
||||
});
|
||||
|
||||
this.contextKeyProviderDisposables.set(group.id, disposable);
|
||||
}
|
||||
|
||||
private updateRegisteredContextKey<T extends ContextKeyValue>(group: IEditorGroupView, provider: IEditorGroupContextKeyProvider<T>): void {
|
||||
|
||||
// Get the group scoped context keys for the provider
|
||||
// If the providers context key has not yet been bound
|
||||
// to the group, do so now.
|
||||
|
||||
let groupRegisteredContextKeys = this.registeredContextKeys.get(group.id);
|
||||
if (!groupRegisteredContextKeys) {
|
||||
groupRegisteredContextKeys = new Map<string, IContextKey>();
|
||||
this.scopedContextKeys.set(group.id, groupRegisteredContextKeys);
|
||||
}
|
||||
|
||||
let scopedRegisteredContextKey = groupRegisteredContextKeys.get(provider.contextKey.key);
|
||||
if (!scopedRegisteredContextKey) {
|
||||
scopedRegisteredContextKey = this.bind(provider.contextKey, group);
|
||||
groupRegisteredContextKeys.set(provider.contextKey.key, scopedRegisteredContextKey);
|
||||
}
|
||||
|
||||
// Set the context key value for the group context
|
||||
scopedRegisteredContextKey.set(provider.getGroupContextKeyValue(group));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { localize } from 'vs/nls';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
|
@ -19,6 +19,8 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
|
|||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
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';
|
||||
|
||||
function getCount(repository: ISCMRepository): number {
|
||||
if (typeof repository.provider.count === 'number') {
|
||||
|
@ -291,19 +293,17 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri
|
|||
|
||||
export class SCMActiveResourceContextKeyController implements IWorkbenchContribution {
|
||||
|
||||
private activeResourceHasChangesContextKey: IContextKey<boolean>;
|
||||
private activeResourceRepositoryContextKey: IContextKey<string | undefined>;
|
||||
private readonly disposables = new DisposableStore();
|
||||
private repositoryDisposables = new Set<IDisposable>();
|
||||
private onDidRepositoryChange = new Emitter<void>();
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupsService: IEditorGroupsService,
|
||||
@ISCMService private readonly scmService: ISCMService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
|
||||
) {
|
||||
this.activeResourceHasChangesContextKey = contextKeyService.createKey('scmActiveResourceHasChanges', false);
|
||||
this.activeResourceRepositoryContextKey = contextKeyService.createKey('scmActiveResourceRepository', undefined);
|
||||
const activeResourceHasChangesContextKey = new RawContextKey<boolean>('scmActiveResourceHasChanges', false, localize('scmActiveResourceHasChanges', "Whether the active resource has changes"));
|
||||
const activeResourceRepositoryContextKey = new RawContextKey<string | undefined>('scmActiveResourceRepository', undefined, localize('scmActiveResourceRepository', "The active resource's repository"));
|
||||
|
||||
this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
|
||||
|
||||
|
@ -311,26 +311,42 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu
|
|||
this.onDidAddRepository(repository);
|
||||
}
|
||||
|
||||
editorService.onDidActiveEditorChange(this.updateContextKey, this, this.disposables);
|
||||
// Create context key providers which will update the context keys based on each groups active editor
|
||||
const hasChangesContextKeyProvider: IEditorGroupContextKeyProvider<boolean> = {
|
||||
contextKey: activeResourceHasChangesContextKey,
|
||||
getGroupContextKeyValue: (group) => this.getEditorHasChanges(group.activeEditor),
|
||||
onDidChange: this.onDidRepositoryChange.event
|
||||
};
|
||||
|
||||
const repositoryContextKeyProvider: IEditorGroupContextKeyProvider<string | undefined> = {
|
||||
contextKey: activeResourceRepositoryContextKey,
|
||||
getGroupContextKeyValue: (group) => this.getEditorRepositoryId(group.activeEditor),
|
||||
onDidChange: this.onDidRepositoryChange.event
|
||||
};
|
||||
|
||||
this.disposables.add(editorGroupsService.registerContextKeyProvider(hasChangesContextKeyProvider));
|
||||
this.disposables.add(editorGroupsService.registerContextKeyProvider(repositoryContextKeyProvider));
|
||||
}
|
||||
|
||||
private onDidAddRepository(repository: ISCMRepository): void {
|
||||
const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources);
|
||||
const changeDisposable = onDidChange(() => this.updateContextKey());
|
||||
const changeDisposable = onDidChange(() => {
|
||||
this.onDidRepositoryChange.fire();
|
||||
});
|
||||
|
||||
const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository);
|
||||
const removeDisposable = onDidRemove(() => {
|
||||
disposable.dispose();
|
||||
this.repositoryDisposables.delete(disposable);
|
||||
this.updateContextKey();
|
||||
this.onDidRepositoryChange.fire();
|
||||
});
|
||||
|
||||
const disposable = combinedDisposable(changeDisposable, removeDisposable);
|
||||
this.repositoryDisposables.add(disposable);
|
||||
}
|
||||
|
||||
private updateContextKey(): void {
|
||||
const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor);
|
||||
private getEditorRepositoryId(activeEditor: EditorInput | null): string | undefined {
|
||||
const activeResource = EditorResourceAccessor.getOriginalUri(activeEditor);
|
||||
|
||||
if (activeResource?.scheme === Schemas.file || activeResource?.scheme === Schemas.vscodeRemote) {
|
||||
const activeResourceRepository = Iterable.find(
|
||||
|
@ -338,27 +354,37 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu
|
|||
r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri))
|
||||
);
|
||||
|
||||
this.activeResourceRepositoryContextKey.set(activeResourceRepository?.id);
|
||||
return activeResourceRepository?.id;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getEditorHasChanges(activeEditor: EditorInput | null): boolean {
|
||||
const activeResource = EditorResourceAccessor.getOriginalUri(activeEditor);
|
||||
|
||||
if (activeResource?.scheme === Schemas.file || activeResource?.scheme === Schemas.vscodeRemote) {
|
||||
const activeResourceRepository = Iterable.find(
|
||||
this.scmService.repositories,
|
||||
r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri))
|
||||
);
|
||||
|
||||
for (const resourceGroup of activeResourceRepository?.provider.groups ?? []) {
|
||||
if (resourceGroup.resources
|
||||
.some(scmResource =>
|
||||
this.uriIdentityService.extUri.isEqual(activeResource, scmResource.sourceUri))) {
|
||||
this.activeResourceHasChangesContextKey.set(true);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.activeResourceHasChangesContextKey.set(false);
|
||||
} else {
|
||||
this.activeResourceHasChangesContextKey.set(false);
|
||||
this.activeResourceRepositoryContextKey.set(undefined);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
dispose(this.repositoryDisposables.values());
|
||||
this.repositoryDisposables.clear();
|
||||
this.onDidRepositoryChange.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
|||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDimension } from 'vs/editor/common/core/dimension';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyValue, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IGroupModelChangeEvent } from 'vs/workbench/common/editor/editorGroupModel';
|
||||
import { IRectangle } from 'vs/platform/window/common/window';
|
||||
|
@ -491,6 +491,24 @@ export interface IEditorWorkingSet {
|
|||
readonly name: string;
|
||||
}
|
||||
|
||||
export interface IEditorGroupContextKeyProvider<T extends ContextKeyValue> {
|
||||
|
||||
/**
|
||||
* The context key that needs to be set for each editor group context and the global context.
|
||||
*/
|
||||
readonly contextKey: RawContextKey<T>;
|
||||
|
||||
/**
|
||||
* Retrieves the context key value for the given editor group.
|
||||
*/
|
||||
readonly getGroupContextKeyValue: (group: IEditorGroup) => T;
|
||||
|
||||
/**
|
||||
* An event that is fired when there was a change leading to the context key value to be re-evaluated.
|
||||
*/
|
||||
readonly onDidChange?: Event<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main service to interact with editor groups across all opened editor parts.
|
||||
*/
|
||||
|
@ -561,6 +579,14 @@ export interface IEditorGroupsService extends IEditorGroupsContainer {
|
|||
* Deletes a working set.
|
||||
*/
|
||||
deleteWorkingSet(workingSet: IEditorWorkingSet): void;
|
||||
|
||||
/**
|
||||
* Registers a context key provider. This provider sets a context key for each scoped editor group context and the global context.
|
||||
*
|
||||
* @param provider - The context key provider to be registered.
|
||||
* @returns - A disposable object to unregister the provider.
|
||||
*/
|
||||
registerContextKeyProvider<T extends ContextKeyValue>(provider: IEditorGroupContextKeyProvider<T>): IDisposable;
|
||||
}
|
||||
|
||||
export const enum OpenEditorContext {
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, createEditorPart, ITestInstantiationService, workbenchTeardown } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, ITestInstantiationService, workbenchTeardown, createEditorParts, TestEditorParts } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService, GroupsArrangement, IEditorGroupContextKeyProvider } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupModelChangeKind, SideBySideEditor, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
@ -19,6 +19,9 @@ import { IGroupModelChangeEvent, IGroupEditorMoveEvent, IGroupEditorOpenEvent }
|
|||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
suite('EditorGroupsService', () => {
|
||||
|
||||
|
@ -42,14 +45,19 @@ suite('EditorGroupsService', () => {
|
|||
disposables.clear();
|
||||
});
|
||||
|
||||
async function createPart(instantiationService = workbenchInstantiationService(undefined, disposables)): Promise<[TestEditorPart, TestInstantiationService]> {
|
||||
async function createParts(instantiationService = workbenchInstantiationService(undefined, disposables)): Promise<[TestEditorParts, TestInstantiationService]> {
|
||||
instantiationService.invokeFunction(accessor => Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).start(accessor));
|
||||
const part = await createEditorPart(instantiationService, disposables);
|
||||
instantiationService.stub(IEditorGroupsService, part);
|
||||
const parts = await createEditorParts(instantiationService, disposables);
|
||||
instantiationService.stub(IEditorGroupsService, parts);
|
||||
|
||||
testLocalInstantiationService = instantiationService;
|
||||
|
||||
return [part, instantiationService];
|
||||
return [parts, instantiationService];
|
||||
}
|
||||
|
||||
async function createPart(instantiationService?: TestInstantiationService): Promise<[TestEditorPart, TestInstantiationService]> {
|
||||
const [parts, testInstantiationService] = await createParts(instantiationService);
|
||||
return [parts.testMainPart, testInstantiationService];
|
||||
}
|
||||
|
||||
function createTestFileEditorInput(resource: URI, typeId: string): TestFileEditorInput {
|
||||
|
@ -2027,5 +2035,167 @@ suite('EditorGroupsService', () => {
|
|||
assert.strictEqual(part.activeGroup.isEmpty, true);
|
||||
});
|
||||
|
||||
test('context key provider', async function () {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Instantiate workbench and setup initial state
|
||||
const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables);
|
||||
const rootContextKeyService = instantiationService.get(IContextKeyService);
|
||||
|
||||
const [parts] = await createParts(instantiationService);
|
||||
|
||||
const input1 = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
|
||||
const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
const group1 = parts.activeGroup;
|
||||
const group2 = parts.addGroup(group1, GroupDirection.RIGHT);
|
||||
|
||||
await group2.openEditor(input2, { pinned: true });
|
||||
await group1.openEditor(input1, { pinned: true });
|
||||
|
||||
// Create context key provider
|
||||
const rawContextKey = new RawContextKey<number>('testContextKey', parts.activeGroup.id);
|
||||
const contextKeyProvider: IEditorGroupContextKeyProvider<number> = {
|
||||
contextKey: rawContextKey,
|
||||
getGroupContextKeyValue: (group) => group.id
|
||||
};
|
||||
disposables.add(parts.registerContextKeyProvider(contextKeyProvider));
|
||||
|
||||
// Initial state: group1 is active
|
||||
assert.strictEqual(parts.activeGroup.id, group1.id);
|
||||
|
||||
let globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
let group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
let group2ContextKeyValue = group2.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, group1.id);
|
||||
assert.strictEqual(group1ContextKeyValue, group1.id);
|
||||
assert.strictEqual(group2ContextKeyValue, group2.id);
|
||||
|
||||
// Make group2 active and ensure both gloabal and local context key values are updated
|
||||
parts.activateGroup(group2);
|
||||
|
||||
globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group2ContextKeyValue = group2.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, group2.id);
|
||||
assert.strictEqual(group1ContextKeyValue, group1.id);
|
||||
assert.strictEqual(group2ContextKeyValue, group2.id);
|
||||
|
||||
// Add a new group and ensure both gloabal and local context key values are updated
|
||||
// Group 3 will be active
|
||||
const group3 = parts.addGroup(group2, GroupDirection.RIGHT);
|
||||
await group3.openEditor(input3, { pinned: true });
|
||||
|
||||
globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group2ContextKeyValue = group2.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
const group3ContextKeyValue = group3.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, group3.id);
|
||||
assert.strictEqual(group1ContextKeyValue, group1.id);
|
||||
assert.strictEqual(group2ContextKeyValue, group2.id);
|
||||
assert.strictEqual(group3ContextKeyValue, group3.id);
|
||||
|
||||
disposables.dispose();
|
||||
});
|
||||
|
||||
test('context key provider: onDidChange', async function () {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Instantiate workbench and setup initial state
|
||||
const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables);
|
||||
const rootContextKeyService = instantiationService.get(IContextKeyService);
|
||||
|
||||
const parts = await createEditorParts(instantiationService, disposables);
|
||||
|
||||
const input1 = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
const group1 = parts.activeGroup;
|
||||
const group2 = parts.addGroup(group1, GroupDirection.RIGHT);
|
||||
|
||||
await group2.openEditor(input2, { pinned: true });
|
||||
await group1.openEditor(input1, { pinned: true });
|
||||
|
||||
// Create context key provider
|
||||
let offset = 0;
|
||||
const _onDidChange = new Emitter<void>();
|
||||
|
||||
const rawContextKey = new RawContextKey<number>('testContextKey', parts.activeGroup.id);
|
||||
const contextKeyProvider: IEditorGroupContextKeyProvider<number> = {
|
||||
contextKey: rawContextKey,
|
||||
getGroupContextKeyValue: (group) => group.id + offset,
|
||||
onDidChange: _onDidChange.event
|
||||
};
|
||||
disposables.add(parts.registerContextKeyProvider(contextKeyProvider));
|
||||
|
||||
// Initial state: group1 is active
|
||||
assert.strictEqual(parts.activeGroup.id, group1.id);
|
||||
|
||||
let globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
let group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
let group2ContextKeyValue = group2.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, group1.id + offset);
|
||||
assert.strictEqual(group1ContextKeyValue, group1.id + offset);
|
||||
assert.strictEqual(group2ContextKeyValue, group2.id + offset);
|
||||
|
||||
// Make a change to the context key provider and fire onDidChange such that all context key values are updated
|
||||
offset = 10;
|
||||
_onDidChange.fire();
|
||||
|
||||
globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group2ContextKeyValue = group2.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, group1.id + offset);
|
||||
assert.strictEqual(group1ContextKeyValue, group1.id + offset);
|
||||
assert.strictEqual(group2ContextKeyValue, group2.id + offset);
|
||||
|
||||
disposables.dispose();
|
||||
});
|
||||
|
||||
test('context key provider: active editor change', async function () {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Instantiate workbench and setup initial state
|
||||
const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables);
|
||||
const rootContextKeyService = instantiationService.get(IContextKeyService);
|
||||
|
||||
const parts = await createEditorParts(instantiationService, disposables);
|
||||
|
||||
const input1 = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
const group1 = parts.activeGroup;
|
||||
|
||||
await group1.openEditor(input2, { pinned: true });
|
||||
await group1.openEditor(input1, { pinned: true });
|
||||
|
||||
// Create context key provider
|
||||
const rawContextKey = new RawContextKey<string>('testContextKey', input1.resource.toString());
|
||||
const contextKeyProvider: IEditorGroupContextKeyProvider<string> = {
|
||||
contextKey: rawContextKey,
|
||||
getGroupContextKeyValue: (group) => group.activeEditor?.resource?.toString() ?? '',
|
||||
};
|
||||
disposables.add(parts.registerContextKeyProvider(contextKeyProvider));
|
||||
|
||||
// Initial state: input1 is active
|
||||
assert.strictEqual(isEqual(group1.activeEditor?.resource, input1.resource), true);
|
||||
|
||||
let globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
let group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, input1.resource.toString());
|
||||
assert.strictEqual(group1ContextKeyValue, input1.resource.toString());
|
||||
|
||||
// Make input2 active and ensure both gloabal and local context key values are updated
|
||||
await group1.openEditor(input2);
|
||||
|
||||
globalContextKeyValue = rootContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
group1ContextKeyValue = group1.scopedContextKeyService.getContextKeyValue(rawContextKey.key);
|
||||
assert.strictEqual(globalContextKeyValue, input2.resource.toString());
|
||||
assert.strictEqual(group1ContextKeyValue, input2.resource.toString());
|
||||
|
||||
disposables.dispose();
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService
|
|||
import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
|
||||
import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position';
|
||||
import { IMenuService, MenuId, IMenu, IMenuChangeEvent } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
@ -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 } 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 } 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';
|
||||
|
@ -867,6 +867,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
|
|||
centerLayout(active: boolean): void { }
|
||||
isLayoutCentered(): boolean { return false; }
|
||||
createEditorDropTarget(container: HTMLElement, delegate: IEditorDropTargetDelegate): IDisposable { return Disposable.None; }
|
||||
registerContextKeyProvider<T extends ContextKeyValue>(_provider: IEditorGroupContextKeyProvider<T>): IDisposable { throw new Error('not implemented'); }
|
||||
|
||||
partOptions!: IEditorPartOptions;
|
||||
enforcePartOptions(options: IEditorPartOptions): IDisposable { return Disposable.None; }
|
||||
|
@ -1842,28 +1843,33 @@ export class TestEditorPart extends MainEditorPart implements IEditorGroupsServi
|
|||
getWorkingSets(): IEditorWorkingSet[] { throw new Error('Method not implemented.'); }
|
||||
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): 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.'); }
|
||||
}
|
||||
|
||||
export async function createEditorPart(instantiationService: IInstantiationService, disposables: DisposableStore): Promise<TestEditorPart> {
|
||||
export class TestEditorParts extends EditorParts {
|
||||
testMainPart!: TestEditorPart;
|
||||
|
||||
class TestEditorParts extends EditorParts {
|
||||
protected override createMainEditorPart(): MainEditorPart {
|
||||
this.testMainPart = this.instantiationService.createInstance(TestEditorPart, this);
|
||||
|
||||
testMainPart!: TestEditorPart;
|
||||
|
||||
protected override createMainEditorPart(): MainEditorPart {
|
||||
this.testMainPart = instantiationService.createInstance(TestEditorPart, this);
|
||||
|
||||
return this.testMainPart;
|
||||
}
|
||||
return this.testMainPart;
|
||||
}
|
||||
}
|
||||
|
||||
const part = disposables.add(instantiationService.createInstance(TestEditorParts)).testMainPart;
|
||||
export async function createEditorParts(instantiationService: IInstantiationService, disposables: DisposableStore): Promise<TestEditorParts> {
|
||||
const parts = instantiationService.createInstance(TestEditorParts);
|
||||
const part = disposables.add(parts).testMainPart;
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(1080, 800, 0, 0);
|
||||
|
||||
await part.whenReady;
|
||||
await parts.whenReady;
|
||||
|
||||
return part;
|
||||
return parts;
|
||||
}
|
||||
|
||||
export async function createEditorPart(instantiationService: IInstantiationService, disposables: DisposableStore): Promise<TestEditorPart> {
|
||||
return (await createEditorParts(instantiationService, disposables)).testMainPart;
|
||||
}
|
||||
|
||||
export class TestListService implements IListService {
|
||||
|
|
Loading…
Reference in a new issue