From 62a86c525378610444a9976dd4409ea207174d20 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Thu, 3 Oct 2019 12:15:12 +0000 Subject: Make key class --- src/content/domains/Key.ts | 129 +++++++++++++++++++++---------------- src/content/domains/KeySequence.ts | 6 +- 2 files changed, 78 insertions(+), 57 deletions(-) (limited to 'src/content/domains') diff --git a/src/content/domains/Key.ts b/src/content/domains/Key.ts index b25616e..669edfc 100644 --- a/src/content/domains/Key.ts +++ b/src/content/domains/Key.ts @@ -1,11 +1,3 @@ -export default interface Key { - key: string; - shiftKey?: boolean; - ctrlKey?: boolean; - altKey?: boolean; - metaKey?: boolean; -} - const modifiedKeyName = (name: string): string => { if (name === ' ') { return 'Space'; @@ -18,55 +10,84 @@ const modifiedKeyName = (name: string): string => { return name; }; -export const fromKeyboardEvent = (e: KeyboardEvent): Key => { - let key = modifiedKeyName(e.key); - let shift = e.shiftKey; - if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { - // make shift false for symbols to enable key bindings by symbold keys. - // But this limits key bindings by symbol keys with Shift (such as Shift+$>. - shift = false; +export default class Key { + public readonly key: string; + + public readonly shift: boolean; + + public readonly ctrl: boolean; + + public readonly alt: boolean; + + public readonly meta: boolean; + + constructor({ key, shift, ctrl, alt, meta }: { + key: string; + shift: boolean; + ctrl: boolean; + alt: boolean; + meta: boolean; + }) { + this.key = key; + this.shift = shift; + this.ctrl = ctrl; + this.alt = alt; + this.meta = meta; } - return { - key: modifiedKeyName(e.key), - shiftKey: shift, - ctrlKey: e.ctrlKey, - altKey: e.altKey, - metaKey: e.metaKey, - }; -}; + static fromMapKey(str: string): Key { + if (str.startsWith('<') && str.endsWith('>')) { + let inner = str.slice(1, -1); + let shift = inner.includes('S-'); + let base = inner.slice(inner.lastIndexOf('-') + 1); + if (shift && base.length === 1) { + base = base.toUpperCase(); + } else if (!shift && base.length === 1) { + base = base.toLowerCase(); + } + return new Key({ + key: base, + shift: shift, + ctrl: inner.includes('C-'), + alt: inner.includes('A-'), + meta: inner.includes('M-'), + }); + } -export const fromMapKey = (key: string): Key => { - if (key.startsWith('<') && key.endsWith('>')) { - let inner = key.slice(1, -1); - let shift = inner.includes('S-'); - let base = inner.slice(inner.lastIndexOf('-') + 1); - if (shift && base.length === 1) { - base = base.toUpperCase(); - } else if (!shift && base.length === 1) { - base = base.toLowerCase(); + return new Key({ + key: str, + shift: str.toLowerCase() !== str, + ctrl: false, + alt: false, + meta: false, + }); + } + + static fromKeyboardEvent(e: KeyboardEvent): Key { + let key = modifiedKeyName(e.key); + let shift = e.shiftKey; + if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { + // make shift false for symbols to enable key bindings by symbold keys. + // But this limits key bindings by symbol keys with Shift + // (such as Shift+$>. + shift = false; } - return { - key: base, - shiftKey: inner.includes('S-'), - ctrlKey: inner.includes('C-'), - altKey: inner.includes('A-'), - metaKey: inner.includes('M-'), - }; + + return new Key({ + key: modifiedKeyName(e.key), + shift: shift, + ctrl: e.ctrlKey, + alt: e.altKey, + meta: e.metaKey, + }); } - return { - key: key, - shiftKey: key.toLowerCase() !== key, - ctrlKey: false, - altKey: false, - metaKey: false, - }; -}; -export const equals = (e1: Key, e2: Key): boolean => { - return e1.key === e2.key && - e1.ctrlKey === e2.ctrlKey && - e1.metaKey === e2.metaKey && - e1.altKey === e2.altKey && - e1.shiftKey === e2.shiftKey; -}; + equals(key: Key) { + return this.key === key.key && + this.ctrl === key.ctrl && + this.meta === key.meta && + this.alt === key.alt && + this.shift === key.shift; + } +} + diff --git a/src/content/domains/KeySequence.ts b/src/content/domains/KeySequence.ts index 6a05c2f..61ceab1 100644 --- a/src/content/domains/KeySequence.ts +++ b/src/content/domains/KeySequence.ts @@ -1,4 +1,4 @@ -import Key, * as keyUtils from './Key'; +import Key from './Key'; export default class KeySequence { private keys: Key[]; @@ -24,7 +24,7 @@ export default class KeySequence { return false; } for (let i = 0; i < o.keys.length; ++i) { - if (!keyUtils.equals(this.keys[i], o.keys[i])) { + if (!this.keys[i].equals(o.keys[i])) { return false; } } @@ -54,7 +54,7 @@ export const fromMapKeys = (keys: string): KeySequence => { return fromMapKeysRecursive( remainings.slice(nextPos), - mappedKeys.concat([keyUtils.fromMapKey(remainings.slice(0, nextPos))]) + mappedKeys.concat([Key.fromMapKey(remainings.slice(0, nextPos))]) ); }; -- cgit v1.2.3 From b496cea5827165bd23a503231f94f708a976cad4 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Thu, 3 Oct 2019 12:32:32 +0000 Subject: Make KeySequence class --- src/content/domains/KeySequence.ts | 60 +++++++++------------- src/content/repositories/KeymapRepository.ts | 4 +- src/content/usecases/KeymapUseCase.ts | 4 +- test/content/domains/KeySequence.test.ts | 57 ++++++++++---------- test/content/repositories/KeymapRepository.test.ts | 8 +-- 5 files changed, 61 insertions(+), 72 deletions(-) (limited to 'src/content/domains') diff --git a/src/content/domains/KeySequence.ts b/src/content/domains/KeySequence.ts index 61ceab1..abae61a 100644 --- a/src/content/domains/KeySequence.ts +++ b/src/content/domains/KeySequence.ts @@ -1,14 +1,9 @@ import Key from './Key'; export default class KeySequence { - private keys: Key[]; - - private constructor(keys: Key[]) { - this.keys = keys; - } - - static from(keys: Key[]): KeySequence { - return new KeySequence(keys); + constructor( + public readonly keys: Key[], + ) { } push(key: Key): number { @@ -31,34 +26,29 @@ export default class KeySequence { return true; } - getKeyArray(): Key[] { - return this.keys; - } -} - -export const fromMapKeys = (keys: string): KeySequence => { - const fromMapKeysRecursive = ( - remainings: string, mappedKeys: Key[], - ): Key[] => { - if (remainings.length === 0) { - return mappedKeys; - } - - let nextPos = 1; - if (remainings.startsWith('<')) { - let ltPos = remainings.indexOf('>'); - if (ltPos > 0) { - nextPos = ltPos + 1; + static fromMapKeys(keys: string): KeySequence { + const fromMapKeysRecursive = ( + remaining: string, mappedKeys: Key[], + ): Key[] => { + if (remaining.length === 0) { + return mappedKeys; } - } - return fromMapKeysRecursive( - remainings.slice(nextPos), - mappedKeys.concat([Key.fromMapKey(remainings.slice(0, nextPos))]) - ); - }; + let nextPos = 1; + if (remaining.startsWith('<')) { + let ltPos = remaining.indexOf('>'); + if (ltPos > 0) { + nextPos = ltPos + 1; + } + } - let data = fromMapKeysRecursive(keys, []); - return KeySequence.from(data); -}; + return fromMapKeysRecursive( + remaining.slice(nextPos), + mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))]) + ); + }; + let data = fromMapKeysRecursive(keys, []); + return new KeySequence(data); + } +} diff --git a/src/content/repositories/KeymapRepository.ts b/src/content/repositories/KeymapRepository.ts index d7b5b7d..4463678 100644 --- a/src/content/repositories/KeymapRepository.ts +++ b/src/content/repositories/KeymapRepository.ts @@ -7,7 +7,7 @@ export default interface KeymapRepository { clear(): void; } -let current: KeySequence = KeySequence.from([]); +let current: KeySequence = new KeySequence([]); export class KeymapRepositoryImpl { @@ -17,6 +17,6 @@ export class KeymapRepositoryImpl { } clear(): void { - current = KeySequence.from([]); + current = new KeySequence([]); } } diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts index d0d039a..0f654c8 100644 --- a/src/content/usecases/KeymapUseCase.ts +++ b/src/content/usecases/KeymapUseCase.ts @@ -5,7 +5,7 @@ import AddonEnabledRepository from '../repositories/AddonEnabledRepository'; import * as operations from '../../shared/operations'; import { Keymaps } from '../../shared/Settings'; import Key from '../domains/Key'; -import KeySequence, * as keySequenceUtils from '../domains/KeySequence'; +import KeySequence from '../domains/KeySequence'; type KeymapEntityMap = Map; @@ -71,7 +71,7 @@ export default class KeymapUseCase { }; let entries = Object.entries(keymaps).map((entry) => { return [ - keySequenceUtils.fromMapKeys(entry[0]), + KeySequence.fromMapKeys(entry[0]), entry[1], ]; }) as [KeySequence, operations.Operation][]; diff --git a/test/content/domains/KeySequence.test.ts b/test/content/domains/KeySequence.test.ts index 9afc360..62af165 100644 --- a/test/content/domains/KeySequence.test.ts +++ b/test/content/domains/KeySequence.test.ts @@ -1,49 +1,48 @@ -import KeySequence, * as utils from '../../../src/content/domains/KeySequence'; +import KeySequence from '../../../src/content/domains/KeySequence'; import Key from '../../../src/content/domains/Key'; import { expect } from 'chai' describe("KeySequence", () => { describe('#push', () => { it('append a key to the sequence', () => { - let seq = KeySequence.from([]); + let seq = new KeySequence([]); seq.push(Key.fromMapKey('g')); seq.push(Key.fromMapKey('')); - let array = seq.getKeyArray(); - expect(array[0].key).to.equal('g'); - expect(array[1].key).to.equal('U'); - expect(array[1].shift).to.be.true; + expect(seq.keys[0].key).to.equal('g'); + expect(seq.keys[1].key).to.equal('U'); + expect(seq.keys[1].shift).to.be.true; }) }); describe('#startsWith', () => { it('returns true if the key sequence starts with param', () => { - let seq = KeySequence.from([ + let seq = new KeySequence([ Key.fromMapKey('g'), Key.fromMapKey(''), ]); - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([ ]))).to.be.true; - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([ Key.fromMapKey('g'), ]))).to.be.true; - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([ Key.fromMapKey('g'), Key.fromMapKey(''), ]))).to.be.true; - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([ Key.fromMapKey('g'), Key.fromMapKey(''), Key.fromMapKey('x'), ]))).to.be.false; - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([ Key.fromMapKey('h'), ]))).to.be.false; }); it('returns true if the empty sequence starts with an empty sequence', () => { - let seq = KeySequence.from([]); + let seq = new KeySequence([]); - expect(seq.startsWith(KeySequence.from([]))).to.be.true; - expect(seq.startsWith(KeySequence.from([ + expect(seq.startsWith(new KeySequence([]))).to.be.true; + expect(seq.startsWith(new KeySequence([ Key.fromMapKey('h'), ]))).to.be.false; }) @@ -51,23 +50,23 @@ describe("KeySequence", () => { describe('#fromMapKeys', () => { it('returns mapped keys for Shift+Esc', () => { - let keyArray = utils.fromMapKeys('').getKeyArray(); - expect(keyArray).to.have.lengthOf(1); - expect(keyArray[0].key).to.equal('Esc'); - expect(keyArray[0].shift).to.be.true; + let keys = KeySequence.fromMapKeys('').keys; + expect(keys).to.have.lengthOf(1); + expect(keys[0].key).to.equal('Esc'); + expect(keys[0].shift).to.be.true; }); it('returns mapped keys for ad', () => { - let keyArray = utils.fromMapKeys('ad').getKeyArray(); - expect(keyArray).to.have.lengthOf(5); - expect(keyArray[0].key).to.equal('a'); - expect(keyArray[1].ctrl).to.be.true; - expect(keyArray[1].key).to.equal('b'); - expect(keyArray[2].alt).to.be.true; - expect(keyArray[2].key).to.equal('c'); - expect(keyArray[3].key).to.equal('d'); - expect(keyArray[4].meta).to.be.true; - expect(keyArray[4].key).to.equal('e'); + let keys = KeySequence.fromMapKeys('ad').keys; + expect(keys).to.have.lengthOf(5); + expect(keys[0].key).to.equal('a'); + expect(keys[1].ctrl).to.be.true; + expect(keys[1].key).to.equal('b'); + expect(keys[2].alt).to.be.true; + expect(keys[2].key).to.equal('c'); + expect(keys[3].key).to.equal('d'); + expect(keys[4].meta).to.be.true; + expect(keys[4].key).to.equal('e'); }); }) }); diff --git a/test/content/repositories/KeymapRepository.test.ts b/test/content/repositories/KeymapRepository.test.ts index 8f0be67..da5624e 100644 --- a/test/content/repositories/KeymapRepository.test.ts +++ b/test/content/repositories/KeymapRepository.test.ts @@ -16,7 +16,7 @@ describe('KeymapRepositoryImpl', () => { sut.enqueueKey(Key.fromMapKey('b'); let sequence = sut.enqueueKey(Key.fromMapKey('c')); - let keys = sequence.getKeyArray(); + let keys = sequence.keys; expect(keys[0].equals(Key.fromMapKey('a'))).to.be.true; expect(keys[1].equals(Key.fromMapKey('b'))).to.be.true; expect(keys[2].equals(Key.fromMapKey('c'))).to.be.true; @@ -25,9 +25,9 @@ describe('KeymapRepositoryImpl', () => { describe('#clear()', () => { it('clears keys', () => { - sut.enqueueKey(Key.fromMapKey('a'); - sut.enqueueKey(Key.fromMapKey('b'); - sut.enqueueKey(Key.fromMapKey('c'); + sut.enqueueKey(Key.fromMapKey('a')); + sut.enqueueKey(Key.fromMapKey('b')); + sut.enqueueKey(Key.fromMapKey('c')); sut.clear(); let sequence = sut.enqueueKey(Key.fromMapKey('a')); -- cgit v1.2.3 From da3ce77aa0a50568ee4ddb423d07bc50422bd79c Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sun, 6 Oct 2019 03:21:38 +0000 Subject: Move Key to settings --- src/content/InputDriver.ts | 36 +++++- src/content/client/FollowMasterClient.ts | 2 +- src/content/controllers/FollowKeyController.ts | 2 +- src/content/controllers/KeymapController.ts | 2 +- src/content/controllers/MarkKeyController.ts | 2 +- src/content/domains/Key.ts | 93 -------------- src/content/domains/KeySequence.ts | 54 -------- src/content/repositories/KeymapRepository.ts | 4 +- src/content/usecases/FollowSlaveUseCase.ts | 2 +- src/content/usecases/KeymapUseCase.ts | 11 +- src/shared/settings/Key.ts | 61 +++++++++ src/shared/settings/KeySequence.ts | 54 ++++++++ test/content/InputDriver.test.ts | 51 +++++++- test/content/domains/Key.test.ts | 139 --------------------- test/content/domains/KeySequence.test.ts | 72 ----------- test/content/repositories/KeymapRepository.test.ts | 2 +- test/shared/settings/Key.test.ts | 92 ++++++++++++++ test/shared/settings/KeySequence.test.ts | 72 +++++++++++ 18 files changed, 375 insertions(+), 376 deletions(-) delete mode 100644 src/content/domains/Key.ts delete mode 100644 src/content/domains/KeySequence.ts create mode 100644 src/shared/settings/Key.ts create mode 100644 src/shared/settings/KeySequence.ts delete mode 100644 test/content/domains/Key.test.ts delete mode 100644 test/content/domains/KeySequence.test.ts create mode 100644 test/shared/settings/Key.test.ts create mode 100644 test/shared/settings/KeySequence.test.ts (limited to 'src/content/domains') diff --git a/src/content/InputDriver.ts b/src/content/InputDriver.ts index e77d857..bc184d2 100644 --- a/src/content/InputDriver.ts +++ b/src/content/InputDriver.ts @@ -1,5 +1,5 @@ import * as dom from '../shared/utils/dom'; -import Key from './domains/Key'; +import Key from '../shared/settings/Key'; const cancelKey = (e: KeyboardEvent): boolean => { if (e.key === 'Escape') { @@ -11,6 +11,38 @@ const cancelKey = (e: KeyboardEvent): boolean => { return false; }; +const modifiedKeyName = (name: string): string => { + if (name === ' ') { + return 'Space'; + } + if (name.length === 1) { + return name; + } else if (name === 'Escape') { + return 'Esc'; + } + return name; +}; + +// visible for testing +export const keyFromKeyboardEvent = (e: KeyboardEvent): Key => { + let key = modifiedKeyName(e.key); + let shift = e.shiftKey; + if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { + // make shift false for symbols to enable key bindings by symbold keys. + // But this limits key bindings by symbol keys with Shift + // (such as Shift+$>. + shift = false; + } + + return new Key({ + key: modifiedKeyName(e.key), + shift: shift, + ctrl: e.ctrlKey, + alt: e.altKey, + meta: e.metaKey, + }); +}; + export default class InputDriver { private pressed: {[key: string]: string} = {}; @@ -66,7 +98,7 @@ export default class InputDriver { return; } - let key = Key.fromKeyboardEvent(e); + let key = keyFromKeyboardEvent(e); for (let listener of this.onKeyListeners) { let stop = listener(key); if (stop) { diff --git a/src/content/client/FollowMasterClient.ts b/src/content/client/FollowMasterClient.ts index f79c8b0..6681e8a 100644 --- a/src/content/client/FollowMasterClient.ts +++ b/src/content/client/FollowMasterClient.ts @@ -1,5 +1,5 @@ import * as messages from '../../shared/messages'; -import Key from '../domains/Key'; +import Key from '../../shared/settings/Key'; export default interface FollowMasterClient { startFollow(newTab: boolean, background: boolean): void; diff --git a/src/content/controllers/FollowKeyController.ts b/src/content/controllers/FollowKeyController.ts index 59d2271..0fd94ff 100644 --- a/src/content/controllers/FollowKeyController.ts +++ b/src/content/controllers/FollowKeyController.ts @@ -1,6 +1,6 @@ import { injectable } from 'tsyringe'; import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase'; -import Key from '../domains/Key'; +import Key from '../../shared/settings/Key'; @injectable() export default class FollowKeyController { diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts index fcfaff1..6157a71 100644 --- a/src/content/controllers/KeymapController.ts +++ b/src/content/controllers/KeymapController.ts @@ -9,7 +9,7 @@ import ClipboardUseCase from '../usecases/ClipboardUseCase'; import OperationClient from '../client/OperationClient'; import MarkKeyyUseCase from '../usecases/MarkKeyUseCase'; import FollowMasterClient from '../client/FollowMasterClient'; -import Key from '../domains/Key'; +import Key from '../../shared/settings/Key'; @injectable() export default class KeymapController { diff --git a/src/content/controllers/MarkKeyController.ts b/src/content/controllers/MarkKeyController.ts index 886e5ff..e7653ee 100644 --- a/src/content/controllers/MarkKeyController.ts +++ b/src/content/controllers/MarkKeyController.ts @@ -1,7 +1,7 @@ import { injectable } from 'tsyringe'; import MarkUseCase from '../usecases/MarkUseCase'; import MarkKeyyUseCase from '../usecases/MarkKeyUseCase'; -import Key from '../domains/Key'; +import Key from '../../shared/settings/Key'; @injectable() export default class MarkKeyController { diff --git a/src/content/domains/Key.ts b/src/content/domains/Key.ts deleted file mode 100644 index 669edfc..0000000 --- a/src/content/domains/Key.ts +++ /dev/null @@ -1,93 +0,0 @@ -const modifiedKeyName = (name: string): string => { - if (name === ' ') { - return 'Space'; - } - if (name.length === 1) { - return name; - } else if (name === 'Escape') { - return 'Esc'; - } - return name; -}; - -export default class Key { - public readonly key: string; - - public readonly shift: boolean; - - public readonly ctrl: boolean; - - public readonly alt: boolean; - - public readonly meta: boolean; - - constructor({ key, shift, ctrl, alt, meta }: { - key: string; - shift: boolean; - ctrl: boolean; - alt: boolean; - meta: boolean; - }) { - this.key = key; - this.shift = shift; - this.ctrl = ctrl; - this.alt = alt; - this.meta = meta; - } - - static fromMapKey(str: string): Key { - if (str.startsWith('<') && str.endsWith('>')) { - let inner = str.slice(1, -1); - let shift = inner.includes('S-'); - let base = inner.slice(inner.lastIndexOf('-') + 1); - if (shift && base.length === 1) { - base = base.toUpperCase(); - } else if (!shift && base.length === 1) { - base = base.toLowerCase(); - } - return new Key({ - key: base, - shift: shift, - ctrl: inner.includes('C-'), - alt: inner.includes('A-'), - meta: inner.includes('M-'), - }); - } - - return new Key({ - key: str, - shift: str.toLowerCase() !== str, - ctrl: false, - alt: false, - meta: false, - }); - } - - static fromKeyboardEvent(e: KeyboardEvent): Key { - let key = modifiedKeyName(e.key); - let shift = e.shiftKey; - if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) { - // make shift false for symbols to enable key bindings by symbold keys. - // But this limits key bindings by symbol keys with Shift - // (such as Shift+$>. - shift = false; - } - - return new Key({ - key: modifiedKeyName(e.key), - shift: shift, - ctrl: e.ctrlKey, - alt: e.altKey, - meta: e.metaKey, - }); - } - - equals(key: Key) { - return this.key === key.key && - this.ctrl === key.ctrl && - this.meta === key.meta && - this.alt === key.alt && - this.shift === key.shift; - } -} - diff --git a/src/content/domains/KeySequence.ts b/src/content/domains/KeySequence.ts deleted file mode 100644 index abae61a..0000000 --- a/src/content/domains/KeySequence.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Key from './Key'; - -export default class KeySequence { - constructor( - public readonly keys: Key[], - ) { - } - - push(key: Key): number { - return this.keys.push(key); - } - - length(): number { - return this.keys.length; - } - - startsWith(o: KeySequence): boolean { - if (this.keys.length < o.keys.length) { - return false; - } - for (let i = 0; i < o.keys.length; ++i) { - if (!this.keys[i].equals(o.keys[i])) { - return false; - } - } - return true; - } - - static fromMapKeys(keys: string): KeySequence { - const fromMapKeysRecursive = ( - remaining: string, mappedKeys: Key[], - ): Key[] => { - if (remaining.length === 0) { - return mappedKeys; - } - - let nextPos = 1; - if (remaining.startsWith('<')) { - let ltPos = remaining.indexOf('>'); - if (ltPos > 0) { - nextPos = ltPos + 1; - } - } - - return fromMapKeysRecursive( - remaining.slice(nextPos), - mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))]) - ); - }; - - let data = fromMapKeysRecursive(keys, []); - return new KeySequence(data); - } -} diff --git a/src/content/repositories/KeymapRepository.ts b/src/content/repositories/KeymapRepository.ts index 4463678..3391229 100644 --- a/src/content/repositories/KeymapRepository.ts +++ b/src/content/repositories/KeymapRepository.ts @@ -1,5 +1,5 @@ -import Key from '../domains/Key'; -import KeySequence from '../domains/KeySequence'; +import Key from '../../shared/settings/Key'; +import KeySequence from '../../shared/settings/KeySequence'; export default interface KeymapRepository { enqueueKey(key: Key): KeySequence; diff --git a/src/content/usecases/FollowSlaveUseCase.ts b/src/content/usecases/FollowSlaveUseCase.ts index 2bd16ee..d471adb 100644 --- a/src/content/usecases/FollowSlaveUseCase.ts +++ b/src/content/usecases/FollowSlaveUseCase.ts @@ -4,7 +4,7 @@ import FollowPresenter from '../presenters/FollowPresenter'; import TabsClient from '../client/TabsClient'; import FollowMasterClient from '../client/FollowMasterClient'; import { LinkHint, InputHint } from '../presenters/Hint'; -import Key from '../domains/Key'; +import Key from '../../shared/settings/Key'; interface Size { width: number; diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts index 62cd04c..495f6d0 100644 --- a/src/content/usecases/KeymapUseCase.ts +++ b/src/content/usecases/KeymapUseCase.ts @@ -3,9 +3,9 @@ import KeymapRepository from '../repositories/KeymapRepository'; import SettingRepository from '../repositories/SettingRepository'; import AddonEnabledRepository from '../repositories/AddonEnabledRepository'; import * as operations from '../../shared/operations'; -import Key from '../domains/Key'; -import KeySequence from '../domains/KeySequence'; import Keymaps from '../../shared/settings/Keymaps'; +import Key from '../../shared/settings/Key'; +import KeySequence from '../../shared/settings/KeySequence'; type KeymapEntityMap = Map; @@ -66,10 +66,9 @@ export default class KeymapUseCase { private keymapEntityMap(): KeymapEntityMap { let keymaps = this.settingRepository.get().keymaps.combine(reservedKeymaps); - let entries = keymaps.entries().map(entry => [ - KeySequence.fromMapKeys(entry[0]), - entry[1], - ]) as [KeySequence, operations.Operation][]; + let entries = keymaps.entries().map( + ([keys, op]) => [KeySequence.fromMapKeys(keys), op] + ) as [KeySequence, operations.Operation][]; return new Map(entries); } } diff --git a/src/shared/settings/Key.ts b/src/shared/settings/Key.ts new file mode 100644 index 0000000..b11eeb2 --- /dev/null +++ b/src/shared/settings/Key.ts @@ -0,0 +1,61 @@ +export default class Key { + public readonly key: string; + + public readonly shift: boolean; + + public readonly ctrl: boolean; + + public readonly alt: boolean; + + public readonly meta: boolean; + + constructor({ key, shift, ctrl, alt, meta }: { + key: string; + shift: boolean; + ctrl: boolean; + alt: boolean; + meta: boolean; + }) { + this.key = key; + this.shift = shift; + this.ctrl = ctrl; + this.alt = alt; + this.meta = meta; + } + + static fromMapKey(str: string): Key { + if (str.startsWith('<') && str.endsWith('>')) { + let inner = str.slice(1, -1); + let shift = inner.includes('S-'); + let base = inner.slice(inner.lastIndexOf('-') + 1); + if (shift && base.length === 1) { + base = base.toUpperCase(); + } else if (!shift && base.length === 1) { + base = base.toLowerCase(); + } + return new Key({ + key: base, + shift: shift, + ctrl: inner.includes('C-'), + alt: inner.includes('A-'), + meta: inner.includes('M-'), + }); + } + + return new Key({ + key: str, + shift: str.toLowerCase() !== str, + ctrl: false, + alt: false, + meta: false, + }); + } + + equals(key: Key) { + return this.key === key.key && + this.ctrl === key.ctrl && + this.meta === key.meta && + this.alt === key.alt && + this.shift === key.shift; + } +} diff --git a/src/shared/settings/KeySequence.ts b/src/shared/settings/KeySequence.ts new file mode 100644 index 0000000..4955583 --- /dev/null +++ b/src/shared/settings/KeySequence.ts @@ -0,0 +1,54 @@ +import Key from '../../shared/settings/Key'; + +export default class KeySequence { + constructor( + public readonly keys: Key[], + ) { + } + + push(key: Key): number { + return this.keys.push(key); + } + + length(): number { + return this.keys.length; + } + + startsWith(o: KeySequence): boolean { + if (this.keys.length < o.keys.length) { + return false; + } + for (let i = 0; i < o.keys.length; ++i) { + if (!this.keys[i].equals(o.keys[i])) { + return false; + } + } + return true; + } + + static fromMapKeys(keys: string): KeySequence { + const fromMapKeysRecursive = ( + remaining: string, mappedKeys: Key[], + ): Key[] => { + if (remaining.length === 0) { + return mappedKeys; + } + + let nextPos = 1; + if (remaining.startsWith('<')) { + let ltPos = remaining.indexOf('>'); + if (ltPos > 0) { + nextPos = ltPos + 1; + } + } + + return fromMapKeysRecursive( + remaining.slice(nextPos), + mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))]) + ); + }; + + let data = fromMapKeysRecursive(keys, []); + return new KeySequence(data); + } +} diff --git a/test/content/InputDriver.test.ts b/test/content/InputDriver.test.ts index b39312c..441d107 100644 --- a/test/content/InputDriver.test.ts +++ b/test/content/InputDriver.test.ts @@ -1,6 +1,6 @@ -import InputDriver from '../../src/content/InputDriver'; +import InputDriver, {keyFromKeyboardEvent} from '../../src/content/InputDriver'; import { expect } from 'chai'; -import Key from '../../src/content/domains/Key'; +import Key from '../../src/shared/settings/Key'; describe('InputDriver', () => { let target: HTMLElement; @@ -127,3 +127,50 @@ describe('InputDriver', () => { div.dispatchEvent(new KeyboardEvent('keydown', { key: 'x' })); }); }); + +describe("#keyFromKeyboardEvent", () => { + it('returns from keyboard input Ctrl+X', () => { + let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', { + key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true, + })); + expect(k.key).to.equal('x'); + expect(k.shift).to.be.false; + expect(k.ctrl).to.be.true; + expect(k.alt).to.be.false; + expect(k.meta).to.be.true; + }); + + it('returns from keyboard input Shift+Esc', () => { + let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', { + key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true + })); + expect(k.key).to.equal('Esc'); + expect(k.shift).to.be.true; + expect(k.ctrl).to.be.false; + expect(k.alt).to.be.false; + expect(k.meta).to.be.true; + }); + + it('returns from keyboard input Ctrl+$', () => { + // $ required shift pressing on most keyboards + let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', { + key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false + })); + expect(k.key).to.equal('$'); + expect(k.shift).to.be.false; + expect(k.ctrl).to.be.true; + expect(k.alt).to.be.false; + expect(k.meta).to.be.false; + }); + + it('returns from keyboard input Crtl+Space', () => { + let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', { + key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false + })); + expect(k.key).to.equal('Space'); + expect(k.shift).to.be.false; + expect(k.ctrl).to.be.true; + expect(k.alt).to.be.false; + expect(k.meta).to.be.false; + }); +}); diff --git a/test/content/domains/Key.test.ts b/test/content/domains/Key.test.ts deleted file mode 100644 index 8e62f80..0000000 --- a/test/content/domains/Key.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import Key from '../../../src/content/domains/Key'; -import { expect } from 'chai' - -describe("Key", () => { - describe('fromKeyboardEvent', () => { - it('returns from keyboard input Ctrl+X', () => { - let k = Key.fromKeyboardEvent(new KeyboardEvent('keydown', { - key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true, - })); - expect(k.key).to.equal('x'); - expect(k.shift).to.be.false; - expect(k.ctrl).to.be.true; - expect(k.alt).to.be.false; - expect(k.meta).to.be.true; - }); - - it('returns from keyboard input Shift+Esc', () => { - let k = Key.fromKeyboardEvent(new KeyboardEvent('keydown', { - key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true - })); - expect(k.key).to.equal('Esc'); - expect(k.shift).to.be.true; - expect(k.ctrl).to.be.false; - expect(k.alt).to.be.false; - expect(k.meta).to.be.true; - }); - - it('returns from keyboard input Ctrl+$', () => { - // $ required shift pressing on most keyboards - let k = Key.fromKeyboardEvent(new KeyboardEvent('keydown', { - key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false - })); - expect(k.key).to.equal('$'); - expect(k.shift).to.be.false; - expect(k.ctrl).to.be.true; - expect(k.alt).to.be.false; - expect(k.meta).to.be.false; - }); - - it('returns from keyboard input Crtl+Space', () => { - let k = Key.fromKeyboardEvent(new KeyboardEvent('keydown', { - key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false - })); - expect(k.key).to.equal('Space'); - expect(k.shift).to.be.false; - expect(k.ctrl).to.be.true; - expect(k.alt).to.be.false; - expect(k.meta).to.be.false; - }); - }); - - describe('fromMapKey', () => { - it('return for X', () => { - let key = Key.fromMapKey('x'); - expect(key.key).to.equal('x'); - expect(key.shift).to.be.false; - expect(key.ctrl).to.be.false; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('return for Shift+X', () => { - let key = Key.fromMapKey('X'); - expect(key.key).to.equal('X'); - expect(key.shift).to.be.true; - expect(key.ctrl).to.be.false; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('return for Ctrl+X', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('x'); - expect(key.shift).to.be.false; - expect(key.ctrl).to.be.true; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('returns for Ctrl+Meta+X', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('x'); - expect(key.shift).to.be.false; - expect(key.ctrl).to.be.true; - expect(key.alt).to.be.false; - expect(key.meta).to.be.true; - }); - - it('returns for Ctrl+Shift+x', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('X'); - expect(key.shift).to.be.true; - expect(key.ctrl).to.be.true; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('returns for Shift+Esc', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('Esc'); - expect(key.shift).to.be.true; - expect(key.ctrl).to.be.false; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('returns for Ctrl+Esc', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('Esc'); - expect(key.shift).to.be.false; - expect(key.ctrl).to.be.true; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - - it('returns for Ctrl+Esc', () => { - let key = Key.fromMapKey(''); - expect(key.key).to.equal('Space'); - expect(key.shift).to.be.false; - expect(key.ctrl).to.be.true; - expect(key.alt).to.be.false; - expect(key.meta).to.be.false; - }); - }); - - describe('equals', () => { - expect(new Key({ - key: 'x', shift: false, ctrl: true, alt: false, meta: false, - }).equals(new Key({ - key: 'x', shift: false, ctrl: true, alt: false, meta: false, - }))).to.be.true; - - expect(new Key({ - key: 'x', shift: false, ctrl: false, alt: false, meta: false, - }).equals(new Key({ - key: 'X', shift: true, ctrl: false, alt: false, meta: false, - }))).to.be.false; - }); -}); diff --git a/test/content/domains/KeySequence.test.ts b/test/content/domains/KeySequence.test.ts deleted file mode 100644 index 62af165..0000000 --- a/test/content/domains/KeySequence.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import KeySequence from '../../../src/content/domains/KeySequence'; -import Key from '../../../src/content/domains/Key'; -import { expect } from 'chai' - -describe("KeySequence", () => { - describe('#push', () => { - it('append a key to the sequence', () => { - let seq = new KeySequence([]); - seq.push(Key.fromMapKey('g')); - seq.push(Key.fromMapKey('')); - - expect(seq.keys[0].key).to.equal('g'); - expect(seq.keys[1].key).to.equal('U'); - expect(seq.keys[1].shift).to.be.true; - }) - }); - - describe('#startsWith', () => { - it('returns true if the key sequence starts with param', () => { - let seq = new KeySequence([ - Key.fromMapKey('g'), - Key.fromMapKey(''), - ]); - - expect(seq.startsWith(new KeySequence([ - ]))).to.be.true; - expect(seq.startsWith(new KeySequence([ - Key.fromMapKey('g'), - ]))).to.be.true; - expect(seq.startsWith(new KeySequence([ - Key.fromMapKey('g'), Key.fromMapKey(''), - ]))).to.be.true; - expect(seq.startsWith(new KeySequence([ - Key.fromMapKey('g'), Key.fromMapKey(''), Key.fromMapKey('x'), - ]))).to.be.false; - expect(seq.startsWith(new KeySequence([ - Key.fromMapKey('h'), - ]))).to.be.false; - }); - - it('returns true if the empty sequence starts with an empty sequence', () => { - let seq = new KeySequence([]); - - expect(seq.startsWith(new KeySequence([]))).to.be.true; - expect(seq.startsWith(new KeySequence([ - Key.fromMapKey('h'), - ]))).to.be.false; - }) - }); - - describe('#fromMapKeys', () => { - it('returns mapped keys for Shift+Esc', () => { - let keys = KeySequence.fromMapKeys('').keys; - expect(keys).to.have.lengthOf(1); - expect(keys[0].key).to.equal('Esc'); - expect(keys[0].shift).to.be.true; - }); - - it('returns mapped keys for ad', () => { - let keys = KeySequence.fromMapKeys('ad').keys; - expect(keys).to.have.lengthOf(5); - expect(keys[0].key).to.equal('a'); - expect(keys[1].ctrl).to.be.true; - expect(keys[1].key).to.equal('b'); - expect(keys[2].alt).to.be.true; - expect(keys[2].key).to.equal('c'); - expect(keys[3].key).to.equal('d'); - expect(keys[4].meta).to.be.true; - expect(keys[4].key).to.equal('e'); - }); - }) -}); diff --git a/test/content/repositories/KeymapRepository.test.ts b/test/content/repositories/KeymapRepository.test.ts index da5624e..df013df 100644 --- a/test/content/repositories/KeymapRepository.test.ts +++ b/test/content/repositories/KeymapRepository.test.ts @@ -1,7 +1,7 @@ import KeymapRepository, { KeymapRepositoryImpl } from '../../../src/content/repositories/KeymapRepository'; -import Key from '../../../src/content/domains/Key' import { expect } from 'chai'; +import Key from "../../../src/shared/settings/Key"; describe('KeymapRepositoryImpl', () => { let sut: KeymapRepository; diff --git a/test/shared/settings/Key.test.ts b/test/shared/settings/Key.test.ts new file mode 100644 index 0000000..8222d5a --- /dev/null +++ b/test/shared/settings/Key.test.ts @@ -0,0 +1,92 @@ +import { expect } from 'chai' +import Key from '../../../src/shared/settings/Key'; + +describe("Key", () => { + describe('fromMapKey', () => { + it('return for X', () => { + let key = Key.fromMapKey('x'); + expect(key.key).to.equal('x'); + expect(key.shift).to.be.false; + expect(key.ctrl).to.be.false; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('return for Shift+X', () => { + let key = Key.fromMapKey('X'); + expect(key.key).to.equal('X'); + expect(key.shift).to.be.true; + expect(key.ctrl).to.be.false; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('return for Ctrl+X', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('x'); + expect(key.shift).to.be.false; + expect(key.ctrl).to.be.true; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('returns for Ctrl+Meta+X', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('x'); + expect(key.shift).to.be.false; + expect(key.ctrl).to.be.true; + expect(key.alt).to.be.false; + expect(key.meta).to.be.true; + }); + + it('returns for Ctrl+Shift+x', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('X'); + expect(key.shift).to.be.true; + expect(key.ctrl).to.be.true; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('returns for Shift+Esc', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('Esc'); + expect(key.shift).to.be.true; + expect(key.ctrl).to.be.false; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('returns for Ctrl+Esc', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('Esc'); + expect(key.shift).to.be.false; + expect(key.ctrl).to.be.true; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + + it('returns for Ctrl+Esc', () => { + let key = Key.fromMapKey(''); + expect(key.key).to.equal('Space'); + expect(key.shift).to.be.false; + expect(key.ctrl).to.be.true; + expect(key.alt).to.be.false; + expect(key.meta).to.be.false; + }); + }); + + describe('equals', () => { + expect(new Key({ + key: 'x', shift: false, ctrl: true, alt: false, meta: false, + }).equals(new Key({ + key: 'x', shift: false, ctrl: true, alt: false, meta: false, + }))).to.be.true; + + expect(new Key({ + key: 'x', shift: false, ctrl: false, alt: false, meta: false, + }).equals(new Key({ + key: 'X', shift: true, ctrl: false, alt: false, meta: false, + }))).to.be.false; + }); +}); diff --git a/test/shared/settings/KeySequence.test.ts b/test/shared/settings/KeySequence.test.ts new file mode 100644 index 0000000..361cbd1 --- /dev/null +++ b/test/shared/settings/KeySequence.test.ts @@ -0,0 +1,72 @@ +import KeySequence from '../../../src/shared/settings/KeySequence'; +import { expect } from 'chai' +import Key from "../../../src/shared/settings/Key"; + +describe("KeySequence", () => { + describe('#push', () => { + it('append a key to the sequence', () => { + let seq = new KeySequence([]); + seq.push(Key.fromMapKey('g')); + seq.push(Key.fromMapKey('')); + + expect(seq.keys[0].key).to.equal('g'); + expect(seq.keys[1].key).to.equal('U'); + expect(seq.keys[1].shift).to.be.true; + }) + }); + + describe('#startsWith', () => { + it('returns true if the key sequence starts with param', () => { + let seq = new KeySequence([ + Key.fromMapKey('g'), + Key.fromMapKey(''), + ]); + + expect(seq.startsWith(new KeySequence([ + ]))).to.be.true; + expect(seq.startsWith(new KeySequence([ + Key.fromMapKey('g'), + ]))).to.be.true; + expect(seq.startsWith(new KeySequence([ + Key.fromMapKey('g'), Key.fromMapKey(''), + ]))).to.be.true; + expect(seq.startsWith(new KeySequence([ + Key.fromMapKey('g'), Key.fromMapKey(''), Key.fromMapKey('x'), + ]))).to.be.false; + expect(seq.startsWith(new KeySequence([ + Key.fromMapKey('h'), + ]))).to.be.false; + }); + + it('returns true if the empty sequence starts with an empty sequence', () => { + let seq = new KeySequence([]); + + expect(seq.startsWith(new KeySequence([]))).to.be.true; + expect(seq.startsWith(new KeySequence([ + Key.fromMapKey('h'), + ]))).to.be.false; + }) + }); + + describe('#fromMapKeys', () => { + it('returns mapped keys for Shift+Esc', () => { + let keys = KeySequence.fromMapKeys('').keys; + expect(keys).to.have.lengthOf(1); + expect(keys[0].key).to.equal('Esc'); + expect(keys[0].shift).to.be.true; + }); + + it('returns mapped keys for ad', () => { + let keys = KeySequence.fromMapKeys('ad').keys; + expect(keys).to.have.lengthOf(5); + expect(keys[0].key).to.equal('a'); + expect(keys[1].ctrl).to.be.true; + expect(keys[1].key).to.equal('b'); + expect(keys[2].alt).to.be.true; + expect(keys[2].key).to.equal('c'); + expect(keys[3].key).to.equal('d'); + expect(keys[4].meta).to.be.true; + expect(keys[4].key).to.equal('e'); + }); + }) +}); -- cgit v1.2.3