未验证 提交 d25064ba 编写于 作者: R Rob Lourens 提交者: GitHub

Merge pull request #104694 from microsoft/roblou/reparentContextKeyServices

Implement reparenting ScopedContextKeyServices
......@@ -5,6 +5,7 @@
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { distinct } from 'vs/base/common/objects';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
......@@ -51,7 +52,11 @@ export class Context implements IContext {
return ret;
}
collectAllValues(): { [key: string]: any; } {
public updateParent(parent: Context): void {
this._parent = parent;
}
public collectAllValues(): { [key: string]: any; } {
let result = this._parent ? this._parent.collectAllValues() : Object.create(null);
result = { ...result, ...this._value };
delete result['_contextId'];
......@@ -239,6 +244,10 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
this._myContextId = myContextId;
}
public get contextId(): number {
return this._myContextId;
}
abstract dispose(): void;
public createKey<T>(key: string, defaultValue: T | undefined): IContextKey<T> {
......@@ -319,6 +328,7 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
public abstract getContextValuesContainer(contextId: number): Context;
public abstract createChildContext(parentContextId?: number): number;
public abstract disposeContext(contextId: number): void;
public abstract updateParent(parentContextKeyService: IContextKeyService): void;
}
export class ContextKeyService extends AbstractContextKeyService implements IContextKeyService {
......@@ -375,6 +385,10 @@ export class ContextKeyService extends AbstractContextKeyService implements ICon
this._contexts.delete(contextId);
}
}
public updateParent(_parentContextKeyService: IContextKeyService): void {
throw new Error('Cannot update parent of root ContextKeyService');
}
}
class ScopedContextKeyService extends AbstractContextKeyService {
......@@ -382,9 +396,12 @@ class ScopedContextKeyService extends AbstractContextKeyService {
private _parent: AbstractContextKeyService;
private _domNode: IContextKeyServiceTarget | undefined;
private _parentChangeListener: IDisposable | undefined;
constructor(parent: AbstractContextKeyService, domNode?: IContextKeyServiceTarget) {
super(parent.createChildContext());
this._parent = parent;
this.updateParentChangeListener();
if (domNode) {
this._domNode = domNode;
......@@ -392,6 +409,17 @@ class ScopedContextKeyService extends AbstractContextKeyService {
}
}
private updateParentChangeListener(): void {
if (this._parentChangeListener) {
this._parentChangeListener.dispose();
}
this._parentChangeListener = this._parent.onDidChangeContext(e => {
// Forward parent events to this listener. Parent will change.
this._onDidChangeContext.fire(e);
});
}
public dispose(): void {
this._isDisposed = true;
this._parent.disposeContext(this._myContextId);
......@@ -402,7 +430,7 @@ class ScopedContextKeyService extends AbstractContextKeyService {
}
public get onDidChangeContext(): Event<IContextKeyChangeEvent> {
return Event.any(this._parent.onDidChangeContext, this._onDidChangeContext.event);
return this._onDidChangeContext.event;
}
public getContextValuesContainer(contextId: number): Context {
......@@ -425,6 +453,24 @@ class ScopedContextKeyService extends AbstractContextKeyService {
}
this._parent.disposeContext(contextId);
}
public updateParent(parentContextKeyService: AbstractContextKeyService): void {
const thisContainer = this._parent.getContextValuesContainer(this._myContextId);
const oldAllValues = thisContainer.collectAllValues();
this._parent = parentContextKeyService;
this.updateParentChangeListener();
const newParentContainer = this._parent.getContextValuesContainer(this._parent.contextId);
thisContainer.updateParent(newParentContainer);
const newAllValues = thisContainer.collectAllValues();
const allValuesDiff = {
...distinct(oldAllValues, newAllValues),
...distinct(newAllValues, oldAllValues)
};
const changedKeys = Object.keys(allValuesDiff);
this._onDidChangeContext.fire(new ArrayContextKeyChangeEvent(changedKeys));
}
}
function findContextAttr(domNode: IContextKeyServiceTarget | null): number {
......
......@@ -1134,6 +1134,8 @@ export interface IContextKeyService {
createScoped(target?: IContextKeyServiceTarget): IContextKeyService;
getContext(target: IContextKeyServiceTarget | null): IContext;
updateParent(parentContextKeyService: IContextKeyService): void;
}
export const SET_CONTEXT_COMMAND_ID = 'setContext';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import * as assert from 'assert';
suite('ContextKeyService', () => {
test('updateParent', () => {
const root = new ContextKeyService(new TestConfigurationService());
const parent1 = root.createScoped(document.createElement('div'));
const parent2 = root.createScoped(document.createElement('div'));
const child = parent1.createScoped(document.createElement('div'));
parent1.createKey('testA', 1);
parent1.createKey('testB', 2);
parent1.createKey('testD', 0);
parent2.createKey('testA', 3);
parent2.createKey('testC', 4);
parent2.createKey('testD', 0);
let complete: () => void;
let reject: (err: Error) => void;
const p = new Promise((_complete, _reject) => {
complete = _complete;
reject = _reject;
});
child.onDidChangeContext(e => {
try {
assert.ok(e.affectsSome(new Set(['testA'])), 'testA changed');
assert.ok(e.affectsSome(new Set(['testB'])), 'testB changed');
assert.ok(e.affectsSome(new Set(['testC'])), 'testC changed');
assert.ok(!e.affectsSome(new Set(['testD'])), 'testD did not change');
assert.equal(child.getContextKeyValue('testA'), 3);
assert.equal(child.getContextKeyValue('testB'), undefined);
assert.equal(child.getContextKeyValue('testC'), 4);
assert.equal(child.getContextKeyValue('testD'), 0);
} catch (err) {
reject(err);
return;
}
complete();
});
child.updateParent(parent2);
return p;
});
});
......@@ -120,7 +120,8 @@ suite('AbstractKeybindingService', () => {
createScoped: undefined!,
getContext: (target: IContextKeyServiceTarget): any => {
return currentContextValue;
}
},
updateParent: () => { }
};
let commandService: ICommandService = {
......
......@@ -66,6 +66,9 @@ export class MockContextKeyService implements IContextKeyService {
public createScoped(domNode: HTMLElement): IContextKeyService {
return this;
}
updateParent(_parentContextKeyService: IContextKeyService): void {
// no-op
}
}
export class MockKeybindingService implements IKeybindingService {
......
......@@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import 'vs/css!./media/notebook';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
......@@ -57,6 +58,7 @@ export class NotebookEditor extends EditorPane {
@INotificationService private readonly _notificationService: INotificationService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorWidgetService private readonly _notebookWidgetService: INotebookEditorWidgetService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
......@@ -180,6 +182,7 @@ export class NotebookEditor extends EditorPane {
const viewState = this._loadNotebookEditorViewState(input);
this._widget.value?.setParentContextKeyService(this._contextKeyService);
await this._widget.value!.setModel(model.notebook, viewState);
await this._widget.value!.setOptions(options instanceof NotebookEditorOptions ? options : undefined);
this._widgetDisposableStore.add(this._widget.value!.onDidFocus(() => this._onDidFocusWidget.fire()));
......
......@@ -29,6 +29,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
......@@ -227,13 +228,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
readonly isEmbedded: boolean;
private readonly contextKeyService: IContextKeyService;
private readonly instantiationService: IInstantiationService;
constructor(
readonly creationOptions: INotebookEditorCreationOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@INotebookService private notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IContextKeyService readonly contextKeyService: IContextKeyService,
@IContextKeyService contextKeyService: IContextKeyService,
@ILayoutService private readonly layoutService: ILayoutService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IMenuService private readonly menuService: IMenuService,
......@@ -241,6 +245,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
) {
super();
this.isEmbedded = creationOptions.isEmbedded || false;
this._overlayContainer = document.createElement('div');
this.contextKeyService = contextKeyService.createScoped(this._overlayContainer);
this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]));
this._memento = new Memento(NotebookEditorWidget.ID, storageService);
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
......@@ -371,7 +380,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
createEditor(): void {
this._overlayContainer = document.createElement('div');
const id = generateUuid();
this._overlayContainer.id = `notebook-${id}`;
this._overlayContainer.className = 'notebookOverlay';
......@@ -631,6 +639,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._onDidFocusEditorWidget.fire();
}
setParentContextKeyService(parentContextKeyService: IContextKeyService): void {
this.contextKeyService.updateParent(parentContextKeyService);
}
async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined): Promise<void> {
if (this._notebookViewModel === undefined || !this._notebookViewModel.equal(textModel)) {
this._detachModel();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册