From 1664a02c02be27a824b19633740a23ec01573c77 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Dec 2016 08:22:18 +0000 Subject: Rework key-mapping parsing. There are two changes here: 1. Treat built-in key mappings and custom key mappings in the same way; that is, we prepend the built-in mappings to the custom mappings and then parse them all together. This results in a number of simplifications due to previous duplication of logic and the elimination of special cases. 2. Parse key mappings in reverse order, so we can just ignore key mappings after first encountering a key sequence. So, `map`, `unmap` and `unmapAll` are all treated in more or less the same way. This is preparatory to reworking some aspects of the help page. In particular, regardless of the order of maps, unmaps, etc., this approach makes it easier to find the mapping in effect for a key sequence (and be able to recreate the order of those mappings). --- background_scripts/commands.coffee | 120 +++++++++++++++---------------------- 1 file changed, 47 insertions(+), 73 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index 6aa8ca0d..2d3312ea 100644 --- a/background_scripts/commands.coffee +++ b/background_scripts/commands.coffee @@ -1,36 +1,54 @@ Commands = + availableCommands: {} + keyToCommandRegistry: null + mapKeyRegistry: null + init: -> - for own command, descriptor of commandDescriptions - @addCommand(command, descriptor[0], descriptor[1]) - @loadKeyMappings Settings.get "keyMappings" + for own command, [description, options] of commandDescriptions + @availableCommands[command] = extend (options ? {}), description: description + Settings.postUpdateHooks["keyMappings"] = @loadKeyMappings.bind this + @loadKeyMappings Settings.get "keyMappings" loadKeyMappings: (customKeyMappings) -> - @clearKeyMappingsAndSetDefaults() - @parseCustomKeyMappings customKeyMappings - @generateKeyStateMapping() - chrome.storage.local.set mapKeyRegistry: @mapKeyRegistry - - availableCommands: {} - keyToCommandRegistry: {} - - # Registers a command, making it available to be optionally bound to a key. - # options: - # - background: whether this command needs to be run against the background page. - addCommand: (command, description, options = {}) -> - if command of @availableCommands - BgUtils.log "#{command} is already defined! Check commands.coffee for duplicates." - return + @keyToCommandRegistry = {} + @mapKeyRegistry = {} - @availableCommands[command] = extend options, description: description + configLines = ("map #{key} #{command}" for own key, command of defaultKeyMappings) + configLines.push BgUtils.parseLines(customKeyMappings)... + seen = {} + unmapAll = false + for line in configLines.reverse() + tokens = line.split /\s+/ + switch tokens[0] + when "map" + if 3 <= tokens.length and not unmapAll + [_, key, command, optionsList...] = tokens + if not seen[key] and registryEntry = @availableCommands[command] + seen[key] = true + keySequence = @parseKeySequence key + options = @parseCommandOptions command, optionsList + @keyToCommandRegistry[key] = extend {keySequence, command, options}, @availableCommands[command] + when "unmap" + if tokens.length == 2 + seen[tokens[1]] = true + when "unmapAll" + unmapAll = true + when "mapkey" + if tokens.length == 3 + fromChar = @parseKeySequence tokens[1] + toChar = @parseKeySequence tokens[2] + @mapKeyRegistry[fromChar[0]] = toChar[0] if fromChar.length == toChar.length == 1 - mapKeyToCommand: ({ key, keySequence, command, options }) -> - unless @availableCommands[command] - BgUtils.log "#{command} doesn't exist!" - return + chrome.storage.local.set mapKeyRegistry: @mapKeyRegistry + @installKeyStateMapping() - options ?= {} - @keyToCommandRegistry[key] = extend { keySequence, command, options }, @availableCommands[command] + # Push the key mapping for passNextKey into Settings so that it's available in the front end for insert + # mode. We exclude single-key mappings (that is, printable keys) because when users press printable keys + # in insert mode they expect the character to be input, not to be droppped into some special Vimium + # mode. + Settings.set "passNextKeyKeys", + (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey" and 1 < key.length) # Lower-case the appropriate portions of named keys. # @@ -60,43 +78,6 @@ Commands = else [key[0], @parseKeySequence(key[1..])...] - parseCustomKeyMappings: (customKeyMappings) -> - for line in BgUtils.parseLines customKeyMappings - tokens = line.split /\s+/ - switch tokens[0] - when "map" - [ _, key, command, optionList... ] = tokens - keySequence = @parseKeySequence key - if command? and @availableCommands[command] - key = keySequence.join "" - BgUtils.log "mapping [\"#{keySequence.join '", "'}\"] to #{command}" - @mapKeyToCommand { key, command, keySequence, options: @parseCommandOptions command, optionList } - else - BgUtils.log "skipping [\"#{keySequence.join '", "'}\"] for #{command} -- something is not right" - - when "unmap" - if tokens.length == 2 - keySequence = @parseKeySequence tokens[1] - key = keySequence.join "" - BgUtils.log "Unmapping #{key}" - delete @keyToCommandRegistry[key] - - when "unmapAll" - @keyToCommandRegistry = {} - - when "mapkey" - if tokens.length == 3 - fromChar = @parseKeySequence tokens[1] - toChar = @parseKeySequence tokens[2] - @mapKeyRegistry[fromChar[0]] = toChar[0] if fromChar.length == toChar.length == 1 - - # Push the key mapping for passNextKey into Settings so that it's available in the front end for insert - # mode. We exclude single-key mappings (that is, printable keys) because when users press printable keys - # in insert mode they expect the character to be input, not to be droppped into some special Vimium - # mode. - Settings.set "passNextKeyKeys", - (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey" and 1 < key.length) - # Command options follow command mappings, and are of one of two forms: # key=value - a value # key - a flag @@ -113,16 +94,9 @@ Commands = options - clearKeyMappingsAndSetDefaults: -> - @keyToCommandRegistry = {} - @mapKeyRegistry = {} - for own key, command of defaultKeyMappings - keySequence = @parseKeySequence key - key = keySequence.join "" - @mapKeyToCommand { key, command, keySequence } - - # This generates a nested key-to-command mapping structure. There is an example in mode_key_handler.coffee. - generateKeyStateMapping: -> + # This generates and installs a nested key-to-command mapping structure. There is an example in + # mode_key_handler.coffee. + installKeyStateMapping: -> keyStateMapping = {} for own keys, registryEntry of @keyToCommandRegistry currentMapping = keyStateMapping @@ -136,7 +110,7 @@ Commands = else currentMapping[key] = extend {}, registryEntry # We don't need these properties in the content scripts. - delete registryEntry[prop] for prop in ["keySequence", "description"] + delete currentMapping[key][prop] for prop in ["keySequence", "description"] chrome.storage.local.set normalModeKeyStateMapping: keyStateMapping # An ordered listing of all available commands, grouped by type. This is the order they will -- cgit v1.2.3 From cc09af6921228033b822ef4ad4713abd26c375e7 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Dec 2016 14:18:59 +0000 Subject: Rework help-dialog HTML. Previously, the dynamic HTML for the help dialog was generated on the background page. The HTML itself was tangled in with program logic. Here, we move all of the HTML to HTML5 templates; also, we build the help-dialog contents in the help dialog itself, not on the background page. Note: #2368 is included here too. (Background: I'm trying to clean up some of the command and help-dialog logic in preparation for addressing the issue of how to document command options, see #2319.) --- background_scripts/commands.coffee | 19 ++++++++++++++ background_scripts/main.coffee | 51 -------------------------------------- 2 files changed, 19 insertions(+), 51 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index 2d3312ea..f5a3840b 100644 --- a/background_scripts/commands.coffee +++ b/background_scripts/commands.coffee @@ -9,6 +9,7 @@ Commands = Settings.postUpdateHooks["keyMappings"] = @loadKeyMappings.bind this @loadKeyMappings Settings.get "keyMappings" + @prepareHelpPageData() loadKeyMappings: (customKeyMappings) -> @keyToCommandRegistry = {} @@ -113,6 +114,24 @@ Commands = delete currentMapping[key][prop] for prop in ["keySequence", "description"] chrome.storage.local.set normalModeKeyStateMapping: keyStateMapping + # Build the "helpPageData" data structure which the help page needs and place it in Chrome storage. We do + # this on the "nextTick" because there's not need for it to be done synchronously. + prepareHelpPageData: -> + Utils.nextTick => + commandToKey = {} + for own key, registryEntry of @keyToCommandRegistry + (commandToKey[registryEntry.command] ?= []).push key + commandGroups = {} + for own group, commands of @commandGroups + commandGroups[group] = [] + for command in commands + commandGroups[group].push + command: command + description: @availableCommands[command].description + keys: commandToKey[command] ? [] + advanced: command in @advancedCommands + chrome.storage.local.set helpPageData: commandGroups + # An ordered listing of all available commands, grouped by type. This is the order they will # be shown in the help page. commandGroups: diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index e57e061d..8f99cf3e 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -80,56 +80,6 @@ onURLChange = (details) -> chrome.webNavigation.onHistoryStateUpdated.addListener onURLChange # history.pushState. chrome.webNavigation.onReferenceFragmentUpdated.addListener onURLChange # Hash changed. -# Retrieves the help dialog HTML template from a file, and populates it with the latest keybindings. -getHelpDialogHtml = ({showUnboundCommands, showCommandNames, customTitle}) -> - commandsToKey = {} - for own key of Commands.keyToCommandRegistry - command = Commands.keyToCommandRegistry[key].command - commandsToKey[command] = (commandsToKey[command] || []).concat(key) - - replacementStrings = - version: Utils.getCurrentVersion() - title: customTitle || "Help" - tip: if showCommandNames then "Tip: click command names to yank them to the clipboard." else " " - - for own group of Commands.commandGroups - replacementStrings[group] = - helpDialogHtmlForCommandGroup(group, commandsToKey, Commands.availableCommands, - showUnboundCommands, showCommandNames) - - replacementStrings - -# -# Generates HTML for a given set of commands. commandGroups are defined in commands.js -# -helpDialogHtmlForCommandGroup = (group, commandsToKey, availableCommands, - showUnboundCommands, showCommandNames) -> - html = [] - for command in Commands.commandGroups[group] - keys = commandsToKey[command] || [] - bindings = ("#{Utils.escapeHtml key}" for key in keys).join ", " - if (showUnboundCommands || commandsToKey[command]) - isAdvanced = Commands.advancedCommands.indexOf(command) >= 0 - description = availableCommands[command].description - if keys.join(", ").length < 12 - helpDialogHtmlForCommand html, isAdvanced, bindings, description, showCommandNames, command - else - # If the length of the bindings is too long, then we display the bindings on a separate row from the - # description. This prevents the column alignment from becoming out of whack. - helpDialogHtmlForCommand html, isAdvanced, bindings, "", false, "" - helpDialogHtmlForCommand html, isAdvanced, "", description, showCommandNames, command - html.join("\n") - -helpDialogHtmlForCommand = (html, isAdvanced, bindings, description, showCommandNames, command) -> - html.push "