monaco-editor-core
Version:
A browser based code editor
433 lines (432 loc) • 15.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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.js';
import { Iterable } from '../../../base/common/iterator.js';
import { Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
import { cloneAndChange } from '../../../base/common/objects.js';
import { TernarySearchTree } from '../../../base/common/ternarySearchTree.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { CommandsRegistry } from '../../commands/common/commands.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IContextKeyService, RawContextKey } from '../common/contextkey.js';
const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';
export class Context {
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;
}
}
class NullContext extends Context {
static { this.INSTANCE = new NullContext(); }
constructor() {
super(-1, null);
}
setValue(key, value) {
return false;
}
removeValue(key) {
return false;
}
getValue(key) {
return undefined;
}
}
class ConfigAwareContextValuesContainer extends Context {
static { this._keyPrefix = 'config.'; }
constructor(id, _configurationService, emitter) {
super(id, null);
this._configurationService = _configurationService;
this._values = TernarySearchTree.forConfigKeys();
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);
}
}
class ContextKey {
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 {
constructor(key) {
this.key = key;
}
affectsSome(keys) {
return keys.has(this.key);
}
allKeysContainedIn(keys) {
return this.affectsSome(keys);
}
}
class ArrayContextKeyChangeEvent {
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 {
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 extends Disposable {
constructor(myContextId) {
super();
this._onDidChangeContext = this._register(new PauseableEmitter({ merge: input => new CompositeContextKeyChangeEvent(input) }));
this.onDidChangeContext = this._onDidChangeContext.event;
this._isDisposed = false;
this._myContextId = 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);
}
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));
}
dispose() {
super.dispose();
this._isDisposed = true;
}
}
let ContextKeyService = class ContextKeyService extends AbstractContextKeyService {
constructor(configurationService) {
super(0);
this._contexts = new Map();
this._lastContextId = 0;
const myContext = this._register(new ConfigAwareContextValuesContainer(this._myContextId, configurationService, this._onDidChangeContext));
this._contexts.set(this._myContextId, 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);
}
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);
}
}
};
ContextKeyService = __decorate([
__param(0, IConfigurationService)
], ContextKeyService);
export { ContextKeyService };
class ScopedContextKeyService extends AbstractContextKeyService {
constructor(parent, domNode) {
super(parent.createChildContext());
this._parentChangeListener = this._register(new MutableDisposable());
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._parent.disposeContext(this._myContextId);
this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR);
super.dispose();
}
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);
}
}
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));
},
metadata: {
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));
});