Merge pull request #654 from ueokande/settings-as-a-class
Refactor settings on shared logicsjh-changes
commit
8eddcc1785
67 changed files with 1315 additions and 1288 deletions
@ -1,72 +0,0 @@ |
|||||||
export default interface Key { |
|
||||||
key: string; |
|
||||||
shiftKey?: boolean; |
|
||||||
ctrlKey?: boolean; |
|
||||||
altKey?: boolean; |
|
||||||
metaKey?: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
const modifiedKeyName = (name: string): string => { |
|
||||||
if (name === ' ') { |
|
||||||
return 'Space'; |
|
||||||
} |
|
||||||
if (name.length === 1) { |
|
||||||
return name; |
|
||||||
} else if (name === 'Escape') { |
|
||||||
return 'Esc'; |
|
||||||
} |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
key: modifiedKeyName(e.key), |
|
||||||
shiftKey: shift, |
|
||||||
ctrlKey: e.ctrlKey, |
|
||||||
altKey: e.altKey, |
|
||||||
metaKey: e.metaKey, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
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 { |
|
||||||
key: base, |
|
||||||
shiftKey: inner.includes('S-'), |
|
||||||
ctrlKey: inner.includes('C-'), |
|
||||||
altKey: inner.includes('A-'), |
|
||||||
metaKey: inner.includes('M-'), |
|
||||||
}; |
|
||||||
} |
|
||||||
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; |
|
||||||
}; |
|
@ -1,64 +0,0 @@ |
|||||||
import Key, * as keyUtils 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); |
|
||||||
} |
|
||||||
|
|
||||||
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 (!keyUtils.equals(this.keys[i], o.keys[i])) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
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; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return fromMapKeysRecursive( |
|
||||||
remainings.slice(nextPos), |
|
||||||
mappedKeys.concat([keyUtils.fromMapKey(remainings.slice(0, nextPos))]) |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
let data = fromMapKeysRecursive(keys, []); |
|
||||||
return KeySequence.from(data); |
|
||||||
}; |
|
||||||
|
|
@ -1,200 +0,0 @@ |
|||||||
import * as operations from './operations'; |
|
||||||
import * as PropertyDefs from './property-defs'; |
|
||||||
|
|
||||||
export type Keymaps = {[key: string]: operations.Operation}; |
|
||||||
|
|
||||||
export interface Search { |
|
||||||
default: string; |
|
||||||
engines: { [key: string]: string }; |
|
||||||
} |
|
||||||
|
|
||||||
export interface Properties { |
|
||||||
hintchars: string; |
|
||||||
smoothscroll: boolean; |
|
||||||
complete: string; |
|
||||||
} |
|
||||||
|
|
||||||
export default interface Settings { |
|
||||||
keymaps: Keymaps; |
|
||||||
search: Search; |
|
||||||
properties: Properties; |
|
||||||
blacklist: string[]; |
|
||||||
} |
|
||||||
|
|
||||||
export const keymapsValueOf = (o: any): Keymaps => { |
|
||||||
return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => { |
|
||||||
let op = operations.valueOf(o[key]); |
|
||||||
keymaps[key] = op; |
|
||||||
return keymaps; |
|
||||||
}, {}); |
|
||||||
}; |
|
||||||
|
|
||||||
export const searchValueOf = (o: any): Search => { |
|
||||||
if (typeof o.default !== 'string') { |
|
||||||
throw new TypeError('string field "default" not set"'); |
|
||||||
} |
|
||||||
for (let name of Object.keys(o.engines)) { |
|
||||||
if ((/\s/).test(name)) { |
|
||||||
throw new TypeError( |
|
||||||
`While space in the search engine not allowed: "${name}"`); |
|
||||||
} |
|
||||||
let url = o.engines[name]; |
|
||||||
if (typeof url !== 'string') { |
|
||||||
throw new TypeError('"engines" not an object of string'); |
|
||||||
} |
|
||||||
let matches = url.match(/{}/g); |
|
||||||
if (matches === null) { |
|
||||||
throw new TypeError(`No {}-placeholders in URL of "${name}"`); |
|
||||||
} else if (matches.length > 1) { |
|
||||||
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) { |
|
||||||
throw new TypeError(`Default engine "${o.default}" not found`); |
|
||||||
} |
|
||||||
return { |
|
||||||
default: o.default as string, |
|
||||||
engines: { ...o.engines }, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
export const propertiesValueOf = (o: any): Properties => { |
|
||||||
let defNames = new Set(PropertyDefs.defs.map(def => def.name)); |
|
||||||
let unknownName = Object.keys(o).find(name => !defNames.has(name)); |
|
||||||
if (unknownName) { |
|
||||||
throw new TypeError(`Unknown property name: "${unknownName}"`); |
|
||||||
} |
|
||||||
|
|
||||||
for (let def of PropertyDefs.defs) { |
|
||||||
if (!Object.prototype.hasOwnProperty.call(o, def.name)) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (typeof o[def.name] !== def.type) { |
|
||||||
throw new TypeError(`property "${def.name}" is not ${def.type}`); |
|
||||||
} |
|
||||||
} |
|
||||||
return { |
|
||||||
...PropertyDefs.defaultValues, |
|
||||||
...o, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
export const blacklistValueOf = (o: any): string[] => { |
|
||||||
if (!Array.isArray(o)) { |
|
||||||
throw new TypeError(`"blacklist" is not an array of string`); |
|
||||||
} |
|
||||||
for (let x of o) { |
|
||||||
if (typeof x !== 'string') { |
|
||||||
throw new TypeError(`"blacklist" is not an array of string`); |
|
||||||
} |
|
||||||
} |
|
||||||
return o as string[]; |
|
||||||
}; |
|
||||||
|
|
||||||
export const valueOf = (o: any): Settings => { |
|
||||||
let settings = { ...DefaultSetting }; |
|
||||||
for (let key of Object.keys(o)) { |
|
||||||
switch (key) { |
|
||||||
case 'keymaps': |
|
||||||
settings.keymaps = keymapsValueOf(o.keymaps); |
|
||||||
break; |
|
||||||
case 'search': |
|
||||||
settings.search = searchValueOf(o.search); |
|
||||||
break; |
|
||||||
case 'properties': |
|
||||||
settings.properties = propertiesValueOf(o.properties); |
|
||||||
break; |
|
||||||
case 'blacklist': |
|
||||||
settings.blacklist = blacklistValueOf(o.blacklist); |
|
||||||
break; |
|
||||||
default: |
|
||||||
throw new TypeError('unknown setting: ' + key); |
|
||||||
} |
|
||||||
} |
|
||||||
return settings; |
|
||||||
}; |
|
||||||
|
|
||||||
export const DefaultSetting: Settings = { |
|
||||||
keymaps: { |
|
||||||
'0': { 'type': 'scroll.home' }, |
|
||||||
':': { 'type': 'command.show' }, |
|
||||||
'o': { 'type': 'command.show.open', 'alter': false }, |
|
||||||
'O': { 'type': 'command.show.open', 'alter': true }, |
|
||||||
't': { 'type': 'command.show.tabopen', 'alter': false }, |
|
||||||
'T': { 'type': 'command.show.tabopen', 'alter': true }, |
|
||||||
'w': { 'type': 'command.show.winopen', 'alter': false }, |
|
||||||
'W': { 'type': 'command.show.winopen', 'alter': true }, |
|
||||||
'b': { 'type': 'command.show.buffer' }, |
|
||||||
'a': { 'type': 'command.show.addbookmark', 'alter': true }, |
|
||||||
'k': { 'type': 'scroll.vertically', 'count': -1 }, |
|
||||||
'j': { 'type': 'scroll.vertically', 'count': 1 }, |
|
||||||
'h': { 'type': 'scroll.horizonally', 'count': -1 }, |
|
||||||
'l': { 'type': 'scroll.horizonally', 'count': 1 }, |
|
||||||
'<C-U>': { 'type': 'scroll.pages', 'count': -0.5 }, |
|
||||||
'<C-D>': { 'type': 'scroll.pages', 'count': 0.5 }, |
|
||||||
'<C-B>': { 'type': 'scroll.pages', 'count': -1 }, |
|
||||||
'<C-F>': { 'type': 'scroll.pages', 'count': 1 }, |
|
||||||
'gg': { 'type': 'scroll.top' }, |
|
||||||
'G': { 'type': 'scroll.bottom' }, |
|
||||||
'$': { 'type': 'scroll.end' }, |
|
||||||
'd': { 'type': 'tabs.close' }, |
|
||||||
'D': { 'type': 'tabs.close', 'select': 'left' }, |
|
||||||
'x$': { 'type': 'tabs.close.right' }, |
|
||||||
'!d': { 'type': 'tabs.close.force' }, |
|
||||||
'u': { 'type': 'tabs.reopen' }, |
|
||||||
'K': { 'type': 'tabs.prev' }, |
|
||||||
'J': { 'type': 'tabs.next' }, |
|
||||||
'gT': { 'type': 'tabs.prev' }, |
|
||||||
'gt': { 'type': 'tabs.next' }, |
|
||||||
'g0': { 'type': 'tabs.first' }, |
|
||||||
'g$': { 'type': 'tabs.last' }, |
|
||||||
'<C-6>': { 'type': 'tabs.prevsel' }, |
|
||||||
'r': { 'type': 'tabs.reload', 'cache': false }, |
|
||||||
'R': { 'type': 'tabs.reload', 'cache': true }, |
|
||||||
'zp': { 'type': 'tabs.pin.toggle' }, |
|
||||||
'zd': { 'type': 'tabs.duplicate' }, |
|
||||||
'zi': { 'type': 'zoom.in' }, |
|
||||||
'zo': { 'type': 'zoom.out' }, |
|
||||||
'zz': { 'type': 'zoom.neutral' }, |
|
||||||
'f': { 'type': 'follow.start', 'newTab': false, 'background': false }, |
|
||||||
'F': { 'type': 'follow.start', 'newTab': true, 'background': false }, |
|
||||||
'm': { 'type': 'mark.set.prefix' }, |
|
||||||
'\'': { 'type': 'mark.jump.prefix' }, |
|
||||||
'H': { 'type': 'navigate.history.prev' }, |
|
||||||
'L': { 'type': 'navigate.history.next' }, |
|
||||||
'[[': { 'type': 'navigate.link.prev' }, |
|
||||||
']]': { 'type': 'navigate.link.next' }, |
|
||||||
'gu': { 'type': 'navigate.parent' }, |
|
||||||
'gU': { 'type': 'navigate.root' }, |
|
||||||
'gi': { 'type': 'focus.input' }, |
|
||||||
'gf': { 'type': 'page.source' }, |
|
||||||
'gh': { 'type': 'page.home', 'newTab': false }, |
|
||||||
'gH': { 'type': 'page.home', 'newTab': true }, |
|
||||||
'y': { 'type': 'urls.yank' }, |
|
||||||
'p': { 'type': 'urls.paste', 'newTab': false }, |
|
||||||
'P': { 'type': 'urls.paste', 'newTab': true }, |
|
||||||
'/': { 'type': 'find.start' }, |
|
||||||
'n': { 'type': 'find.next' }, |
|
||||||
'N': { 'type': 'find.prev' }, |
|
||||||
'.': { 'type': 'repeat.last' }, |
|
||||||
'<S-Esc>': { 'type': 'addon.toggle.enabled' } |
|
||||||
}, |
|
||||||
search: { |
|
||||||
default: 'google', |
|
||||||
engines: { |
|
||||||
'google': 'https://google.com/search?q={}', |
|
||||||
'yahoo': 'https://search.yahoo.com/search?p={}', |
|
||||||
'bing': 'https://www.bing.com/search?q={}', |
|
||||||
'duckduckgo': 'https://duckduckgo.com/?q={}', |
|
||||||
'twitter': 'https://twitter.com/search?q={}', |
|
||||||
'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}' |
|
||||||
} |
|
||||||
}, |
|
||||||
properties: { |
|
||||||
hintchars: 'abcdefghijklmnopqrstuvwxyz', |
|
||||||
smoothscroll: false, |
|
||||||
complete: 'sbh' |
|
||||||
}, |
|
||||||
blacklist: [] |
|
||||||
}; |
|
@ -1,13 +0,0 @@ |
|||||||
import * as re from './utils/re'; |
|
||||||
|
|
||||||
const includes = (blacklist: string[], url: string): boolean => { |
|
||||||
let u = new URL(url); |
|
||||||
return blacklist.some((item) => { |
|
||||||
if (!item.includes('/')) { |
|
||||||
return re.fromWildcard(item).test(u.host); |
|
||||||
} |
|
||||||
return re.fromWildcard(item).test(u.host + u.pathname); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
export { includes }; |
|
@ -1,50 +0,0 @@ |
|||||||
export type Type = string | number | boolean; |
|
||||||
|
|
||||||
export class Def { |
|
||||||
private name0: string; |
|
||||||
|
|
||||||
private description0: string; |
|
||||||
|
|
||||||
private defaultValue0: Type; |
|
||||||
|
|
||||||
constructor( |
|
||||||
name: string, |
|
||||||
description: string, |
|
||||||
defaultValue: Type, |
|
||||||
) { |
|
||||||
this.name0 = name; |
|
||||||
this.description0 = description; |
|
||||||
this.defaultValue0 = defaultValue; |
|
||||||
} |
|
||||||
|
|
||||||
public get name(): string { |
|
||||||
return this.name0; |
|
||||||
} |
|
||||||
|
|
||||||
public get defaultValue(): Type { |
|
||||||
return this.defaultValue0; |
|
||||||
} |
|
||||||
|
|
||||||
public get description(): Type { |
|
||||||
return this.description0; |
|
||||||
} |
|
||||||
|
|
||||||
public get type(): string { |
|
||||||
return typeof this.defaultValue; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export const defs: Def[] = [ |
|
||||||
new Def( |
|
||||||
'hintchars', |
|
||||||
'hint characters on follow mode', |
|
||||||
'abcdefghijklmnopqrstuvwxyz'), |
|
||||||
new Def( |
|
||||||
'smoothscroll', |
|
||||||
'smooth scroll', |
|
||||||
false), |
|
||||||
new Def( |
|
||||||
'complete', |
|
||||||
'which are completed at the open page', |
|
||||||
'sbh'), |
|
||||||
]; |
|
@ -1,56 +0,0 @@ |
|||||||
export type Type = string | number | boolean; |
|
||||||
|
|
||||||
export class Def { |
|
||||||
private name0: string; |
|
||||||
|
|
||||||
private description0: string; |
|
||||||
|
|
||||||
private defaultValue0: Type; |
|
||||||
|
|
||||||
constructor( |
|
||||||
name: string, |
|
||||||
description: string, |
|
||||||
defaultValue: Type, |
|
||||||
) { |
|
||||||
this.name0 = name; |
|
||||||
this.description0 = description; |
|
||||||
this.defaultValue0 = defaultValue; |
|
||||||
} |
|
||||||
|
|
||||||
public get name(): string { |
|
||||||
return this.name0; |
|
||||||
} |
|
||||||
|
|
||||||
public get defaultValue(): Type { |
|
||||||
return this.defaultValue0; |
|
||||||
} |
|
||||||
|
|
||||||
public get description(): Type { |
|
||||||
return this.description0; |
|
||||||
} |
|
||||||
|
|
||||||
public get type(): string { |
|
||||||
return typeof this.defaultValue; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export const defs: Def[] = [ |
|
||||||
new Def( |
|
||||||
'hintchars', |
|
||||||
'hint characters on follow mode', |
|
||||||
'abcdefghijklmnopqrstuvwxyz'), |
|
||||||
new Def( |
|
||||||
'smoothscroll', |
|
||||||
'smooth scroll', |
|
||||||
false), |
|
||||||
new Def( |
|
||||||
'complete', |
|
||||||
'which are completed at the open page', |
|
||||||
'sbh'), |
|
||||||
]; |
|
||||||
|
|
||||||
export const defaultValues = { |
|
||||||
hintchars: 'abcdefghijklmnopqrstuvwxyz', |
|
||||||
smoothscroll: false, |
|
||||||
complete: 'sbh', |
|
||||||
}; |
|
@ -0,0 +1,39 @@ |
|||||||
|
export type BlacklistJSON = string[]; |
||||||
|
|
||||||
|
const fromWildcard = (pattern: string): RegExp => { |
||||||
|
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; |
||||||
|
return new RegExp(regexStr); |
||||||
|
}; |
||||||
|
|
||||||
|
export default class Blacklist { |
||||||
|
constructor( |
||||||
|
private blacklist: string[], |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
static fromJSON(json: any): Blacklist { |
||||||
|
if (!Array.isArray(json)) { |
||||||
|
throw new TypeError(`"blacklist" is not an array of string`); |
||||||
|
} |
||||||
|
for (let x of json) { |
||||||
|
if (typeof x !== 'string') { |
||||||
|
throw new TypeError(`"blacklist" is not an array of string`); |
||||||
|
} |
||||||
|
} |
||||||
|
return new Blacklist(json); |
||||||
|
} |
||||||
|
|
||||||
|
toJSON(): BlacklistJSON { |
||||||
|
return this.blacklist; |
||||||
|
} |
||||||
|
|
||||||
|
includes(url: string): boolean { |
||||||
|
let u = new URL(url); |
||||||
|
return this.blacklist.some((item) => { |
||||||
|
if (!item.includes('/')) { |
||||||
|
return fromWildcard(item).test(u.host); |
||||||
|
} |
||||||
|
return fromWildcard(item).test(u.host + u.pathname); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
import * as operations from '../operations'; |
||||||
|
|
||||||
|
export type KeymapsJSON = { [key: string]: operations.Operation }; |
||||||
|
|
||||||
|
export default class Keymaps { |
||||||
|
constructor( |
||||||
|
private readonly data: KeymapsJSON, |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
static fromJSON(json: any): Keymaps { |
||||||
|
if (typeof json !== 'object' || json === null) { |
||||||
|
throw new TypeError('invalid keymaps type: ' + JSON.stringify(json)); |
||||||
|
} |
||||||
|
|
||||||
|
let data: KeymapsJSON = {}; |
||||||
|
for (let key of Object.keys(json)) { |
||||||
|
data[key] = operations.valueOf(json[key]); |
||||||
|
} |
||||||
|
return new Keymaps(data); |
||||||
|
} |
||||||
|
|
||||||
|
combine(other: Keymaps): Keymaps { |
||||||
|
return new Keymaps({ |
||||||
|
...this.data, |
||||||
|
...other.data, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
toJSON(): KeymapsJSON { |
||||||
|
return this.data; |
||||||
|
} |
||||||
|
|
||||||
|
entries(): [string, operations.Operation][] { |
||||||
|
return Object.entries(this.data); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
export type PropertiesJSON = { |
||||||
|
hintchars?: string; |
||||||
|
smoothscroll?: boolean; |
||||||
|
complete?: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export type PropertyTypes = { |
||||||
|
hintchars: string; |
||||||
|
smoothscroll: string; |
||||||
|
complete: string; |
||||||
|
}; |
||||||
|
|
||||||
|
type PropertyName = 'hintchars' | 'smoothscroll' | 'complete'; |
||||||
|
|
||||||
|
type PropertyDef = { |
||||||
|
name: PropertyName; |
||||||
|
description: string; |
||||||
|
defaultValue: string | number | boolean; |
||||||
|
type: 'string' | 'number' | 'boolean'; |
||||||
|
}; |
||||||
|
|
||||||
|
const defs: PropertyDef[] = [ |
||||||
|
{ |
||||||
|
name: 'hintchars', |
||||||
|
description: 'hint characters on follow mode', |
||||||
|
defaultValue: 'abcdefghijklmnopqrstuvwxyz', |
||||||
|
type: 'string', |
||||||
|
}, { |
||||||
|
name: 'smoothscroll', |
||||||
|
description: 'smooth scroll', |
||||||
|
defaultValue: false, |
||||||
|
type: 'boolean', |
||||||
|
}, { |
||||||
|
name: 'complete', |
||||||
|
description: 'which are completed at the open page', |
||||||
|
defaultValue: 'sbh', |
||||||
|
type: 'string', |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
const defaultValues = { |
||||||
|
hintchars: 'abcdefghijklmnopqrstuvwxyz', |
||||||
|
smoothscroll: false, |
||||||
|
complete: 'sbh', |
||||||
|
}; |
||||||
|
|
||||||
|
export default class Properties { |
||||||
|
public hintchars: string; |
||||||
|
|
||||||
|
public smoothscroll: boolean; |
||||||
|
|
||||||
|
public complete: string; |
||||||
|
|
||||||
|
constructor({ |
||||||
|
hintchars, |
||||||
|
smoothscroll, |
||||||
|
complete, |
||||||
|
}: { |
||||||
|
hintchars?: string; |
||||||
|
smoothscroll?: boolean; |
||||||
|
complete?: string; |
||||||
|
} = {}) { |
||||||
|
this.hintchars = hintchars || defaultValues.hintchars; |
||||||
|
this.smoothscroll = smoothscroll || defaultValues.smoothscroll; |
||||||
|
this.complete = complete || defaultValues.complete; |
||||||
|
} |
||||||
|
|
||||||
|
static fromJSON(json: any): Properties { |
||||||
|
let defNames: Set<string> = new Set(defs.map(def => def.name)); |
||||||
|
let unknownName = Object.keys(json).find(name => !defNames.has(name)); |
||||||
|
if (unknownName) { |
||||||
|
throw new TypeError(`Unknown property name: "${unknownName}"`); |
||||||
|
} |
||||||
|
|
||||||
|
for (let def of defs) { |
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, def.name)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (typeof json[def.name] !== def.type) { |
||||||
|
throw new TypeError( |
||||||
|
`property "${def.name}" is not ${def.type}`); |
||||||
|
} |
||||||
|
} |
||||||
|
return new Properties(json); |
||||||
|
} |
||||||
|
|
||||||
|
static types(): PropertyTypes { |
||||||
|
return { |
||||||
|
hintchars: 'string', |
||||||
|
smoothscroll: 'boolean', |
||||||
|
complete: 'string', |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
static def(name: string): PropertyDef | undefined { |
||||||
|
return defs.find(p => p.name === name); |
||||||
|
} |
||||||
|
|
||||||
|
static defs(): PropertyDef[] { |
||||||
|
return defs; |
||||||
|
} |
||||||
|
|
||||||
|
toJSON(): PropertiesJSON { |
||||||
|
return { |
||||||
|
hintchars: this.hintchars, |
||||||
|
smoothscroll: this.smoothscroll, |
||||||
|
complete: this.complete, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
type Entries = { [name: string]: string }; |
||||||
|
|
||||||
|
export type SearchJSON = { |
||||||
|
default: string; |
||||||
|
engines: { [key: string]: string }; |
||||||
|
}; |
||||||
|
|
||||||
|
export default class Search { |
||||||
|
constructor( |
||||||
|
public defaultEngine: string, |
||||||
|
public engines: Entries, |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
static fromJSON(json: any): Search { |
||||||
|
let defaultEngine = Search.getStringField(json, 'default'); |
||||||
|
let engines = Search.getObjectField(json, 'engines'); |
||||||
|
|
||||||
|
for (let [name, url] of Object.entries(engines)) { |
||||||
|
if ((/\s/).test(name)) { |
||||||
|
throw new TypeError( |
||||||
|
`While space in the search engine not allowed: "${name}"`); |
||||||
|
} |
||||||
|
if (typeof url !== 'string') { |
||||||
|
throw new TypeError( |
||||||
|
`Invalid type of value in filed "engines": ${JSON.stringify(json)}`); |
||||||
|
} |
||||||
|
let matches = url.match(/{}/g); |
||||||
|
if (matches === null) { |
||||||
|
throw new TypeError(`No {}-placeholders in URL of "${name}"`); |
||||||
|
} else if (matches.length > 1) { |
||||||
|
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!Object.keys(engines).includes(defaultEngine)) { |
||||||
|
throw new TypeError(`Default engine "${defaultEngine}" not found`); |
||||||
|
} |
||||||
|
|
||||||
|
return new Search( |
||||||
|
json.default as string, |
||||||
|
json.engines, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
toJSON(): SearchJSON { |
||||||
|
return { |
||||||
|
default: this.defaultEngine, |
||||||
|
engines: this.engines, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private static getStringField(json: any, name: string): string { |
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, name)) { |
||||||
|
throw new TypeError( |
||||||
|
`missing field "${name}" on search: ${JSON.stringify(json)}`); |
||||||
|
} |
||||||
|
if (typeof json[name] !== 'string') { |
||||||
|
throw new TypeError( |
||||||
|
`invalid type of filed "${name}" on search: ${JSON.stringify(json)}`); |
||||||
|
} |
||||||
|
return json[name]; |
||||||
|
} |
||||||
|
|
||||||
|
private static getObjectField(json: any, name: string): Object { |
||||||
|
if (!Object.prototype.hasOwnProperty.call(json, name)) { |
||||||
|
throw new TypeError( |
||||||
|
`missing field "${name}" on search: ${JSON.stringify(json)}`); |
||||||
|
} |
||||||
|
if (typeof json[name] !== 'object' || json[name] === null) { |
||||||
|
throw new TypeError( |
||||||
|
`invalid type of filed "${name}" on search: ${JSON.stringify(json)}`); |
||||||
|
} |
||||||
|
return json[name]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
import Keymaps, { KeymapsJSON } from './Keymaps'; |
||||||
|
import Search, { SearchJSON } from './Search'; |
||||||
|
import Properties, { PropertiesJSON } from './Properties'; |
||||||
|
import Blacklist, { BlacklistJSON } from './Blacklist'; |
||||||
|
|
||||||
|
export type SettingsJSON = { |
||||||
|
keymaps: KeymapsJSON, |
||||||
|
search: SearchJSON, |
||||||
|
properties: PropertiesJSON, |
||||||
|
blacklist: BlacklistJSON, |
||||||
|
}; |
||||||
|
|
||||||
|
export default class Settings { |
||||||
|
public keymaps: Keymaps; |
||||||
|
|
||||||
|
public search: Search; |
||||||
|
|
||||||
|
public properties: Properties; |
||||||
|
|
||||||
|
public blacklist: Blacklist; |
||||||
|
|
||||||
|
constructor({ |
||||||
|
keymaps, |
||||||
|
search, |
||||||
|
properties, |
||||||
|
blacklist, |
||||||
|
}: { |
||||||
|
keymaps: Keymaps; |
||||||
|
search: Search; |
||||||
|
properties: Properties; |
||||||
|
blacklist: Blacklist; |
||||||
|
}) { |
||||||
|
this.keymaps = keymaps; |
||||||
|
this.search = search; |
||||||
|
this.properties = properties; |
||||||
|
this.blacklist = blacklist; |
||||||
|
} |
||||||
|
|
||||||
|
static fromJSON(json: any): Settings { |
||||||
|
let settings = { ...DefaultSetting }; |
||||||
|
for (let key of Object.keys(json)) { |
||||||
|
switch (key) { |
||||||
|
case 'keymaps': |
||||||
|
settings.keymaps = Keymaps.fromJSON(json.keymaps); |
||||||
|
break; |
||||||
|
case 'search': |
||||||
|
settings.search = Search.fromJSON(json.search); |
||||||
|
break; |
||||||
|
case 'properties': |
||||||
|
settings.properties = Properties.fromJSON(json.properties); |
||||||
|
break; |
||||||
|
case 'blacklist': |
||||||
|
settings.blacklist = Blacklist.fromJSON(json.blacklist); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new TypeError('unknown setting: ' + key); |
||||||
|
} |
||||||
|
} |
||||||
|
return new Settings(settings); |
||||||
|
} |
||||||
|
|
||||||
|
toJSON(): SettingsJSON { |
||||||
|
return { |
||||||
|
keymaps: this.keymaps.toJSON(), |
||||||
|
search: this.search.toJSON(), |
||||||
|
properties: this.properties.toJSON(), |
||||||
|
blacklist: this.blacklist.toJSON(), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const DefaultSettingJSONText = `{
|
||||||
|
"keymaps": { |
||||||
|
"0": { "type": "scroll.home" }, |
||||||
|
":": { "type": "command.show" }, |
||||||
|
"o": { "type": "command.show.open", "alter": false }, |
||||||
|
"O": { "type": "command.show.open", "alter": true }, |
||||||
|
"t": { "type": "command.show.tabopen", "alter": false }, |
||||||
|
"T": { "type": "command.show.tabopen", "alter": true }, |
||||||
|
"w": { "type": "command.show.winopen", "alter": false }, |
||||||
|
"W": { "type": "command.show.winopen", "alter": true }, |
||||||
|
"b": { "type": "command.show.buffer" }, |
||||||
|
"a": { "type": "command.show.addbookmark", "alter": true }, |
||||||
|
"k": { "type": "scroll.vertically", "count": -1 }, |
||||||
|
"j": { "type": "scroll.vertically", "count": 1 }, |
||||||
|
"h": { "type": "scroll.horizonally", "count": -1 }, |
||||||
|
"l": { "type": "scroll.horizonally", "count": 1 }, |
||||||
|
"<C-U>": { "type": "scroll.pages", "count": -0.5 }, |
||||||
|
"<C-D>": { "type": "scroll.pages", "count": 0.5 }, |
||||||
|
"<C-B>": { "type": "scroll.pages", "count": -1 }, |
||||||
|
"<C-F>": { "type": "scroll.pages", "count": 1 }, |
||||||
|
"gg": { "type": "scroll.top" }, |
||||||
|
"G": { "type": "scroll.bottom" }, |
||||||
|
"$": { "type": "scroll.end" }, |
||||||
|
"d": { "type": "tabs.close" }, |
||||||
|
"D": { "type": "tabs.close", "select": "left" }, |
||||||
|
"x$": { "type": "tabs.close.right" }, |
||||||
|
"!d": { "type": "tabs.close.force" }, |
||||||
|
"u": { "type": "tabs.reopen" }, |
||||||
|
"K": { "type": "tabs.prev" }, |
||||||
|
"J": { "type": "tabs.next" }, |
||||||
|
"gT": { "type": "tabs.prev" }, |
||||||
|
"gt": { "type": "tabs.next" }, |
||||||
|
"g0": { "type": "tabs.first" }, |
||||||
|
"g$": { "type": "tabs.last" }, |
||||||
|
"<C-6>": { "type": "tabs.prevsel" }, |
||||||
|
"r": { "type": "tabs.reload", "cache": false }, |
||||||
|
"R": { "type": "tabs.reload", "cache": true }, |
||||||
|
"zp": { "type": "tabs.pin.toggle" }, |
||||||
|
"zd": { "type": "tabs.duplicate" }, |
||||||
|
"zi": { "type": "zoom.in" }, |
||||||
|
"zo": { "type": "zoom.out" }, |
||||||
|
"zz": { "type": "zoom.neutral" }, |
||||||
|
"f": { "type": "follow.start", "newTab": false }, |
||||||
|
"F": { "type": "follow.start", "newTab": true, "background": false }, |
||||||
|
"m": { "type": "mark.set.prefix" }, |
||||||
|
"'": { "type": "mark.jump.prefix" }, |
||||||
|
"H": { "type": "navigate.history.prev" }, |
||||||
|
"L": { "type": "navigate.history.next" }, |
||||||
|
"[[": { "type": "navigate.link.prev" }, |
||||||
|
"]]": { "type": "navigate.link.next" }, |
||||||
|
"gu": { "type": "navigate.parent" }, |
||||||
|
"gU": { "type": "navigate.root" }, |
||||||
|
"gi": { "type": "focus.input" }, |
||||||
|
"gf": { "type": "page.source" }, |
||||||
|
"gh": { "type": "page.home" }, |
||||||
|
"gH": { "type": "page.home", "newTab": true }, |
||||||
|
"y": { "type": "urls.yank" }, |
||||||
|
"p": { "type": "urls.paste", "newTab": false }, |
||||||
|
"P": { "type": "urls.paste", "newTab": true }, |
||||||
|
"/": { "type": "find.start" }, |
||||||
|
"n": { "type": "find.next" }, |
||||||
|
"N": { "type": "find.prev" }, |
||||||
|
".": { "type": "repeat.last" }, |
||||||
|
"<S-Esc>": { "type": "addon.toggle.enabled" } |
||||||
|
}, |
||||||
|
"search": { |
||||||
|
"default": "google", |
||||||
|
"engines": { |
||||||
|
"google": "https://google.com/search?q={}", |
||||||
|
"yahoo": "https://search.yahoo.com/search?p={}", |
||||||
|
"bing": "https://www.bing.com/search?q={}", |
||||||
|
"duckduckgo": "https://duckduckgo.com/?q={}", |
||||||
|
"twitter": "https://twitter.com/search?q={}", |
||||||
|
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}" |
||||||
|
} |
||||||
|
}, |
||||||
|
"properties": { |
||||||
|
"hintchars": "abcdefghijklmnopqrstuvwxyz", |
||||||
|
"smoothscroll": false, |
||||||
|
"complete": "sbh" |
||||||
|
}, |
||||||
|
"blacklist": [ |
||||||
|
] |
||||||
|
}`;
|
||||||
|
|
||||||
|
export const DefaultSetting: Settings = |
||||||
|
Settings.fromJSON(JSON.parse(DefaultSettingJSONText)); |
@ -1,6 +0,0 @@ |
|||||||
const fromWildcard = (pattern: string): RegExp => { |
|
||||||
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; |
|
||||||
return new RegExp(regexStr); |
|
||||||
}; |
|
||||||
|
|
||||||
export { fromWildcard }; |
|
@ -1,137 +0,0 @@ |
|||||||
import Key, * as keys from '../../../src/content/domains/Key'; |
|
||||||
import { expect } from 'chai' |
|
||||||
|
|
||||||
describe("Key", () => { |
|
||||||
describe('fromKeyboardEvent', () => { |
|
||||||
it('returns from keyboard input Ctrl+X', () => { |
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { |
|
||||||
key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true, |
|
||||||
})); |
|
||||||
expect(k.key).to.equal('x'); |
|
||||||
expect(k.shiftKey).to.be.false; |
|
||||||
expect(k.ctrlKey).to.be.true; |
|
||||||
expect(k.altKey).to.be.false; |
|
||||||
expect(k.metaKey).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns from keyboard input Shift+Esc', () => { |
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { |
|
||||||
key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true |
|
||||||
})); |
|
||||||
expect(k.key).to.equal('Esc'); |
|
||||||
expect(k.shiftKey).to.be.true; |
|
||||||
expect(k.ctrlKey).to.be.false; |
|
||||||
expect(k.altKey).to.be.false; |
|
||||||
expect(k.metaKey).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns from keyboard input Ctrl+$', () => { |
|
||||||
// $ required shift pressing on most keyboards
|
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { |
|
||||||
key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false |
|
||||||
})); |
|
||||||
expect(k.key).to.equal('$'); |
|
||||||
expect(k.shiftKey).to.be.false; |
|
||||||
expect(k.ctrlKey).to.be.true; |
|
||||||
expect(k.altKey).to.be.false; |
|
||||||
expect(k.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns from keyboard input Crtl+Space', () => { |
|
||||||
let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', { |
|
||||||
key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false |
|
||||||
})); |
|
||||||
expect(k.key).to.equal('Space'); |
|
||||||
expect(k.shiftKey).to.be.false; |
|
||||||
expect(k.ctrlKey).to.be.true; |
|
||||||
expect(k.altKey).to.be.false; |
|
||||||
expect(k.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromMapKey', () => { |
|
||||||
it('return for X', () => { |
|
||||||
let key = keys.fromMapKey('x'); |
|
||||||
expect(key.key).to.equal('x'); |
|
||||||
expect(key.shiftKey).to.be.false; |
|
||||||
expect(key.ctrlKey).to.be.false; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('return for Shift+X', () => { |
|
||||||
let key = keys.fromMapKey('X'); |
|
||||||
expect(key.key).to.equal('X'); |
|
||||||
expect(key.shiftKey).to.be.true; |
|
||||||
expect(key.ctrlKey).to.be.false; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('return for Ctrl+X', () => { |
|
||||||
let key = keys.fromMapKey('<C-X>'); |
|
||||||
expect(key.key).to.equal('x'); |
|
||||||
expect(key.shiftKey).to.be.false; |
|
||||||
expect(key.ctrlKey).to.be.true; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns for Ctrl+Meta+X', () => { |
|
||||||
let key = keys.fromMapKey('<C-M-X>'); |
|
||||||
expect(key.key).to.equal('x'); |
|
||||||
expect(key.shiftKey).to.be.false; |
|
||||||
expect(key.ctrlKey).to.be.true; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns for Ctrl+Shift+x', () => { |
|
||||||
let key = keys.fromMapKey('<C-S-x>'); |
|
||||||
expect(key.key).to.equal('X'); |
|
||||||
expect(key.shiftKey).to.be.true; |
|
||||||
expect(key.ctrlKey).to.be.true; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns for Shift+Esc', () => { |
|
||||||
let key = keys.fromMapKey('<S-Esc>'); |
|
||||||
expect(key.key).to.equal('Esc'); |
|
||||||
expect(key.shiftKey).to.be.true; |
|
||||||
expect(key.ctrlKey).to.be.false; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns for Ctrl+Esc', () => { |
|
||||||
let key = keys.fromMapKey('<C-Esc>'); |
|
||||||
expect(key.key).to.equal('Esc'); |
|
||||||
expect(key.shiftKey).to.be.false; |
|
||||||
expect(key.ctrlKey).to.be.true; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns for Ctrl+Esc', () => { |
|
||||||
let key = keys.fromMapKey('<C-Space>'); |
|
||||||
expect(key.key).to.equal('Space'); |
|
||||||
expect(key.shiftKey).to.be.false; |
|
||||||
expect(key.ctrlKey).to.be.true; |
|
||||||
expect(key.altKey).to.be.false; |
|
||||||
expect(key.metaKey).to.be.false; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('equals', () => { |
|
||||||
expect(keys.equals( |
|
||||||
{ key: 'x', ctrlKey: true, }, |
|
||||||
{ key: 'x', ctrlKey: true, }, |
|
||||||
)).to.be.true; |
|
||||||
|
|
||||||
expect(keys.equals( |
|
||||||
{ key: 'X', shiftKey: true, }, |
|
||||||
{ key: 'x', ctrlKey: true, }, |
|
||||||
)).to.be.false; |
|
||||||
}); |
|
||||||
}); |
|
@ -1,72 +0,0 @@ |
|||||||
import KeySequence, * as utils from '../../../src/content/domains/KeySequence'; |
|
||||||
import { expect } from 'chai' |
|
||||||
|
|
||||||
describe("KeySequence", () => { |
|
||||||
describe('#push', () => { |
|
||||||
it('append a key to the sequence', () => { |
|
||||||
let seq = KeySequence.from([]); |
|
||||||
seq.push({ key: 'g' }); |
|
||||||
seq.push({ key: 'u', shiftKey: true }); |
|
||||||
|
|
||||||
let array = seq.getKeyArray(); |
|
||||||
expect(array[0]).to.deep.equal({ key: 'g' }); |
|
||||||
expect(array[1]).to.deep.equal({ key: 'u', shiftKey: true }); |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#startsWith', () => { |
|
||||||
it('returns true if the key sequence starts with param', () => { |
|
||||||
let seq = KeySequence.from([ |
|
||||||
{ key: 'g' }, |
|
||||||
{ key: 'u', shiftKey: true }, |
|
||||||
]); |
|
||||||
|
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
]))).to.be.true; |
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
{ key: 'g' }, |
|
||||||
]))).to.be.true; |
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
{ key: 'g' }, { key: 'u', shiftKey: true }, |
|
||||||
]))).to.be.true; |
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
{ key: 'g' }, { key: 'u', shiftKey: true }, { key: 'x' }, |
|
||||||
]))).to.be.false; |
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
{ key: 'h' }, |
|
||||||
]))).to.be.false; |
|
||||||
}) |
|
||||||
|
|
||||||
it('returns true if the empty sequence starts with an empty sequence', () => { |
|
||||||
let seq = KeySequence.from([]); |
|
||||||
|
|
||||||
expect(seq.startsWith(KeySequence.from([]))).to.be.true; |
|
||||||
expect(seq.startsWith(KeySequence.from([ |
|
||||||
{ key: 'h' }, |
|
||||||
]))).to.be.false; |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#fromMapKeys', () => { |
|
||||||
it('returns mapped keys for Shift+Esc', () => { |
|
||||||
let keyArray = utils.fromMapKeys('<S-Esc>').getKeyArray(); |
|
||||||
expect(keyArray).to.have.lengthOf(1); |
|
||||||
expect(keyArray[0].key).to.equal('Esc'); |
|
||||||
expect(keyArray[0].shiftKey).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns mapped keys for a<C-B><A-C>d<M-e>', () => { |
|
||||||
let keyArray = utils.fromMapKeys('a<C-B><A-C>d<M-e>').getKeyArray(); |
|
||||||
expect(keyArray).to.have.lengthOf(5); |
|
||||||
expect(keyArray[0].key).to.equal('a'); |
|
||||||
expect(keyArray[1].ctrlKey).to.be.true; |
|
||||||
expect(keyArray[1].key).to.equal('b'); |
|
||||||
expect(keyArray[2].altKey).to.be.true; |
|
||||||
expect(keyArray[2].key).to.equal('c'); |
|
||||||
expect(keyArray[3].key).to.equal('d'); |
|
||||||
expect(keyArray[4].metaKey).to.be.true; |
|
||||||
expect(keyArray[4].key).to.equal('e'); |
|
||||||
}); |
|
||||||
}) |
|
||||||
|
|
||||||
}); |
|
@ -1,194 +0,0 @@ |
|||||||
import * as settings from '../../src/shared/Settings'; |
|
||||||
import { expect } from 'chai'; |
|
||||||
|
|
||||||
describe('Settings', () => { |
|
||||||
describe('#keymapsValueOf', () => { |
|
||||||
it('returns empty object by empty settings', () => { |
|
||||||
let keymaps = settings.keymapsValueOf({}); |
|
||||||
expect(keymaps).to.be.empty; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns keymaps by valid settings', () => { |
|
||||||
let keymaps = settings.keymapsValueOf({ |
|
||||||
k: { type: "scroll.vertically", count: -1 }, |
|
||||||
j: { type: "scroll.vertically", count: 1 }, |
|
||||||
}); |
|
||||||
|
|
||||||
expect(keymaps['k']).to.deep.equal({ type: "scroll.vertically", count: -1 }); |
|
||||||
expect(keymaps['j']).to.deep.equal({ type: "scroll.vertically", count: 1 }); |
|
||||||
}); |
|
||||||
|
|
||||||
it('throws a TypeError by invalid settings', () => { |
|
||||||
expect(() => settings.keymapsValueOf(null)).to.throw(TypeError); |
|
||||||
expect(() => settings.keymapsValueOf({ |
|
||||||
k: { type: "invalid.operation" }, |
|
||||||
})).to.throw(TypeError); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#searchValueOf', () => { |
|
||||||
it('returns search settings by valid settings', () => { |
|
||||||
let search = settings.searchValueOf({ |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
"google": "https://google.com/search?q={}", |
|
||||||
"yahoo": "https://search.yahoo.com/search?p={}", |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
expect(search).to.deep.equal({ |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
"google": "https://google.com/search?q={}", |
|
||||||
"yahoo": "https://search.yahoo.com/search?p={}", |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('throws a TypeError by invalid settings', () => { |
|
||||||
expect(() => settings.searchValueOf(null)).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf([])).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: 123, |
|
||||||
engines: {} |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
"google": 123456, |
|
||||||
} |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: "wikipedia", |
|
||||||
engines: { |
|
||||||
"google": "https://google.com/search?q={}", |
|
||||||
"yahoo": "https://search.yahoo.com/search?p={}", |
|
||||||
} |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: "g o o g l e", |
|
||||||
engines: { |
|
||||||
"g o o g l e": "https://google.com/search?q={}", |
|
||||||
} |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
"google": "https://google.com/search", |
|
||||||
} |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.searchValueOf({ |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
"google": "https://google.com/search?q={}&r={}", |
|
||||||
} |
|
||||||
})).to.throw(TypeError); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#propertiesValueOf', () => { |
|
||||||
it('returns with default properties by empty settings', () => { |
|
||||||
let props = settings.propertiesValueOf({}); |
|
||||||
expect(props).to.deep.equal({ |
|
||||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
|
||||||
smoothscroll: false, |
|
||||||
complete: "sbh" |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns properties by valid settings', () => { |
|
||||||
let props = settings.propertiesValueOf({ |
|
||||||
hintchars: "abcdefgh", |
|
||||||
smoothscroll: false, |
|
||||||
complete: "sbh" |
|
||||||
}); |
|
||||||
|
|
||||||
expect(props).to.deep.equal({ |
|
||||||
hintchars: "abcdefgh", |
|
||||||
smoothscroll: false, |
|
||||||
complete: "sbh" |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('throws a TypeError by invalid settings', () => { |
|
||||||
expect(() => settings.keymapsValueOf(null)).to.throw(TypeError); |
|
||||||
expect(() => settings.keymapsValueOf({ |
|
||||||
smoothscroll: 'false', |
|
||||||
})).to.throw(TypeError); |
|
||||||
expect(() => settings.keymapsValueOf({ |
|
||||||
unknown: 'xyz' |
|
||||||
})).to.throw(TypeError); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#blacklistValueOf', () => { |
|
||||||
it('returns empty array by empty settings', () => { |
|
||||||
let blacklist = settings.blacklistValueOf([]); |
|
||||||
expect(blacklist).to.be.empty; |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns blacklist by valid settings', () => { |
|
||||||
let blacklist = settings.blacklistValueOf([ |
|
||||||
"github.com", |
|
||||||
"circleci.com", |
|
||||||
]); |
|
||||||
|
|
||||||
expect(blacklist).to.deep.equal([ |
|
||||||
"github.com", |
|
||||||
"circleci.com", |
|
||||||
]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('throws a TypeError by invalid settings', () => { |
|
||||||
expect(() => settings.blacklistValueOf(null)).to.throw(TypeError); |
|
||||||
expect(() => settings.blacklistValueOf({})).to.throw(TypeError); |
|
||||||
expect(() => settings.blacklistValueOf([1,2,3])).to.throw(TypeError); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#valueOf', () => { |
|
||||||
it('returns settings by valid settings', () => { |
|
||||||
let x = settings.valueOf({ |
|
||||||
keymaps: {}, |
|
||||||
"search": { |
|
||||||
"default": "google", |
|
||||||
"engines": { |
|
||||||
"google": "https://google.com/search?q={}", |
|
||||||
} |
|
||||||
}, |
|
||||||
"properties": {}, |
|
||||||
"blacklist": [] |
|
||||||
}); |
|
||||||
|
|
||||||
expect(x).to.deep.equal({ |
|
||||||
keymaps: {}, |
|
||||||
search: { |
|
||||||
default: "google", |
|
||||||
engines: { |
|
||||||
google: "https://google.com/search?q={}", |
|
||||||
} |
|
||||||
}, |
|
||||||
properties: { |
|
||||||
hintchars: "abcdefghijklmnopqrstuvwxyz", |
|
||||||
smoothscroll: false, |
|
||||||
complete: "sbh" |
|
||||||
}, |
|
||||||
blacklist: [] |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('sets default settings', () => { |
|
||||||
let value = settings.valueOf({}); |
|
||||||
expect(value.keymaps).to.not.be.empty; |
|
||||||
expect(value.properties).to.not.be.empty; |
|
||||||
expect(value.search.default).to.be.a('string'); |
|
||||||
expect(value.search.engines).to.be.an('object'); |
|
||||||
expect(value.blacklist).to.be.empty; |
|
||||||
}); |
|
||||||
|
|
||||||
it('throws a TypeError with an unknown field', () => { |
|
||||||
expect(() => settings.valueOf({ name: 'alice' })).to.throw(TypeError) |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,49 +0,0 @@ |
|||||||
import { includes } from 'shared/blacklists'; |
|
||||||
|
|
||||||
describe("shared/blacklist", () => { |
|
||||||
it('matches by *', () => { |
|
||||||
let blacklist = ['*']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true; |
|
||||||
}) |
|
||||||
|
|
||||||
it('matches by hostname', () => { |
|
||||||
let blacklist = ['github.com']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com')).to.be.true; |
|
||||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.false; |
|
||||||
expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true; |
|
||||||
expect(includes(blacklist, 'https://github.org')).to.be.false; |
|
||||||
expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false; |
|
||||||
}) |
|
||||||
|
|
||||||
it('matches by hostname with wildcard', () => { |
|
||||||
let blacklist = ['*.github.com']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com')).to.be.false; |
|
||||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.true; |
|
||||||
}) |
|
||||||
|
|
||||||
it('matches by path', () => { |
|
||||||
let blacklist = ['github.com/abc']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true; |
|
||||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false; |
|
||||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; |
|
||||||
}) |
|
||||||
|
|
||||||
it('matches by path with wildcard', () => { |
|
||||||
let blacklist = ['github.com/abc*']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true; |
|
||||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true; |
|
||||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; |
|
||||||
}) |
|
||||||
|
|
||||||
it('matches address and port', () => { |
|
||||||
let blacklist = ['127.0.0.1:8888']; |
|
||||||
|
|
||||||
expect(includes(blacklist, 'http://127.0.0.1:8888/')).to.be.true; |
|
||||||
expect(includes(blacklist, 'http://127.0.0.1:8888/hello')).to.be.true; |
|
||||||
}) |
|
||||||
}); |
|
@ -1,18 +0,0 @@ |
|||||||
import * as settings from 'shared/settings'; |
|
||||||
|
|
||||||
describe('properties', () => { |
|
||||||
describe('Def class', () => { |
|
||||||
it('returns property definitions', () => { |
|
||||||
let def = new proerties.Def( |
|
||||||
'smoothscroll', |
|
||||||
'smooth scroll', |
|
||||||
false); |
|
||||||
|
|
||||||
expect(def.name).to.equal('smoothscroll'); |
|
||||||
expect(def.describe).to.equal('smooth scroll'); |
|
||||||
expect(def.defaultValue).to.equal(false); |
|
||||||
expect(def.type).to.equal('boolean'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
@ -1,18 +0,0 @@ |
|||||||
import * as settings from 'shared/settings'; |
|
||||||
|
|
||||||
describe('properties', () => { |
|
||||||
describe('Def class', () => { |
|
||||||
it('returns property definitions', () => { |
|
||||||
let def = new proerties.Def( |
|
||||||
'smoothscroll', |
|
||||||
'smooth scroll', |
|
||||||
false); |
|
||||||
|
|
||||||
expect(def.name).to.equal('smoothscroll'); |
|
||||||
expect(def.describe).to.equal('smooth scroll'); |
|
||||||
expect(def.defaultValue).to.equal(false); |
|
||||||
expect(def.type).to.equal('boolean'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
@ -0,0 +1,77 @@ |
|||||||
|
import Blacklist from '../../../src/shared/settings/Blacklist'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
describe('Blacklist', () => { |
||||||
|
describe('fromJSON', () => { |
||||||
|
it('returns empty array by empty settings', () => { |
||||||
|
let blacklist = Blacklist.fromJSON([]); |
||||||
|
expect(blacklist.toJSON()).to.be.empty; |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns blacklist by valid settings', () => { |
||||||
|
let blacklist = Blacklist.fromJSON([ |
||||||
|
'github.com', |
||||||
|
'circleci.com', |
||||||
|
]); |
||||||
|
|
||||||
|
expect(blacklist.toJSON()).to.deep.equal([ |
||||||
|
'github.com', |
||||||
|
'circleci.com', |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws a TypeError by invalid settings', () => { |
||||||
|
expect(() => Blacklist.fromJSON(null)).to.throw(TypeError); |
||||||
|
expect(() => Blacklist.fromJSON({})).to.throw(TypeError); |
||||||
|
expect(() => Blacklist.fromJSON([1,2,3])).to.throw(TypeError); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('#includes', () => { |
||||||
|
it('matches by *', () => { |
||||||
|
let blacklist = new Blacklist(['*']); |
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('matches by hostname', () => { |
||||||
|
let blacklist = new Blacklist(['github.com']); |
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com')).to.be.true; |
||||||
|
expect(blacklist.includes('https://gist.github.com')).to.be.false; |
||||||
|
expect(blacklist.includes('https://github.com/ueokande')).to.be.true; |
||||||
|
expect(blacklist.includes('https://github.org')).to.be.false; |
||||||
|
expect(blacklist.includes('https://google.com/search?q=github.org')).to.be.false; |
||||||
|
}); |
||||||
|
|
||||||
|
it('matches by hostname with wildcard', () => { |
||||||
|
let blacklist = new Blacklist(['*.github.com']); |
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com')).to.be.false; |
||||||
|
expect(blacklist.includes('https://gist.github.com')).to.be.true; |
||||||
|
}) |
||||||
|
|
||||||
|
it('matches by path', () => { |
||||||
|
let blacklist = new Blacklist(['github.com/abc']); |
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true; |
||||||
|
expect(blacklist.includes('https://github.com/abcdef')).to.be.false; |
||||||
|
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false; |
||||||
|
}) |
||||||
|
|
||||||
|
it('matches by path with wildcard', () => { |
||||||
|
let blacklist = new Blacklist(['github.com/abc*']); |
||||||
|
|
||||||
|
expect(blacklist.includes('https://github.com/abc')).to.be.true; |
||||||
|
expect(blacklist.includes('https://github.com/abcdef')).to.be.true; |
||||||
|
expect(blacklist.includes('https://gist.github.com/abc')).to.be.false; |
||||||
|
}) |
||||||
|
|
||||||
|
it('matches address and port', () => { |
||||||
|
let blacklist = new Blacklist(['127.0.0.1:8888']); |
||||||
|
|
||||||
|
expect(blacklist.includes('http://127.0.0.1:8888/')).to.be.true; |
||||||
|
expect(blacklist.includes('http://127.0.0.1:8888/hello')).to.be.true; |
||||||
|
}) |
||||||
|
}) |
||||||
|
}); |
@ -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('<C-X>'); |
||||||
|
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('<C-M-X>'); |
||||||
|
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('<C-S-x>'); |
||||||
|
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('<S-Esc>'); |
||||||
|
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('<C-Esc>'); |
||||||
|
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('<C-Space>'); |
||||||
|
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; |
||||||
|
}); |
||||||
|
}); |
@ -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('<S-U>')); |
||||||
|
|
||||||
|
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('<S-U>'), |
||||||
|
]); |
||||||
|
|
||||||
|
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('<S-U>'), |
||||||
|
]))).to.be.true; |
||||||
|
expect(seq.startsWith(new KeySequence([ |
||||||
|
Key.fromMapKey('g'), Key.fromMapKey('<S-U>'), 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('<S-Esc>').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 a<C-B><A-C>d<M-e>', () => { |
||||||
|
let keys = KeySequence.fromMapKeys('a<C-B><A-C>d<M-e>').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'); |
||||||
|
}); |
||||||
|
}) |
||||||
|
}); |
@ -0,0 +1,66 @@ |
|||||||
|
import Keymaps from '../../../src/shared/settings/Keymaps'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
describe('Keymaps', () => { |
||||||
|
describe('#valueOf', () => { |
||||||
|
it('returns empty object by empty settings', () => { |
||||||
|
let keymaps = Keymaps.fromJSON({}).toJSON(); |
||||||
|
expect(keymaps).to.be.empty; |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns keymaps by valid settings', () => { |
||||||
|
let keymaps = Keymaps.fromJSON({ |
||||||
|
k: { type: "scroll.vertically", count: -1 }, |
||||||
|
j: { type: "scroll.vertically", count: 1 }, |
||||||
|
}).toJSON(); |
||||||
|
|
||||||
|
expect(keymaps['k']).to.deep.equal({ type: "scroll.vertically", count: -1 }); |
||||||
|
expect(keymaps['j']).to.deep.equal({ type: "scroll.vertically", count: 1 }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws a TypeError by invalid settings', () => { |
||||||
|
expect(() => Keymaps.fromJSON(null)).to.throw(TypeError); |
||||||
|
expect(() => Keymaps.fromJSON({ |
||||||
|
k: { type: "invalid.operation" }, |
||||||
|
})).to.throw(TypeError); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('#combine', () => { |
||||||
|
it('returns combined keymaps', () => { |
||||||
|
let keymaps = Keymaps.fromJSON({ |
||||||
|
k: { type: "scroll.vertically", count: -1 }, |
||||||
|
j: { type: "scroll.vertically", count: 1 }, |
||||||
|
}).combine(Keymaps.fromJSON({ |
||||||
|
n: { type: "find.next" }, |
||||||
|
N: { type: "find.prev" }, |
||||||
|
})); |
||||||
|
|
||||||
|
let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2)); |
||||||
|
expect(entries).deep.equals([ |
||||||
|
['j', { type: "scroll.vertically", count: 1 }], |
||||||
|
['k', { type: "scroll.vertically", count: -1 }], |
||||||
|
['n', { type: "find.next" }], |
||||||
|
['N', { type: "find.prev" }], |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('overrides current keymaps', () => { |
||||||
|
let keymaps = Keymaps.fromJSON({ |
||||||
|
k: { type: "scroll.vertically", count: -1 }, |
||||||
|
j: { type: "scroll.vertically", count: 1 }, |
||||||
|
}).combine(Keymaps.fromJSON({ |
||||||
|
n: { type: "find.next" }, |
||||||
|
j: { type: "find.prev" }, |
||||||
|
})); |
||||||
|
|
||||||
|
let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2)); |
||||||
|
expect(entries).deep.equals([ |
||||||
|
['j', { type: "find.prev" }], |
||||||
|
['k', { type: "scroll.vertically", count: -1 }], |
||||||
|
['n', { type: "find.next" }], |
||||||
|
]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
@ -0,0 +1,30 @@ |
|||||||
|
import Properties from '../../../src/shared/settings/Properties'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
describe('Properties', () => { |
||||||
|
describe('#propertiesValueOf', () => { |
||||||
|
it('returns with default properties by empty settings', () => { |
||||||
|
let props = Properties.fromJSON({}); |
||||||
|
expect(props).to.deep.equal({ |
||||||
|
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||||
|
smoothscroll: false, |
||||||
|
complete: "sbh" |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns properties by valid settings', () => { |
||||||
|
let props = Properties.fromJSON({ |
||||||
|
hintchars: "abcdefgh", |
||||||
|
smoothscroll: false, |
||||||
|
complete: "sbh" |
||||||
|
}); |
||||||
|
|
||||||
|
expect(props).to.deep.equal({ |
||||||
|
hintchars: "abcdefgh", |
||||||
|
smoothscroll: false, |
||||||
|
complete: "sbh" |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
@ -0,0 +1,68 @@ |
|||||||
|
import Search from '../../../src/shared/settings/Search'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
describe('Search', () => { |
||||||
|
it('returns search settings by valid settings', () => { |
||||||
|
let search = Search.fromJSON({ |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
'google': 'https://google.com/search?q={}', |
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}', |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
expect(search.defaultEngine).to.equal('google') |
||||||
|
expect(search.engines).to.deep.equals({ |
||||||
|
'google': 'https://google.com/search?q={}', |
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}', |
||||||
|
}); |
||||||
|
expect(search.toJSON()).to.deep.equal({ |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
'google': 'https://google.com/search?q={}', |
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}', |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws a TypeError by invalid settings', () => { |
||||||
|
expect(() => Search.fromJSON(null)).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON([])).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 123, |
||||||
|
engines: {} |
||||||
|
})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
'google': 123456, |
||||||
|
} |
||||||
|
})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 'wikipedia', |
||||||
|
engines: { |
||||||
|
'google': 'https://google.com/search?q={}', |
||||||
|
'yahoo': 'https://search.yahoo.com/search?p={}', |
||||||
|
} |
||||||
|
})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 'g o o g l e', |
||||||
|
engines: { |
||||||
|
'g o o g l e': 'https://google.com/search?q={}', |
||||||
|
} |
||||||
|
})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
'google': 'https://google.com/search', |
||||||
|
} |
||||||
|
})).to.throw(TypeError); |
||||||
|
expect(() => Search.fromJSON({ |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
'google': 'https://google.com/search?q={}&r={}', |
||||||
|
} |
||||||
|
})).to.throw(TypeError); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,54 @@ |
|||||||
|
import Settings from '../../../src/shared/settings/Settings'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
describe('Settings', () => { |
||||||
|
describe('#valueOf', () => { |
||||||
|
it('returns settings by valid settings', () => { |
||||||
|
let x = Settings.fromJSON({ |
||||||
|
keymaps: {}, |
||||||
|
"search": { |
||||||
|
"default": "google", |
||||||
|
"engines": { |
||||||
|
"google": "https://google.com/search?q={}", |
||||||
|
} |
||||||
|
}, |
||||||
|
"properties": {}, |
||||||
|
"blacklist": [] |
||||||
|
}); |
||||||
|
|
||||||
|
expect({ |
||||||
|
keymaps: x.keymaps.toJSON(), |
||||||
|
search: x.search.toJSON(), |
||||||
|
properties: x.properties.toJSON(), |
||||||
|
blacklist: x.blacklist.toJSON(), |
||||||
|
}).to.deep.equal({ |
||||||
|
keymaps: {}, |
||||||
|
search: { |
||||||
|
default: "google", |
||||||
|
engines: { |
||||||
|
google: "https://google.com/search?q={}", |
||||||
|
} |
||||||
|
}, |
||||||
|
properties: { |
||||||
|
hintchars: "abcdefghijklmnopqrstuvwxyz", |
||||||
|
smoothscroll: false, |
||||||
|
complete: "sbh" |
||||||
|
}, |
||||||
|
blacklist: [] |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('sets default settings', () => { |
||||||
|
let value = Settings.fromJSON({}); |
||||||
|
expect(value.keymaps.toJSON()).to.not.be.empty; |
||||||
|
expect(value.properties.toJSON()).to.not.be.empty; |
||||||
|
expect(value.search.defaultEngine).to.be.a('string'); |
||||||
|
expect(value.search.engines).to.be.an('object'); |
||||||
|
expect(value.blacklist.toJSON()).to.be.empty; |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws a TypeError with an unknown field', () => { |
||||||
|
expect(() => Settings.fromJSON({ name: 'alice' })).to.throw(TypeError) |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -1,19 +0,0 @@ |
|||||||
import * as re from 'shared/utils/re'; |
|
||||||
|
|
||||||
describe("re util", () => { |
|
||||||
it('matches by pattern', () => { |
|
||||||
let regex = re.fromWildcard('*.example.com/*'); |
|
||||||
expect('foo.example.com/bar').to.match(regex); |
|
||||||
expect('foo.example.com').not.to.match(regex); |
|
||||||
expect('example.com/bar').not.to.match(regex); |
|
||||||
|
|
||||||
regex = re.fromWildcard('example.com/*') |
|
||||||
expect('example.com/foo').to.match(regex); |
|
||||||
expect('example.com/').to.match(regex); |
|
||||||
|
|
||||||
regex = re.fromWildcard('example.com/*bar') |
|
||||||
expect('example.com/foobar').to.match(regex); |
|
||||||
expect('example.com/bar').to.match(regex); |
|
||||||
expect('example.com/foobarfoo').not.to.match(regex); |
|
||||||
}) |
|
||||||
}); |
|
Reference in new issue