diff --git a/README.md b/README.md index 1534fc6ed..b0a851497 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ Examples: showing a bookmark dialog is suppressed. - `map r reload hard` maps the r key to reloading the page, and also includes the "hard" option to hard-reload the page. +- `mapkey ы s` maps one key to another. +- `langmap фыва;asdf,aAbBzZ,cC,dD` maps multiple keys with `from_keys;to_keys` or `fT` pairs, separated by comma. - `unmap ` removes any mapping for ctrl+d and restores Chrome's default behavior. - `unmap r` removes any mapping for the r key. diff --git a/background_scripts/commands.js b/background_scripts/commands.js index faaf58497..af8efbf0b 100644 --- a/background_scripts/commands.js +++ b/background_scripts/commands.js @@ -89,6 +89,53 @@ const KeyMappingsParser = { } }; + const mapCharacter = (from, to) => { + const fromChar = this.parseKeySequence(from); + const toChar = this.parseKeySequence(to); + const isValid = fromChar.length == toChar.length && toChar.length === 1; + if (isValid) { + mapKeyRegistry[fromChar[0]] = toChar[0]; + } else { + errors.push( + `mapkey/langmap only support mapping keys which are single characters`, + ); + } + }; + + const parseLangmap = (langmap) => { + let escape = false; + let current = []; + let left = []; + for (const c of langmap) { + if (escape) { + current.push(c); + escape = false; + continue; + } + if (c === ";") { + left = current; + current = []; + continue; + } + current.push(c); + } + if (left.length > 0) { + if (left.length !== current.length) { + errors.push(`Incorrect usage for langmap in the line: ${line}`); + return; + } + for (let i = 0; i < left.length; i++) { + mapCharacter(left[i], current[i]); + } + } else if (current.length % 2 === 0) { + for (let i = 0; i < current.length - 1; i += 2) { + mapCharacter(current[i], current[i + 1]); + } + } else { + errors.push(`Incorrect usage for langmap in the line: ${line}`); + } + }; + for (const line of configLines) { const tokens = line.split(/\s+/); const action = tokens[0].toLowerCase(); @@ -173,17 +220,36 @@ const KeyMappingsParser = { errors.push(`Incorrect usage for mapKey in the line: ${line}`); continue; } - const fromChar = this.parseKeySequence(tokens[1]); - const toChar = this.parseKeySequence(tokens[2]); - // NOTE(philc): I'm not sure why we enforce that the fromChar and toChar have to be - // length one. It's been that way since this feature was introduced in 6596e30. - const isValid = fromChar.length == toChar.length && toChar.length === 1; - if (isValid) { - mapKeyRegistry[fromChar[0]] = toChar[0]; - } else { - errors.push( - `mapkey only supports mapping keys which are single characters. Line: ${line}`, - ); + mapCharacter(tokens[1], tokens[2]); + break; + } + case "langmap": { + if (tokens.length !== 2) { + errors.push(`Incorrect usage for langmap in the line: ${line}`); + continue; + } + const langmap = tokens[1]; + let escape = false; + let current = []; + for (const c of langmap) { + if (escape) { + current.push(c); + escape = false; + continue; + } + if (c === ",") { + parseLangmap(current); + current = []; + continue; + } + if (c === "\\") { + escape = true; + continue; + } + current.push(c); + } + if (current.length > 0) { + parseLangmap(current); } break; } diff --git a/pages/options.html b/pages/options.html index 4cc95e1eb..715c1acd7 100644 --- a/pages/options.html +++ b/pages/options.html @@ -43,6 +43,8 @@

Custom key mappings

> map j scrollDown map z2 setZoom level=2 +mapkey я z +langmap aA,йцу;qwe unmap j unmapAll " this is a comment diff --git a/tests/unit_tests/commands_test.js b/tests/unit_tests/commands_test.js index e3b63c11c..0e9a3cee4 100644 --- a/tests/unit_tests/commands_test.js +++ b/tests/unit_tests/commands_test.js @@ -38,6 +38,11 @@ context("KeyMappingsParser", () => { assert.equal({ "a": "b" }, keyToMappedKey); }); + should("handle langmap statements", () => { + const { keyToMappedKey } = KeyMappingsParser.parse("langmap фы;as,a\\bAB"); + assert.equal({ "ф": "a", "ы": "s", "a": "b", "A": "B" }, keyToMappedKey); + }); + should("handle unmap statements", () => { const input = "mapkey a b \n unmap a"; const { keyToMappedKey } = KeyMappingsParser.parse(input);