2017-11-09 00:38:25 +00:00
|
|
|
/** Tridactyl normal mode:
|
|
|
|
|
|
|
|
differs from Vim in that no map may be a prefix of another map (e.g. 'g' and 'gg' cannot both be maps). This simplifies the parser.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Normal-mode mappings.
|
|
|
|
// keystr -> ex_str
|
|
|
|
// TODO: Move these into a tridactyl-wide state namespace
|
|
|
|
// TODO: stop stealing keys from "insert mode"
|
|
|
|
// r -> refresh page is particularly unhelpful
|
|
|
|
// Can't stringify a map -> just use an object
|
2017-11-19 06:05:15 +00:00
|
|
|
export const DEFAULTNMAPS = {
|
2017-11-09 00:38:25 +00:00
|
|
|
"o": "fillcmdline open",
|
2017-11-09 15:30:09 +00:00
|
|
|
"O": "current_url open",
|
2017-11-09 00:38:25 +00:00
|
|
|
"w": "fillcmdline winopen",
|
2017-11-09 15:30:09 +00:00
|
|
|
"W": "current_url winopen",
|
2017-11-09 00:38:25 +00:00
|
|
|
"t": "tabopen",
|
|
|
|
//["t": "fillcmdline tabopen", // for now, use mozilla completion
|
|
|
|
"]]": "clicknext",
|
|
|
|
"[[": "clicknext prev",
|
2017-11-09 15:30:09 +00:00
|
|
|
"T": "current_url tabopen",
|
2017-11-09 00:38:25 +00:00
|
|
|
"yy": "clipboard yank",
|
|
|
|
"p": "clipboard open",
|
|
|
|
"P": "clipboard tabopen",
|
|
|
|
"j": "scrollline 10",
|
|
|
|
"k": "scrollline -10",
|
|
|
|
"h": "scrollpx -50",
|
|
|
|
"l": "scrollpx 50",
|
|
|
|
"G": "scrollto 100",
|
|
|
|
"gg": "scrollto 0",
|
|
|
|
"H": "back",
|
|
|
|
"L": "forward",
|
|
|
|
"d": "tabclose",
|
|
|
|
"u": "undo",
|
|
|
|
"r": "reload",
|
|
|
|
"R": "reloadhard",
|
|
|
|
"gt": "tabnext",
|
|
|
|
"gT": "tabprev",
|
|
|
|
"gr": "reader",
|
|
|
|
":": "fillcmdline",
|
|
|
|
"s": "fillcmdline open google",
|
|
|
|
"S": "fillcmdline tabopen google",
|
2017-11-19 13:45:18 +01:00
|
|
|
"M": "gobble 1 quickmark",
|
2017-11-09 00:38:25 +00:00
|
|
|
"xx": "something",
|
|
|
|
"b": "openbuffer",
|
|
|
|
"ZZ": "qall",
|
2017-11-09 00:41:07 +00:00
|
|
|
"f": "hint",
|
2017-11-18 03:10:58 +00:00
|
|
|
"F": "hint -b",
|
2017-11-15 00:03:34 +00:00
|
|
|
"I": "mode ignore",
|
2017-11-09 00:38:25 +00:00
|
|
|
// Special keys must be prepended with 🄰
|
|
|
|
// ["🄰Backspace", "something"],
|
|
|
|
}
|
|
|
|
|
2017-11-19 06:05:15 +00:00
|
|
|
let nmaps = Object.assign(Object.create(null), DEFAULTNMAPS)
|
|
|
|
|
2017-11-09 00:38:25 +00:00
|
|
|
// Allow config to be changed in settings
|
|
|
|
// TODO: make this more general
|
|
|
|
browser.storage.sync.get("nmaps").then(lazyloadconfig)
|
2017-11-19 06:05:15 +00:00
|
|
|
async function lazyloadconfig(storageResult){
|
|
|
|
nmaps = Object.assign(Object.create(null), DEFAULTNMAPS, storageResult.nmaps)
|
2017-11-09 00:38:25 +00:00
|
|
|
console.log(nmaps)
|
|
|
|
}
|
|
|
|
|
|
|
|
browser.storage.onChanged.addListener(
|
|
|
|
(changes, areaname) => {
|
|
|
|
if (areaname == "sync") {
|
2017-11-09 10:23:36 +00:00
|
|
|
browser.storage.sync.get("nmaps").then(lazyloadconfig)
|
2017-11-09 00:38:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Split a string into a number prefix and some following keys.
|
|
|
|
function keys_split_count(keys: string[]){
|
|
|
|
// Extracts the first number with capturing parentheses
|
|
|
|
const FIRST_NUM_REGEX = /^([0-9]+)/
|
|
|
|
|
|
|
|
let keystr = keys.join("")
|
|
|
|
let regexCapture = FIRST_NUM_REGEX.exec(keystr)
|
|
|
|
let count = regexCapture ? regexCapture[0] : null
|
|
|
|
keystr = keystr.replace(FIRST_NUM_REGEX,"")
|
|
|
|
return [count, keystr]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given a valid keymap, resolve it to an ex_str
|
|
|
|
function resolve_map(map) {
|
|
|
|
// TODO: This needs to become recursive to allow maps to be defined in terms of other maps.
|
|
|
|
return nmaps[map]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Valid keystr to ex_str by splitting count, resolving keystr and appending count as final argument.
|
|
|
|
// TODO: This is a naive way to deal with counts and won't work for ExCmds that don't expect a numeric answer.
|
|
|
|
// TODO: Refactor to return a ExCmdPartial object?
|
|
|
|
function get_ex_str(keys): string {
|
|
|
|
let [count, keystr] = keys_split_count(keys)
|
|
|
|
let ex_str = resolve_map(keystr)
|
|
|
|
if (ex_str){
|
|
|
|
ex_str = count ? ex_str + " " + count : ex_str
|
|
|
|
}
|
|
|
|
return ex_str
|
|
|
|
}
|
|
|
|
|
|
|
|
// A list of maps that keys could potentially map to.
|
|
|
|
function possible_maps(keys): string[] {
|
|
|
|
let [count, keystr] = keys_split_count(keys)
|
|
|
|
|
|
|
|
// Short circuit or search maps.
|
2017-11-19 06:05:15 +00:00
|
|
|
if (Object.getOwnPropertyNames(nmaps).includes(keystr)) {
|
2017-11-09 00:38:25 +00:00
|
|
|
return [keystr,]
|
|
|
|
} else {
|
|
|
|
// Efficiency: this can be short-circuited.
|
|
|
|
return completions(keystr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A list of maps that start with the fragment.
|
|
|
|
export function completions(fragment): string[] {
|
|
|
|
let posskeystrs = Array.from(Object.keys(nmaps))
|
|
|
|
return posskeystrs.filter((key)=>key.startsWith(fragment))
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface NormalResponse {
|
|
|
|
keys?: string[]
|
|
|
|
ex_str?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parser(keys): NormalResponse {
|
|
|
|
// If there aren't any possible matches, throw away keys until there are
|
|
|
|
while ((possible_maps(keys).length == 0) && (keys.length)) {
|
|
|
|
keys = keys.slice(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If keys map to an ex_str, send it
|
|
|
|
let ex_str = get_ex_str(keys)
|
|
|
|
if (ex_str){
|
|
|
|
return {ex_str}
|
|
|
|
}
|
|
|
|
// Otherwise, return the keys that might be used in a future command
|
|
|
|
return {keys}
|
|
|
|
}
|