import { injectable, inject } from "tsyringe"; import KeymapRepository from "../repositories/KeymapRepository"; import SettingRepository from "../repositories/SettingRepository"; import AddonEnabledRepository from "../repositories/AddonEnabledRepository"; import * as operations from "../../shared/operations"; import Keymaps from "../../shared/settings/Keymaps"; import Key from "../../shared/settings/Key"; import KeySequence from "../domains/KeySequence"; import AddressRepository from "../repositories/AddressRepository"; const reservedKeymaps = Keymaps.fromJSON({ "": { type: operations.CANCEL }, "": { type: operations.CANCEL }, }); const enableAddonOps = [ operations.ADDON_ENABLE, operations.ADDON_TOGGLE_ENABLED, ]; @injectable() export default class KeymapUseCase { constructor( @inject("KeymapRepository") private repository: KeymapRepository, @inject("SettingRepository") private settingRepository: SettingRepository, @inject("AddonEnabledRepository") private addonEnabledRepository: AddonEnabledRepository, @inject("AddressRepository") private addressRepository: AddressRepository ) {} // eslint-disable-next-line max-statements nextOps(key: Key): { repeat: number; op: operations.Operation } | null { const sequence = this.repository.enqueueKey(key); const baseSequence = sequence.trimNumericPrefix(); const keymaps = this.keymapEntityMap(); const matched = keymaps.filter(([seq]) => seq.startsWith(sequence)); const baseMatched = keymaps.filter(([seq]) => seq.startsWith(baseSequence)); if (baseSequence.length() === 1 && this.blacklistKey(key)) { // ignore if the input starts with black list keys this.repository.clear(); return null; } if (matched.length === 1 && sequence.length() === matched[0][0].length()) { // keys are matched with an operation this.repository.clear(); return { repeat: 1, op: matched[0][1] }; } else if ( baseMatched.length === 1 && baseSequence.length() === baseMatched[0][0].length() ) { // keys are matched with an operation with a numeric prefix this.repository.clear(); return { repeat: sequence.repeatCount(), op: baseMatched[0][1] }; } else if (matched.length >= 1 || baseMatched.length >= 1) { // keys are matched with an operation's prefix return null; } // matched with no operations this.repository.clear(); return null; } cancel() { this.repository.clear(); } private keymapEntityMap(): [KeySequence, operations.Operation][] { const keymaps = this.settingRepository .get() .keymaps.combine(reservedKeymaps); let entries = keymaps .entries() .map(([keys, op]) => [KeySequence.fromMapKeys(keys), op]) as [ KeySequence, operations.Operation ][]; if (!this.addonEnabledRepository.get()) { // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if // the addon disabled entries = entries.filter(([_seq, { type }]) => enableAddonOps.includes(type) ); } return entries; } private blacklistKey(key: Key): boolean { const url = this.addressRepository.getCurrentURL(); const blacklist = this.settingRepository.get().blacklist; return blacklist.includeKey(url, key); } }