diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 01bef2897cedf..5b536419db98c 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -32,6 +32,10 @@ export const enum ContextKeyExprType { Or = 9, In = 10, NotIn = 11, + Greater = 12, + GreaterEquals = 13, + Smaller = 14, + SmallerEquals = 15, } export interface IContextKeyExprMapper { @@ -39,6 +43,10 @@ export interface IContextKeyExprMapper { 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; } @@ -57,7 +65,9 @@ export interface IContextKeyExpression { export type ContextKeyExpression = ( ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr - | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr | ContextKeyNotInExpr + | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr + | ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr + | ContextKeySmallerExpr | ContextKeySmallerEqualsExpr ); export abstract class ContextKeyExpr { @@ -102,6 +112,14 @@ export abstract class ContextKeyExpr { 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: boolean = false): ContextKeyExpression | undefined { if (!serialized) { return undefined; @@ -143,6 +161,26 @@ export abstract class ContextKeyExpr { return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim()); } + if (serializedOne.indexOf('>=') >= 0) { + const pieces = serializedOne.split('>='); + return ContextKeyGreaterEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (serializedOne.indexOf('>') >= 0) { + const pieces = serializedOne.split('>'); + return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (serializedOne.indexOf('<=') >= 0) { + const pieces = serializedOne.split('<='); + return ContextKeySmallerEqualsExpr.create(pieces[0].trim(), pieces[1].trim()); + } + + if (serializedOne.indexOf('<') >= 0) { + const pieces = serializedOne.split('<'); + return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim()); + } + if (/^\!\s*/.test(serializedOne)) { return ContextKeyNotExpr.create(serializedOne.substr(1).trim()); } @@ -302,13 +340,7 @@ export class ContextKeyDefinedExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - return 0; + return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { @@ -362,19 +394,7 @@ export class ContextKeyEqualsExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.value < other.value) { - return -1; - } - if (this.value > other.value) { - return 1; - } - return 0; + return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { @@ -422,19 +442,7 @@ export class ContextKeyInExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.valueKey < other.valueKey) { - return -1; - } - if (this.valueKey > other.valueKey) { - return 1; - } - return 0; + return cmp2(this.key, this.valueKey, other.key, other.valueKey); } public equals(other: ContextKeyExpression): boolean { @@ -549,19 +557,7 @@ export class ContextKeyNotEqualsExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - if (this.value < other.value) { - return -1; - } - if (this.value > other.value) { - return 1; - } - return 0; + return cmp2(this.key, this.value, other.key, other.value); } public equals(other: ContextKeyExpression): boolean { @@ -613,13 +609,7 @@ export class ContextKeyNotExpr implements IContextKeyExpression { if (other.type !== this.type) { return this.type - other.type; } - if (this.key < other.key) { - return -1; - } - if (this.key > other.key) { - return 1; - } - return 0; + return cmp1(this.key, other.key); } public equals(other: ContextKeyExpression): boolean { @@ -650,6 +640,200 @@ export class ContextKeyNotExpr implements IContextKeyExpression { } } +export class ContextKeyGreaterExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + return new ContextKeyGreaterExpr(key, value); + } + + public readonly type = ContextKeyExprType.Greater; + + private constructor( + private readonly key: string, + private readonly value: any + ) { } + + 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(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 constructor( + private readonly key: string, + private readonly value: any + ) { } + + 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(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 constructor( + private readonly key: string, + private readonly value: any + ) { + } + + 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(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 constructor( + private readonly key: string, + private readonly value: any + ) { + } + + 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(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 { @@ -1143,3 +1327,29 @@ export interface IContextKeyService { } 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; +} diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 2912df4b0eac0..5701b9dc86b94 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -186,4 +186,73 @@ suite('ContextKeyExpr', () => { ); assert.equal(actual!.equals(expected!), true); }); + + test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => { + function checkEvaluate(expr: string, ctx: any, expected: any): void { + const _expr = ContextKeyExpr.deserialize(expr)!; + assert.equal(_expr.evaluate(createContext(ctx)), expected); + } + + checkEvaluate('a>1', {}, false); + checkEvaluate('a>1', { a: 0 }, false); + checkEvaluate('a>1', { a: 1 }, false); + checkEvaluate('a>1', { a: 2 }, true); + checkEvaluate('a>1', { a: '0' }, false); + checkEvaluate('a>1', { a: '1' }, false); + checkEvaluate('a>1', { a: '2' }, true); + checkEvaluate('a>1', { a: 'a' }, false); + + checkEvaluate('a>10', { a: 2 }, false); + checkEvaluate('a>10', { a: 11 }, true); + checkEvaluate('a>10', { a: '11' }, true); + checkEvaluate('a>10', { a: '2' }, false); + checkEvaluate('a>10', { a: '11' }, true); + + checkEvaluate('a>1.1', { a: 1 }, false); + checkEvaluate('a>1.1', { a: 2 }, true); + checkEvaluate('a>1.1', { a: 11 }, true); + checkEvaluate('a>1.1', { a: '1.1' }, false); + checkEvaluate('a>1.1', { a: '2' }, true); + checkEvaluate('a>1.1', { a: '11' }, true); + + checkEvaluate('a>b', { a: 'b' }, false); + checkEvaluate('a>b', { a: 'c' }, false); + checkEvaluate('a>b', { a: 1000 }, false); + + checkEvaluate('a >= 2', { a: '1' }, false); + checkEvaluate('a >= 2', { a: '2' }, true); + checkEvaluate('a >= 2', { a: '3' }, true); + + checkEvaluate('a < 2', { a: '1' }, true); + checkEvaluate('a < 2', { a: '2' }, false); + checkEvaluate('a < 2', { a: '3' }, false); + + checkEvaluate('a <= 2', { a: '1' }, true); + checkEvaluate('a <= 2', { a: '2' }, true); + checkEvaluate('a <= 2', { a: '3' }, false); + }); + + test('Greater, GreaterEquals, Smaller, SmallerEquals negate', () => { + function checkNegate(expr: string, expected: string): void { + const a = ContextKeyExpr.deserialize(expr)!; + const b = a.negate(); + assert.equal(b.serialize(), expected); + } + + checkNegate('a>1', 'a <= 1'); + checkNegate('a>1.1', 'a <= 1.1'); + checkNegate('a>b', 'a <= b'); + + checkNegate('a>=1', 'a < 1'); + checkNegate('a>=1.1', 'a < 1.1'); + checkNegate('a>=b', 'a < b'); + + checkNegate('a<1', 'a >= 1'); + checkNegate('a<1.1', 'a >= 1.1'); + checkNegate('a= b'); + + checkNegate('a<=1', 'a > 1'); + checkNegate('a<=1.1', 'a > 1.1'); + checkNegate('a<=b', 'a > b'); + }); });