UNPKG

sussudio

Version:

An unofficial VS Code Internal API

555 lines (554 loc) 19.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { PauseableEmitter } from "../../../base/common/event.mjs"; import { Iterable } from "../../../base/common/iterator.mjs"; import { DisposableStore, MutableDisposable } from "../../../base/common/lifecycle.mjs"; import { cloneAndChange, distinct } from "../../../base/common/objects.mjs"; import { TernarySearchTree } from "../../../base/common/ternarySearchTree.mjs"; import { URI } from "../../../base/common/uri.mjs"; import { localize } from "../../../nls.mjs"; import { CommandsRegistry } from "../../commands/common/commands.mjs"; import { IConfigurationService } from "../../configuration/common/configuration.mjs"; import { IContextKeyService, RawContextKey } from "../common/contextkey.mjs"; const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; export class Context { _parent; _value; _id; constructor(id, parent) { this._id = id; this._parent = parent; this._value = Object.create(null); this._value['_contextId'] = id; } get value() { return { ...this._value }; } setValue(key, value) { // console.log('SET ' + key + ' = ' + value + ' ON ' + this._id); if (this._value[key] !== value) { this._value[key] = value; return true; } return false; } removeValue(key) { // console.log('REMOVE ' + key + ' FROM ' + this._id); if (key in this._value) { delete this._value[key]; return true; } return false; } getValue(key) { const ret = this._value[key]; if (typeof ret === 'undefined' && this._parent) { return this._parent.getValue(key); } return ret; } updateParent(parent) { this._parent = parent; } collectAllValues() { let result = this._parent ? this._parent.collectAllValues() : Object.create(null); result = { ...result, ...this._value }; delete result['_contextId']; return result; } } class NullContext extends Context { static INSTANCE = new NullContext(); constructor() { super(-1, null); } setValue(key, value) { return false; } removeValue(key) { return false; } getValue(key) { return undefined; } collectAllValues() { return Object.create(null); } } class ConfigAwareContextValuesContainer extends Context { _configurationService; static _keyPrefix = 'config.'; _values = TernarySearchTree.forConfigKeys(); _listener; constructor(id, _configurationService, emitter) { super(id, null); this._configurationService = _configurationService; this._listener = this._configurationService.onDidChangeConfiguration(event => { if (event.source === 7 /* ConfigurationTarget.DEFAULT */) { // new setting, reset everything const allKeys = Array.from(this._values, ([k]) => k); this._values.clear(); emitter.fire(new ArrayContextKeyChangeEvent(allKeys)); } else { const changedKeys = []; for (const configKey of event.affectedKeys) { const contextKey = `config.${configKey}`; const cachedItems = this._values.findSuperstr(contextKey); if (cachedItems !== undefined) { changedKeys.push(...Iterable.map(cachedItems, ([key]) => key)); this._values.deleteSuperstr(contextKey); } if (this._values.has(contextKey)) { changedKeys.push(contextKey); this._values.delete(contextKey); } } emitter.fire(new ArrayContextKeyChangeEvent(changedKeys)); } }); } dispose() { this._listener.dispose(); } getValue(key) { if (key.indexOf(ConfigAwareContextValuesContainer._keyPrefix) !== 0) { return super.getValue(key); } if (this._values.has(key)) { return this._values.get(key); } const configKey = key.substr(ConfigAwareContextValuesContainer._keyPrefix.length); const configValue = this._configurationService.getValue(configKey); let value = undefined; switch (typeof configValue) { case 'number': case 'boolean': case 'string': value = configValue; break; default: if (Array.isArray(configValue)) { value = JSON.stringify(configValue); } else { value = configValue; } } this._values.set(key, value); return value; } setValue(key, value) { return super.setValue(key, value); } removeValue(key) { return super.removeValue(key); } collectAllValues() { const result = Object.create(null); this._values.forEach((value, index) => result[index] = value); return { ...result, ...super.collectAllValues() }; } } class ContextKey { _service; _key; _defaultValue; constructor(service, key, defaultValue) { this._service = service; this._key = key; this._defaultValue = defaultValue; this.reset(); } set(value) { this._service.setContext(this._key, value); } reset() { if (typeof this._defaultValue === 'undefined') { this._service.removeContext(this._key); } else { this._service.setContext(this._key, this._defaultValue); } } get() { return this._service.getContextKeyValue(this._key); } } class SimpleContextKeyChangeEvent { key; constructor(key) { this.key = key; } affectsSome(keys) { return keys.has(this.key); } allKeysContainedIn(keys) { return this.affectsSome(keys); } } class ArrayContextKeyChangeEvent { keys; constructor(keys) { this.keys = keys; } affectsSome(keys) { for (const key of this.keys) { if (keys.has(key)) { return true; } } return false; } allKeysContainedIn(keys) { return this.keys.every(key => keys.has(key)); } } class CompositeContextKeyChangeEvent { events; constructor(events) { this.events = events; } affectsSome(keys) { for (const e of this.events) { if (e.affectsSome(keys)) { return true; } } return false; } allKeysContainedIn(keys) { return this.events.every(evt => evt.allKeysContainedIn(keys)); } } function allEventKeysInContext(event, context) { return event.allKeysContainedIn(new Set(Object.keys(context))); } export class AbstractContextKeyService { _isDisposed; _myContextId; _onDidChangeContext = new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }); onDidChangeContext = this._onDidChangeContext.event; constructor(myContextId) { this._isDisposed = false; this._myContextId = myContextId; } get contextId() { return this._myContextId; } createKey(key, defaultValue) { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } return new ContextKey(this, key, defaultValue); } bufferChangeEvents(callback) { this._onDidChangeContext.pause(); try { callback(); } finally { this._onDidChangeContext.resume(); } } createScoped(domNode) { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } return new ScopedContextKeyService(this, domNode); } createOverlay(overlay = Iterable.empty()) { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } return new OverlayContextKeyService(this, overlay); } contextMatchesRules(rules) { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } const context = this.getContextValuesContainer(this._myContextId); const result = (rules ? rules.evaluate(context) : true); // console.group(rules.serialize() + ' -> ' + result); // rules.keys().forEach(key => { console.log(key, ctx[key]); }); // console.groupEnd(); return result; } getContextKeyValue(key) { if (this._isDisposed) { return undefined; } return this.getContextValuesContainer(this._myContextId).getValue(key); } setContext(key, value) { if (this._isDisposed) { return; } const myContext = this.getContextValuesContainer(this._myContextId); if (!myContext) { return; } if (myContext.setValue(key, value)) { this._onDidChangeContext.fire(new SimpleContextKeyChangeEvent(key)); } } removeContext(key) { if (this._isDisposed) { return; } if (this.getContextValuesContainer(this._myContextId).removeValue(key)) { this._onDidChangeContext.fire(new SimpleContextKeyChangeEvent(key)); } } getContext(target) { if (this._isDisposed) { return NullContext.INSTANCE; } return this.getContextValuesContainer(findContextAttr(target)); } } let ContextKeyService = class ContextKeyService extends AbstractContextKeyService { _lastContextId; _contexts = new Map(); _toDispose = new DisposableStore(); constructor(configurationService) { super(0); this._lastContextId = 0; const myContext = new ConfigAwareContextValuesContainer(this._myContextId, configurationService, this._onDidChangeContext); this._contexts.set(this._myContextId, myContext); this._toDispose.add(myContext); // Uncomment this to see the contexts continuously logged // let lastLoggedValue: string | null = null; // setInterval(() => { // let values = Object.keys(this._contexts).map((key) => this._contexts[key]); // let logValue = values.map(v => JSON.stringify(v._value, null, '\t')).join('\n'); // if (lastLoggedValue !== logValue) { // lastLoggedValue = logValue; // console.log(lastLoggedValue); // } // }, 2000); } dispose() { this._onDidChangeContext.dispose(); this._isDisposed = true; this._toDispose.dispose(); } getContextValuesContainer(contextId) { if (this._isDisposed) { return NullContext.INSTANCE; } return this._contexts.get(contextId) || NullContext.INSTANCE; } createChildContext(parentContextId = this._myContextId) { if (this._isDisposed) { throw new Error(`ContextKeyService has been disposed`); } const id = (++this._lastContextId); this._contexts.set(id, new Context(id, this.getContextValuesContainer(parentContextId))); return id; } disposeContext(contextId) { if (!this._isDisposed) { this._contexts.delete(contextId); } } updateParent(_parentContextKeyService) { throw new Error('Cannot update parent of root ContextKeyService'); } }; ContextKeyService = __decorate([ __param(0, IConfigurationService) ], ContextKeyService); export { ContextKeyService }; class ScopedContextKeyService extends AbstractContextKeyService { _parent; _domNode; _parentChangeListener = new MutableDisposable(); constructor(parent, domNode) { super(parent.createChildContext()); this._parent = parent; this._updateParentChangeListener(); this._domNode = domNode; if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) { let extraInfo = ''; if (this._domNode.classList) { extraInfo = Array.from(this._domNode.classList.values()).join(', '); } console.error(`Element already has context attribute${extraInfo ? ': ' + extraInfo : ''}`); } this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId)); } _updateParentChangeListener() { // Forward parent events to this listener. Parent will change. this._parentChangeListener.value = this._parent.onDidChangeContext(e => { const thisContainer = this._parent.getContextValuesContainer(this._myContextId); const thisContextValues = thisContainer.value; if (!allEventKeysInContext(e, thisContextValues)) { this._onDidChangeContext.fire(e); } }); } dispose() { if (this._isDisposed) { return; } this._onDidChangeContext.dispose(); this._parent.disposeContext(this._myContextId); this._parentChangeListener.dispose(); this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR); this._isDisposed = true; } getContextValuesContainer(contextId) { if (this._isDisposed) { return NullContext.INSTANCE; } return this._parent.getContextValuesContainer(contextId); } createChildContext(parentContextId = this._myContextId) { if (this._isDisposed) { throw new Error(`ScopedContextKeyService has been disposed`); } return this._parent.createChildContext(parentContextId); } disposeContext(contextId) { if (this._isDisposed) { return; } this._parent.disposeContext(contextId); } updateParent(parentContextKeyService) { 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)); } } class OverlayContext { parent; overlay; constructor(parent, overlay) { this.parent = parent; this.overlay = overlay; } getValue(key) { return this.overlay.has(key) ? this.overlay.get(key) : this.parent.getValue(key); } } class OverlayContextKeyService { parent; overlay; get contextId() { return this.parent.contextId; } get onDidChangeContext() { return this.parent.onDidChangeContext; } constructor(parent, overlay) { this.parent = parent; this.overlay = new Map(overlay); } bufferChangeEvents(callback) { this.parent.bufferChangeEvents(callback); } createKey() { throw new Error('Not supported.'); } getContext(target) { return new OverlayContext(this.parent.getContext(target), this.overlay); } getContextValuesContainer(contextId) { const parentContext = this.parent.getContextValuesContainer(contextId); return new OverlayContext(parentContext, this.overlay); } contextMatchesRules(rules) { const context = this.getContextValuesContainer(this.contextId); const result = (rules ? rules.evaluate(context) : true); return result; } getContextKeyValue(key) { return this.overlay.has(key) ? this.overlay.get(key) : this.parent.getContextKeyValue(key); } createScoped() { throw new Error('Not supported.'); } createOverlay(overlay = Iterable.empty()) { return new OverlayContextKeyService(this, overlay); } updateParent() { throw new Error('Not supported.'); } dispose() { // noop } } function findContextAttr(domNode) { while (domNode) { if (domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) { const attr = domNode.getAttribute(KEYBINDING_CONTEXT_ATTR); if (attr) { return parseInt(attr, 10); } return NaN; } domNode = domNode.parentElement; } return 0; } export function setContext(accessor, contextKey, contextValue) { const contextKeyService = accessor.get(IContextKeyService); contextKeyService.createKey(String(contextKey), stringifyURIs(contextValue)); } function stringifyURIs(contextValue) { return cloneAndChange(contextValue, (obj) => { if (typeof obj === 'object' && obj.$mid === 1 /* MarshalledId.Uri */) { return URI.revive(obj).toString(); } if (obj instanceof URI) { return obj.toString(); } return undefined; }); } CommandsRegistry.registerCommand('_setContext', setContext); CommandsRegistry.registerCommand({ id: 'getContextKeyInfo', handler() { return [...RawContextKey.all()].sort((a, b) => a.key.localeCompare(b.key)); }, description: { description: localize('getContextKeyInfo', "A command that returns information about context keys"), args: [] } }); CommandsRegistry.registerCommand('_generateContextKeyInfo', function () { const result = []; const seen = new Set(); for (const info of RawContextKey.all()) { if (!seen.has(info.key)) { seen.add(info.key); result.push(info); } } result.sort((a, b) => a.key.localeCompare(b.key)); console.log(JSON.stringify(result, undefined, 2)); });