UNPKG

@difizen/mana-core

Version:

1,495 lines (1,225 loc) 39.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import type { Event } from '@difizen/mana-common'; import { isFalsyOrWhitespace } from '@difizen/mana-common'; // import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { userAgent, isMacintosh, isLinux, isWindows, isWeb, } from '@difizen/mana-common'; import { Syringe } from '@difizen/mana-syringe'; const _userAgent = userAgent || ''; const STATIC_VALUES = new Map<string, boolean>(); STATIC_VALUES.set('false', false); STATIC_VALUES.set('true', true); STATIC_VALUES.set('isMac', isMacintosh); STATIC_VALUES.set('isLinux', isLinux); STATIC_VALUES.set('isWindows', isWindows); STATIC_VALUES.set('isWeb', isWeb); STATIC_VALUES.set('isMacNative', isMacintosh && !isWeb); STATIC_VALUES.set('isEdge', _userAgent.indexOf('Edg/') >= 0); STATIC_VALUES.set('isFirefox', _userAgent.indexOf('Firefox') >= 0); STATIC_VALUES.set('isChrome', _userAgent.indexOf('Chrome') >= 0); STATIC_VALUES.set('isSafari', _userAgent.indexOf('Safari') >= 0); STATIC_VALUES.set('isIPad', _userAgent.indexOf('iPad') >= 0); const { hasOwnProperty } = Object.prototype; export enum ContextKeyExprType { False = 0, True = 1, Defined = 2, Not = 3, Equals = 4, NotEquals = 5, And = 6, Regex = 7, NotRegex = 8, Or = 9, In = 10, NotIn = 11, Greater = 12, GreaterEquals = 13, Smaller = 14, SmallerEquals = 15, } export interface IContextKeyExprMapper { mapDefined: (key: string) => ContextKeyExpression; mapNot: (key: string) => ContextKeyExpression; mapEquals: (key: string, value: any) => ContextKeyExpression; mapNotEquals: (key: string, value: any) => ContextKeyExpression; mapGreater: (key: string, value: any) => ContextKeyExpression; mapGreaterEquals: (key: string, value: any) => ContextKeyExpression; mapSmaller: (key: string, value: any) => ContextKeyExpression; mapSmallerEquals: (key: string, value: any) => ContextKeyExpression; mapRegex: (key: string, regexp: RegExp | null) => ContextKeyRegexExpr; mapIn: (key: string, valueKey: string) => ContextKeyInExpr; } export interface IContextKeyExpression { cmp: (other: ContextKeyExpression) => number; equals: (other: ContextKeyExpression) => boolean; evaluate: (context: IContext) => boolean; serialize: () => string; keys: () => string[]; map: (mapFnc: IContextKeyExprMapper) => ContextKeyExpression; negate: () => ContextKeyExpression; } export type ContextKeyExpression = | ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr | ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr | ContextKeySmallerExpr | ContextKeySmallerEqualsExpr; export abstract class ContextKeyExpr { public static false(): ContextKeyExpression { return ContextKeyFalseExpr.INSTANCE; } public static true(): ContextKeyExpression { return ContextKeyTrueExpr.INSTANCE; } public static has(key: string): ContextKeyExpression { return ContextKeyDefinedExpr.create(key); } public static equals(key: string, value: any): ContextKeyExpression { return ContextKeyEqualsExpr.create(key, value); } public static notEquals(key: string, value: any): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(key, value); } public static regex(key: string, value: RegExp): ContextKeyExpression { return ContextKeyRegexExpr.create(key, value); } public static in(key: string, value: string): ContextKeyExpression { return ContextKeyInExpr.create(key, value); } public static not(key: string): ContextKeyExpression { return ContextKeyNotExpr.create(key); } public static and( ...expr: (ContextKeyExpression | undefined | null)[] ): ContextKeyExpression | undefined { return ContextKeyAndExpr.create(expr); } public static or( ...expr: (ContextKeyExpression | undefined | null)[] ): ContextKeyExpression | undefined { return ContextKeyOrExpr.create(expr); } public static greater(key: string, value: any): ContextKeyExpression { return ContextKeyGreaterExpr.create(key, value); } public static less(key: string, value: any): ContextKeyExpression { return ContextKeySmallerExpr.create(key, value); } public static deserialize( serialized: string | null | undefined, strict = false, ): ContextKeyExpression | undefined { if (!serialized) { return undefined; } return this._deserializeOrExpression(serialized, strict); } private static _deserializeOrExpression( serialized: string, strict: boolean, ): ContextKeyExpression | undefined { const pieces = serialized.split('||'); return ContextKeyOrExpr.create( pieces.map((p) => this._deserializeAndExpression(p, strict)), ); } private static _deserializeAndExpression( serialized: string, strict: boolean, ): ContextKeyExpression | undefined { const pieces = serialized.split('&&'); return ContextKeyAndExpr.create(pieces.map((p) => this._deserializeOne(p, strict))); } private static _deserializeOne( serializedOne: string, strict: boolean, ): ContextKeyExpression { serializedOne = serializedOne.trim(); if (serializedOne.indexOf('!=') >= 0) { const pieces = serializedOne.split('!='); return ContextKeyNotEqualsExpr.create( pieces[0].trim(), this._deserializeValue(pieces[1], strict), ); } if (serializedOne.indexOf('==') >= 0) { const pieces = serializedOne.split('=='); return ContextKeyEqualsExpr.create( pieces[0].trim(), this._deserializeValue(pieces[1], strict), ); } if (serializedOne.indexOf('=~') >= 0) { const pieces = serializedOne.split('=~'); return ContextKeyRegexExpr.create( pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict), ); } if (serializedOne.indexOf(' in ') >= 0) { const pieces = serializedOne.split(' in '); return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim()); } if (/^[^<=>]+>=[^<=>]+$/.test(serializedOne)) { const pieces = serializedOne.split('>='); return ContextKeyGreaterEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); } if (/^[^<=>]+>[^<=>]+$/.test(serializedOne)) { const pieces = serializedOne.split('>'); return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim()); } if (/^[^<=>]+<=[^<=>]+$/.test(serializedOne)) { const pieces = serializedOne.split('<='); return ContextKeySmallerEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); } if (/^[^<=>]+<[^<=>]+$/.test(serializedOne)) { const pieces = serializedOne.split('<'); return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim()); } // eslint-disable-next-line no-useless-escape if (/^\!\s*/.test(serializedOne)) { return ContextKeyNotExpr.create(serializedOne.substr(1).trim()); } return ContextKeyDefinedExpr.create(serializedOne); } private static _deserializeValue(serializedValue: string, _strict: boolean): any { serializedValue = serializedValue.trim(); if (serializedValue === 'true') { return true; } if (serializedValue === 'false') { return false; } const m = /^'([^']*)'$/.exec(serializedValue); if (m) { return m[1].trim(); } return serializedValue; } private static _deserializeRegexValue( serializedValue: string, strict: boolean, ): RegExp | null { if (isFalsyOrWhitespace(serializedValue)) { if (strict) { throw new Error('missing regexp-value for =~-expression'); } else { console.warn('missing regexp-value for =~-expression'); } return null; } const start = serializedValue.indexOf('/'); const end = serializedValue.lastIndexOf('/'); if (start === end || start < 0 /* || to < 0 */) { if (strict) { throw new Error(`bad regexp-value '${serializedValue}', missing /-enclosure`); } else { console.warn(`bad regexp-value '${serializedValue}', missing /-enclosure`); } return null; } const value = serializedValue.slice(start + 1, end); const caseIgnoreFlag = serializedValue[end + 1] === 'i' ? 'i' : ''; try { return new RegExp(value, caseIgnoreFlag); } catch (e) { if (strict) { throw new Error(`bad regexp-value '${serializedValue}', parse error: ${e}`); } else { console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`); } return null; } } } function cmp(a: ContextKeyExpression, b: ContextKeyExpression): number { return a.cmp(b); } export class ContextKeyFalseExpr implements IContextKeyExpression { public static INSTANCE = new ContextKeyFalseExpr(); public readonly type = ContextKeyExprType.False; protected constructor() { // } public cmp(other: ContextKeyExpression): number { return this.type - other.type; } public equals(other: ContextKeyExpression): boolean { return other.type === this.type; } public evaluate(_context: IContext): boolean { return false; } public serialize(): string { return 'false'; } public keys(): string[] { return []; } public map(_mapFnc: IContextKeyExprMapper): ContextKeyExpression { return this; } public negate(): ContextKeyExpression { return ContextKeyTrueExpr.INSTANCE; } } export class ContextKeyTrueExpr implements IContextKeyExpression { public static INSTANCE = new ContextKeyTrueExpr(); public readonly type = ContextKeyExprType.True; protected constructor() { // } public cmp(other: ContextKeyExpression): number { return this.type - other.type; } public equals(other: ContextKeyExpression): boolean { return other.type === this.type; } public evaluate(_context: IContext): boolean { return true; } public serialize(): string { return 'true'; } public keys(): string[] { return []; } public map(_mapFnc: IContextKeyExprMapper): ContextKeyExpression { return this; } public negate(): ContextKeyExpression { return ContextKeyFalseExpr.INSTANCE; } } export class ContextKeyDefinedExpr implements IContextKeyExpression { public static create(key: string): ContextKeyExpression { const staticValue = STATIC_VALUES.get(key); if (typeof staticValue === 'boolean') { return staticValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE; } return new ContextKeyDefinedExpr(key); } public readonly type = ContextKeyExprType.Defined; protected readonly key: string; protected constructor(key: string) { this.key = key; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key; } return false; } public evaluate(context: IContext): boolean { return !!context.getValue(this.key); } public serialize(): string { return this.key; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapDefined(this.key); } public negate(): ContextKeyExpression { return ContextKeyNotExpr.create(this.key); } } export class ContextKeyEqualsExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { return value ? ContextKeyDefinedExpr.create(key) : ContextKeyNotExpr.create(key); } const staticValue = STATIC_VALUES.get(key); if (typeof staticValue === 'boolean') { const trueValue = staticValue ? 'true' : 'false'; return value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE; } return new ContextKeyEqualsExpr(key, value); } public readonly type = ContextKeyExprType.Equals; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { // Intentional == // eslint-disable-next-line eqeqeq return context.getValue(this.key) == this.value; } public serialize(): string { return `${this.key} == '${this.value}'`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapEquals(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(this.key, this.value); } } export class ContextKeyInExpr implements IContextKeyExpression { public static create(key: string, valueKey: string): ContextKeyInExpr { return new ContextKeyInExpr(key, valueKey); } public readonly type = ContextKeyExprType.In; private readonly key: string; private readonly valueKey: string; private constructor(key: string, valueKey: string) { this.key = key; this.valueKey = valueKey; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.valueKey, other.key, other.valueKey); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.valueKey === other.valueKey; } return false; } public evaluate(context: IContext): boolean { const source = context.getValue(this.valueKey); const item = context.getValue(this.key); if (Array.isArray(source)) { return source.indexOf(item) >= 0; } if (typeof item === 'string' && typeof source === 'object' && source !== null) { return hasOwnProperty.call(source, item); } return false; } public serialize(): string { return `${this.key} in '${this.valueKey}'`; } public keys(): string[] { return [this.key, this.valueKey]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyInExpr { return mapFnc.mapIn(this.key, this.valueKey); } public negate(): ContextKeyExpression { return ContextKeyNotInExpr.create(this); } } export class ContextKeyNotInExpr implements IContextKeyExpression { public static create(actual: ContextKeyInExpr): ContextKeyNotInExpr { return new ContextKeyNotInExpr(actual); } public readonly type = ContextKeyExprType.NotIn; private readonly _actual: ContextKeyInExpr; private constructor(_actual: ContextKeyInExpr) { // this._actual = _actual; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return this._actual.cmp(other._actual); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this._actual.equals(other._actual); } return false; } public evaluate(context: IContext): boolean { return !this._actual.evaluate(context); } public serialize(): string { throw new Error('Method not implemented.'); } public keys(): string[] { return this._actual.keys(); } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyNotInExpr(this._actual.map(mapFnc)); } public negate(): ContextKeyExpression { return this._actual; } } export class ContextKeyNotEqualsExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { if (value) { return ContextKeyNotExpr.create(key); } return ContextKeyDefinedExpr.create(key); } const staticValue = STATIC_VALUES.get(key); if (typeof staticValue === 'boolean') { const falseValue = staticValue ? 'true' : 'false'; return value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE; } return new ContextKeyNotEqualsExpr(key, value); } public readonly type = ContextKeyExprType.NotEquals; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { // Intentional != // eslint-disable-next-line eqeqeq return context.getValue(this.key) != this.value; } public serialize(): string { return `${this.key} != '${this.value}'`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNotEquals(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeyEqualsExpr.create(this.key, this.value); } } export class ContextKeyNotExpr implements IContextKeyExpression { public static create(key: string): ContextKeyExpression { const staticValue = STATIC_VALUES.get(key); if (typeof staticValue === 'boolean') { return staticValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE; } return new ContextKeyNotExpr(key); } public readonly type = ContextKeyExprType.Not; private readonly key: string; private constructor(key: string) { this.key = key; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key; } return false; } public evaluate(context: IContext): boolean { return !context.getValue(this.key); } public serialize(): string { return `!${this.key}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNot(this.key); } public negate(): ContextKeyExpression { return ContextKeyDefinedExpr.create(this.key); } } export class ContextKeyGreaterExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { return new ContextKeyGreaterExpr(key, value); } public readonly type = ContextKeyExprType.Greater; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { return parseFloat(<any>context.getValue(this.key)) > parseFloat(this.value); } public serialize(): string { return `${this.key} > ${this.value}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapGreater(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeySmallerEqualsExpr.create(this.key, this.value); } } export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { return new ContextKeyGreaterEqualsExpr(key, value); } public readonly type = ContextKeyExprType.GreaterEquals; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { return parseFloat(<any>context.getValue(this.key)) >= parseFloat(this.value); } public serialize(): string { return `${this.key} >= ${this.value}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapGreaterEquals(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeySmallerExpr.create(this.key, this.value); } } export class ContextKeySmallerExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { return new ContextKeySmallerExpr(key, value); } public readonly type = ContextKeyExprType.Smaller; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { return parseFloat(<any>context.getValue(this.key)) < parseFloat(this.value); } public serialize(): string { return `${this.key} < ${this.value}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapSmaller(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeyGreaterEqualsExpr.create(this.key, this.value); } } export class ContextKeySmallerEqualsExpr implements IContextKeyExpression { public static create(key: string, value: any): ContextKeyExpression { return new ContextKeySmallerEqualsExpr(key, value); } public readonly type = ContextKeyExprType.SmallerEquals; private readonly key: string; private readonly value: any; private constructor(key: string, value: any) { this.key = key; this.value = value; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this.key === other.key && this.value === other.value; } return false; } public evaluate(context: IContext): boolean { return parseFloat(<any>context.getValue(this.key)) <= parseFloat(this.value); } public serialize(): string { return `${this.key} <= ${this.value}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapSmallerEquals(this.key, this.value); } public negate(): ContextKeyExpression { return ContextKeyGreaterExpr.create(this.key, this.value); } } export class ContextKeyRegexExpr implements IContextKeyExpression { public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr { return new ContextKeyRegexExpr(key, regexp); } public readonly type = ContextKeyExprType.Regex; private readonly key: string; private readonly regexp: RegExp | null; private constructor(key: string, regexp: RegExp | null) { // this.key = key; this.regexp = regexp; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } if (this.key < other.key) { return -1; } if (this.key > other.key) { return 1; } const thisSource = this.regexp ? this.regexp.source : ''; const otherSource = other.regexp ? other.regexp.source : ''; if (thisSource < otherSource) { return -1; } if (thisSource > otherSource) { return 1; } return 0; } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { const thisSource = this.regexp ? this.regexp.source : ''; const otherSource = other.regexp ? other.regexp.source : ''; return this.key === other.key && thisSource === otherSource; } return false; } public evaluate(context: IContext): boolean { const value = context.getValue<any>(this.key); return this.regexp ? this.regexp.test(value) : false; } public serialize(): string { const value = this.regexp ? `/${this.regexp.source}/${this.regexp.ignoreCase ? 'i' : ''}` : '/invalid/'; return `${this.key} =~ ${value}`; } public keys(): string[] { return [this.key]; } public map(mapFnc: IContextKeyExprMapper): ContextKeyRegexExpr { return mapFnc.mapRegex(this.key, this.regexp); } public negate(): ContextKeyExpression { return ContextKeyNotRegexExpr.create(this); } } export class ContextKeyNotRegexExpr implements IContextKeyExpression { public static create(actual: ContextKeyRegexExpr): ContextKeyExpression { return new ContextKeyNotRegexExpr(actual); } public readonly type = ContextKeyExprType.NotRegex; private readonly _actual: ContextKeyRegexExpr; private constructor(_actual: ContextKeyRegexExpr) { this._actual = _actual; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } return this._actual.cmp(other._actual); } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { return this._actual.equals(other._actual); } return false; } public evaluate(context: IContext): boolean { return !this._actual.evaluate(context); } public serialize(): string { throw new Error('Method not implemented.'); } public keys(): string[] { return this._actual.keys(); } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyNotRegexExpr(this._actual.map(mapFnc)); } public negate(): ContextKeyExpression { return this._actual; } } export class ContextKeyAndExpr implements IContextKeyExpression { public static create( _expr: readonly (ContextKeyExpression | null | undefined)[], ): ContextKeyExpression | undefined { return ContextKeyAndExpr._normalizeArr(_expr); } public readonly type = ContextKeyExprType.And; public readonly expr: ContextKeyExpression[]; private constructor(expr: ContextKeyExpression[]) { this.expr = expr; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } if (this.expr.length < other.expr.length) { return -1; } if (this.expr.length > other.expr.length) { return 1; } for (let i = 0, len = this.expr.length; i < len; i++) { const r = cmp(this.expr[i], other.expr[i]); if (r !== 0) { return r; } } return 0; } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } for (let i = 0, len = this.expr.length; i < len; i++) { if (!this.expr[i].equals(other.expr[i])) { return false; } } return true; } return false; } public evaluate(context: IContext): boolean { for (let i = 0, len = this.expr.length; i < len; i++) { if (!this.expr[i].evaluate(context)) { return false; } } return true; } private static _normalizeArr( arr: readonly (ContextKeyExpression | null | undefined)[], ): ContextKeyExpression | undefined { const expr: ContextKeyExpression[] = []; let hasTrue = false; for (const e of arr) { if (!e) { continue; } if (e.type === ContextKeyExprType.True) { // anything && true ==> anything hasTrue = true; continue; } if (e.type === ContextKeyExprType.False) { // anything && false ==> false return ContextKeyFalseExpr.INSTANCE; } if (e.type === ContextKeyExprType.And) { expr.push(...e.expr); continue; } expr.push(e); } if (expr.length === 0 && hasTrue) { return ContextKeyTrueExpr.INSTANCE; } if (expr.length === 0) { return undefined; } if (expr.length === 1) { return expr[0]; } expr.sort(cmp); // We must distribute any OR expression because we don't support parens // OR extensions will be at the end (due to sorting rules) while (expr.length > 1) { const lastElement = expr[expr.length - 1]; if (lastElement.type !== ContextKeyExprType.Or) { break; } // pop the last element expr.pop(); // pop the second to last element const secondToLastElement = expr.pop()!; // distribute `lastElement` over `secondToLastElement` const resultElement = ContextKeyOrExpr.create( lastElement.expr.map((el) => ContextKeyAndExpr.create([el, secondToLastElement]), ), ); if (resultElement) { expr.push(resultElement); expr.sort(cmp); } } if (expr.length === 1) { return expr[0]; } return new ContextKeyAndExpr(expr); } public serialize(): string { return this.expr.map((e) => e.serialize()).join(' && '); } public keys(): string[] { const result: string[] = []; for (const expr of this.expr) { result.push(...expr.keys()); } return result; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyAndExpr(this.expr.map((expr) => expr.map(mapFnc))); } public negate(): ContextKeyExpression { const result: ContextKeyExpression[] = []; for (const expr of this.expr) { result.push(expr.negate()); } return ContextKeyOrExpr.create(result)!; } } export class ContextKeyOrExpr implements IContextKeyExpression { public static create( _expr: readonly (ContextKeyExpression | null | undefined)[], ): ContextKeyExpression | undefined { const expr = ContextKeyOrExpr._normalizeArr(_expr); if (expr.length === 0) { return undefined; } if (expr.length === 1) { return expr[0]; } return new ContextKeyOrExpr(expr); } public readonly type = ContextKeyExprType.Or; public readonly expr: ContextKeyExpression[]; private constructor(expr: ContextKeyExpression[]) { this.expr = expr; } public cmp(other: ContextKeyExpression): number { if (other.type !== this.type) { return this.type - other.type; } if (this.expr.length < other.expr.length) { return -1; } if (this.expr.length > other.expr.length) { return 1; } for (let i = 0, len = this.expr.length; i < len; i++) { const r = cmp(this.expr[i], other.expr[i]); if (r !== 0) { return r; } } return 0; } public equals(other: ContextKeyExpression): boolean { if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } for (let i = 0, len = this.expr.length; i < len; i++) { if (!this.expr[i].equals(other.expr[i])) { return false; } } return true; } return false; } public evaluate(context: IContext): boolean { for (let i = 0, len = this.expr.length; i < len; i++) { if (this.expr[i].evaluate(context)) { return true; } } return false; } private static _normalizeArr( arr: readonly (ContextKeyExpression | null | undefined)[], ): ContextKeyExpression[] { let expr: ContextKeyExpression[] = []; let hasFalse = false; if (arr) { for (let i = 0, len = arr.length; i < len; i++) { const e = arr[i]; if (!e) { continue; } if (e.type === ContextKeyExprType.False) { // anything || false ==> anything hasFalse = true; continue; } if (e.type === ContextKeyExprType.True) { // anything || true ==> true return [ContextKeyTrueExpr.INSTANCE]; } if (e.type === ContextKeyExprType.Or) { expr = expr.concat(e.expr); continue; } expr.push(e); } if (expr.length === 0 && hasFalse) { return [ContextKeyFalseExpr.INSTANCE]; } expr.sort(cmp); } return expr; } public serialize(): string { return this.expr.map((e) => e.serialize()).join(' || '); } public keys(): string[] { const result: string[] = []; for (const expr of this.expr) { result.push(...expr.keys()); } return result; } public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyOrExpr(this.expr.map((expr) => expr.map(mapFnc))); } public negate(): ContextKeyExpression { const result: ContextKeyExpression[] = []; for (const expr of this.expr) { result.push(expr.negate()); } const terminals = (node: ContextKeyExpression) => { if (node.type === ContextKeyExprType.Or) { return node.expr; } return [node]; }; // We don't support parens, so here we distribute the AND over the OR terminals // We always take the first 2 AND pairs and distribute them while (result.length > 1) { const LEFT = result.shift()!; const RIGHT = result.shift()!; const all: ContextKeyExpression[] = []; for (const left of terminals(LEFT)) { for (const right of terminals(RIGHT)) { all.push(ContextKeyExpr.and(left, right)!); } } result.unshift(ContextKeyExpr.or(...all)!); } return result[0]; } } export interface ContextKeyInfo { readonly key: string; readonly type?: string | undefined; readonly description?: string | undefined; } export class RawContextKey<T> extends ContextKeyDefinedExpr { private static _info: ContextKeyInfo[] = []; static all(): IterableIterator<ContextKeyInfo> { return RawContextKey._info.values(); } private readonly _defaultValue: T | undefined; override readonly key: string; constructor( key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string; description: string }, ) { super(key); this.key = key; this._defaultValue = defaultValue; // collect all context keys into a central place if (typeof metaOrHide === 'object') { RawContextKey._info.push({ ...metaOrHide, key }); } else if (metaOrHide !== true) { RawContextKey._info.push({ key, description: metaOrHide, type: defaultValue !== null && defaultValue !== undefined ? typeof defaultValue : undefined, }); } } public bindTo(target: IContextKeyService): IContextKey<T> { return target.createKey(this.key, this._defaultValue); } public getValue(target: IContextKeyService): T | undefined { return target.getContextKeyValue<T>(this.key); } public toNegated(): ContextKeyExpression { return ContextKeyExpr.not(this.key); } public isEqualTo(value: any): ContextKeyExpression { return ContextKeyExpr.equals(this.key, value); } public notEqualsTo(value: any): ContextKeyExpression { return ContextKeyExpr.notEquals(this.key, value); } } export interface IContext { getValue: <T>(key: string) => T | undefined; } export interface IContextKey<T> { set: (value: T) => void; reset: () => void; get: () => T | undefined; } export namespace IContextKey { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const None: IContextKey<any> = Object.freeze({ set: () => { // }, reset: () => { // }, get: () => undefined, }); } export interface IContextKeyServiceTarget { parentElement: IContextKeyServiceTarget | null; setAttribute: (attr: string, value: string) => void; removeAttribute: (attr: string) => void; hasAttribute: (attr: string) => boolean; getAttribute: (attr: string) => string | null; } // export const IContextKeyService = createDecorator<IContextKeyService>('contextKeyService'); export const IContextKeyService = Syringe.defineToken('IContextKeyService'); export interface IReadableSet<T> { has: (value: T) => boolean; } export interface IContextKeyChangeEvent { affectsSome: (keys: IReadableSet<string>) => boolean; } export interface IContextKeyService { readonly _serviceBrand: undefined; dispose: () => void; onDidChangeContext: Event<IContextKeyChangeEvent>; bufferChangeEvents: (callback: () => void) => void; createKey: <T>(key: string, defaultValue: T | undefined) => IContextKey<T>; contextMatchesRules: (rules: ContextKeyExpression | undefined) => boolean; getContextKeyValue: <T>(key: string) => T | undefined; createScoped: (target: IContextKeyServiceTarget) => IContextKeyService; createOverlay: (overlay: Iterable<[string, any]>) => IContextKeyService; getContext: (target: IContextKeyServiceTarget | null) => IContext; updateParent: (parentContextKeyService: IContextKeyService) => void; } export const SET_CONTEXT_COMMAND_ID = 'setContext'; function cmp1(key1: string, key2: string): number { if (key1 < key2) { return -1; } if (key1 > key2) { return 1; } return 0; } function cmp2(key1: string, value1: any, key2: string, value2: any): number { if (key1 < key2) { return -1; } if (key1 > key2) { return 1; } if (value1 < value2) { return -1; } if (value1 > value2) { return 1; } return 0; }