From 355228217d7e7a61f5e1edbb9efbfb0f3e4ef81c Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Thu, 4 Jun 2015 22:22:17 +0200 Subject: [PATCH] Major refactor: Rework all UI and related improvements MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This commit is unfortunately very large. I do not like that, but most of the things in this commit are very closely related, which made it easier to do it this way. Help dialog =========== - It is no longer used to customize shortcuts. That's done in VimFx's settings in the Add-ons Manager instead. - Moved "enjoying/found bug" from the Help dialog and into the readme. The readme will be used as the extension description on AMO. That description is also shown in the Add-ons Manager, so users will see it when customizing VimFx. - It is no longer injected inside web pages, but in the Firefox chrome. This allows to get rid of the nasty resets in the CSS and makes it impossible for page elements to cover it. #477 - It now fills the entire page and adjusts based on your screen size. It is designed to work with lots of shortcut customization and the additon of new modes by other extensions. - Added `gulp help.html` to make it easier to style the help dialog. Settings UI =========== - Shortcuts customization is now done in VimFx's settings page in the Add-ons Manager. This keeps all settings together and makes it obvious that you can customize the shortcuts. The new UI is also more accessible. - The new UI also lets you: - Insert the default value of a pref. This is useful for the "prev/next patterns" by letting you merge in new defaults into your own customizations. - Reset to the default value. #262 - Insert the next keypress as text. The default shortcut for this is ``. (q as in quote. vim also has this shortcut, which works like its ``.) For example, if the user is unsure how to express `` (s)he could simply press `` and `` would be inserted as text. Button ====== - Now has high-resolution images. #383 - Is now implemented using CustomizableUI. #303 #349 - Greatly simplified: - If in normal mode, clicking it opens the help dialog. - Otherwise it exits the current mode returning to normal mode. This is useful if the user accidentally removes the keyboard shortcuts to exit modes, or don't know how to exit insert mode, for example. The above means that the "disable" feature has been removed. The UI to "blacklist" sites has been removed as well. See the "Removed/Changed/Addesd features" section for more info. - It is now easily stylable with user CSS. - State is no longer lost when moving a tab into a new window. #57 Removed/Changed/Added features ============================== - The "disable" feature has been completely removed. It is rather useless compared to insert mode, and sometimes trips new users up. #327 - The "blacklist" feature has been changed into an "auto-insert-mode" feature. It is basically the same, except that you can actually choose to leave the blacklisted state for a while if you feel like it. The default shortcut to leave insert (``) is not likely to conflict with page shortcuts. It is possible to disable the exit shortcut on certain pages using the public API. - The "blacklist" feature has been de-emphasized. The button no longer offers any UI to blacklist and un-blacklist sites (see above). VimFx is supposed to be keyboard driven, so having a mouse-oriented UI makes no sense. Blacklisting is also a bit of an anti-feature, so we shouldn't maintain lots of UI code for it. It's simple enough to just open the Add-ons Manager and add `*currentdomain.com*` to the blacklist setting. - The "blacklist" no longer supports the `!` wild card (that matched a single character) to keep things simple. The less to explain to the user the better. If users need more advanced wildcards than the simple `*` they can use the public API (see the Public API section) which allows to use all of JavaScript's power. - The "blacklist" no longer supports the `##key1#key2` syntax to disable certain keys on certain sites. Instead a superior public API function is offered. This feature only existed in the develop branch for a while and was never released. Any users of it must manually move to the public API. - The "prev/next patterns" no longer support the `*` and `!` wildcards. Instead they are treated as regexes (which allows to use `.*` and `.` instead). This is way simpler, and more powerful. Non-technical users can just keep adding simple patterns such as "next" translated into their language without being troubled by regex syntax, while advanced users can use the full power of regex if needed. The public API may of course also be used for unlimited power. - The "blacklist" and "prev/next patterns" are no longer comma/space-separated. To keep things simple their just space-separated. Space is not a valid URL character, but a comma is, so this allows to match a URL which ends with a comma (but that is probably too rare to ever be useful). More importantly it makes the settings easier to read. Space could be part of a "prev/next pattern", though. Thanks to the above point one may use `\s` instead, which is also more robust. The prefs are automatically migrated to the new format. - The "translations" pref in about:config is no longer used. Instead you have to use the public API. This is because it is an advanced feature and next to impossible to manage as a single line of JSON. This also keeps the code simpler. The pref only existed in the develop branch for a while and was never released. Any users of it must manually move to the public API. - Most hard-coded constants have been moved into prefs, which can only be changed via about:config or the public API. For advanced users only. For example: - "prev/next patterns" attributes. #489 - "target blank prevention". #452 - Pressing `aj` used to trigger the `j` shortcut, but doesn't any longer. Since there is no `aj` shortcut (by default) nothing happens. The reason that "feature" used to exist was because there used to be no concept of a timeout. For example if you pressed `a`, went from the computer for a while, came back and pressed `/` the `a/` shortcut would be invoked, which is a bit surprising. Now there is a timeout instead, which defaults to 2 seconds. So if you type `aj` with less than 2 seconds between the key presses, nothing will happen. Otherwise the `a` partial match will be cancelled and the `j` command will be invoked. This is more like vim works. - The escape command used to be special-cased so that you could trigger it inside text boxes. Now this is done through the concept of "force keys" instead. If you add `` at the beginning of a shortcut it will be available in text boxes as well. #194 Refactoring =========== - All modes are know implemented the same way. Normal mode used to be special-cased and all other modes were rather limited. - Commands and modes are now a lot more DRY. - defaults.coffee now takes care of command categorization and ordering. It is essentially the text version of the help dialog. Changing order and/or categories there automatically affects the help dialog (and the settings UI) as well. - The command matching algorithm is now much more simple and flexible. - Because the help dialog was moved into browser chrome, there is no longer any need to insert the style sheet as an `AGENT_SHEET`, which made it difficult to override it with user CSS, for example to change the font size or color of hint markers. #465 #424 #233 #220 Public API ========== Added a public API. This will be used by advanced users and by other extensions that wish to extend VimFx. It allows to: - Keep all VimFx configuration in a text file (instead of configuring it via VimFx’s GUI and storing it in Firefox’s prefs system). #245 - Add your own custom commands. #508 #490 #235 - Add site-specific options. - Disable specific keys on some sites. #255 - Do things when entering pages, such as automatically entering insert mode (this can be used instead of the "blacklist" feature) or hints mode. #408 - Set some advanced settings that cannot be set any other way. - Add new command categories. - Add new modes. --- .gitignore | 1 + CONTRIBUTING-CODE.md | 3 + README.md | 11 +- extension/bootstrap.coffee | 9 +- extension/chrome.manifest.tmpl | 2 + extension/icon.png | Bin 3508 -> 3164 bytes extension/lib/api.coffee | 113 ++++++ extension/lib/button.coffee | 255 ++---------- extension/lib/command.coffee | 65 ---- extension/lib/commands.coffee | 353 ++++++++--------- extension/lib/defaults.coffee | 259 ++++++++----- extension/lib/events.coffee | 61 +-- extension/lib/help.coffee | 264 +++---------- extension/lib/hints.coffee | 22 +- extension/lib/l10n.coffee | 2 +- extension/lib/legacy.coffee | 28 +- extension/lib/main.coffee | 58 ++- extension/lib/marker.coffee | 29 +- extension/lib/migrations.coffee | 39 +- extension/lib/modes.coffee | 362 +++++++++--------- extension/lib/options.coffee | 243 ++++++++---- extension/lib/parse-prefs.coffee | 92 +++++ extension/lib/prefs.coffee | 77 ++-- extension/lib/public.coffee | 40 ++ extension/lib/utils.coffee | 255 ++++-------- extension/lib/vim.coffee | 53 ++- extension/lib/vimfx.coffee | 223 ++++++++++- extension/lib/window-utils.coffee | 8 +- extension/locale/de/vimfx.properties | 62 ++- extension/locale/el-GR/vimfx.properties | 64 ++-- extension/locale/en-US/vimfx.properties | 64 ++-- extension/locale/fr/vimfx.properties | 64 ++-- extension/locale/hu/vimfx.properties | 64 ++-- extension/locale/id/vimfx.properties | 64 ++-- extension/locale/it/vimfx.properties | 64 ++-- extension/locale/ja/vimfx.properties | 64 ++-- extension/locale/nl/vimfx.properties | 64 ++-- extension/locale/pl/vimfx.properties | 64 ++-- extension/locale/ru/vimfx.properties | 64 ++-- extension/locale/sv-SE/vimfx.properties | 64 ++-- extension/locale/zh-CN/vimfx.properties | 66 ++-- extension/locale/zh-TW/vimfx.properties | 64 ++-- extension/resources/icon16-blacklist.png | Bin 715 -> 0 bytes .../resources/icon16-blacklist_inverse.png | Bin 412 -> 0 bytes extension/resources/icon16-grey.png | Bin 1714 -> 0 bytes extension/resources/style.css | 275 ------------- extension/skin/icon128-red.png | Bin 0 -> 8082 bytes extension/skin/icon128.png | Bin 0 -> 6533 bytes extension/{resources => skin}/icon16-red.png | Bin .../icon16-normal.png => skin/icon16.png} | Bin extension/skin/icon32-red.png | Bin 0 -> 1722 bytes extension/skin/icon32.png | Bin 0 -> 1692 bytes extension/skin/icon64-red.png | Bin 0 -> 3445 bytes extension/skin/icon64.png | Bin 0 -> 3164 bytes extension/skin/style.css | 204 ++++++++++ extension/test/index.coffee | 4 +- extension/test/test-api.coffee | 330 ++++++++++++++++ extension/test/test-legacy.coffee | 3 + extension/test/test-utils.coffee | 2 + gulpfile.coffee | 34 +- icon-large.png | Bin 23493 -> 0 bytes package.json | 1 + 62 files changed, 2462 insertions(+), 2214 deletions(-) create mode 100644 extension/lib/api.coffee delete mode 100644 extension/lib/command.coffee create mode 100644 extension/lib/parse-prefs.coffee create mode 100644 extension/lib/public.coffee delete mode 100755 extension/resources/icon16-blacklist.png delete mode 100644 extension/resources/icon16-blacklist_inverse.png delete mode 100644 extension/resources/icon16-grey.png delete mode 100644 extension/resources/style.css create mode 100644 extension/skin/icon128-red.png create mode 100644 extension/skin/icon128.png rename extension/{resources => skin}/icon16-red.png (100%) rename extension/{resources/icon16-normal.png => skin/icon16.png} (100%) create mode 100644 extension/skin/icon32-red.png create mode 100644 extension/skin/icon32.png create mode 100644 extension/skin/icon64-red.png create mode 100644 extension/skin/icon64.png create mode 100644 extension/skin/style.css create mode 100644 extension/test/test-api.coffee delete mode 100755 icon-large.png diff --git a/.gitignore b/.gitignore index 52fdfdd..ea708ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules /build /gulpfile.js +/help.html diff --git a/CONTRIBUTING-CODE.md b/CONTRIBUTING-CODE.md index a1c6b87..d8cc834 100644 --- a/CONTRIBUTING-CODE.md +++ b/CONTRIBUTING-CODE.md @@ -103,6 +103,9 @@ Finally send a pull request to same branch as you based your topic branch on - `gulp sync-locales` syncs all locales against the en-US locale. To sync against for example the sv-SE locale instead, pass `--sv-SE` as an option. See also the “Syncing locales” section below. +- `gulp help.html` dumps VimFx’s Keyboard Shortcuts dialog into help.html. You + can then open up help.html in Firefox and style it live using the Style + Editor! You can even press the “Save” button when done to save your changes! - Use the `--test` or `-t` option to include the unit test files. The output of the tests are `console.log`ed. See the browser console, or start Firefox from the command line to see it. diff --git a/README.md b/README.md index 59e0580..e58bf98 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VimFx – Vim keyboard shortcuts for Firefox - + [VimFx] is a [Mozilla Firefox] extension which adds Vim-style keyboard shortcuts for browsing and navigation, significantly reducing the use of the mouse, and @@ -65,3 +65,12 @@ shortcuts, and lets you customize every single one of them. You can also look at all default shortcuts [here][defaults]. [defaults]: https://github.com/akhodakivskiy/VimFx/blob/develop/extension/lib/defaults.coffee + +## Feedback + +Enjoying VimFx? [Write a review][review]! + +Found a bug? Report it in the [issue tracker]! + +[review]: https://addons.mozilla.org/firefox/addon/vimfx/reviews/add +[issue tracker]: https://github.com/akhodakivskiy/VimFx/issues diff --git a/extension/bootstrap.coffee b/extension/bootstrap.coffee index 1503c70..9a74e5b 100644 --- a/extension/bootstrap.coffee +++ b/extension/bootstrap.coffee @@ -59,10 +59,11 @@ require.scopes = {} require.data = require('./require-data') # Set default prefs and apply migrations as early as possible. -{ setDefaultPrefs } = require('./lib/prefs') -{ applyMigrations -, migrations } = require('./lib/migrations') -setDefaultPrefs() +{ applyMigrations } = require('./lib/legacy') +migrations = require('./lib/migrations') +prefs = require('./lib/prefs') + +prefs.default._init() applyMigrations(migrations) do (global = this) -> diff --git a/extension/chrome.manifest.tmpl b/extension/chrome.manifest.tmpl index 7da50d4..1441ea3 100644 --- a/extension/chrome.manifest.tmpl +++ b/extension/chrome.manifest.tmpl @@ -1,3 +1,5 @@ +skin vimfx classic/1.0 skin/ + {{#locales}} locale vimfx {{locale}} locale/{{locale}}/ {{/locales}} diff --git a/extension/icon.png b/extension/icon.png index eb0a59da38ceeb207f07ed3d4b4e8a716af0996e..cfe4350a5f4bbc2d22629addc3ef619f08a4c1eb 100644 GIT binary patch literal 3164 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 diff --git a/extension/lib/api.coffee b/extension/lib/api.coffee new file mode 100644 index 0000000..a8969ee --- /dev/null +++ b/extension/lib/api.coffee @@ -0,0 +1,113 @@ +### +# Copyright Simon Lydell 2015. +# +# This file is part of VimFx. +# +# VimFx is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# VimFx is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with VimFx. If not, see . +### + +defaults = require('./defaults') +prefs = require('./prefs') +utils = require('./utils') + +counter = new utils.Counter({start: 10000, step: 100}) + +createAPI = (vimfx) -> + get: (pref) -> switch + when pref of defaults.parsed_options + vimfx.defaults.parsed_options[pref] + when pref of defaults.all_prefs or pref?.startsWith('custom.') + prefs.get(pref) + else + throw new Error("VimFx: Unknown pref: #{ pref }") + + set: (pref, value) -> switch + when pref of defaults.parsed_options + vimfx.options[pref] = value + when pref of defaults.all_prefs or pref?.startsWith('custom.') + prefs.set(pref, value) + else + throw new Error("VimFx: Unknown pref: #{ pref }") + + addCommand: ({ name, description, mode, category, order } = {}, fn) -> + mode ?= 'normal' + category ?= if mode == 'normal' then 'misc' else '' + order ?= counter.tick() + + unless typeof name == 'string' + throw new Error("VimFx: A command name as a string is required. + Got: #{ name }") + unless /^[a-z_]+$/.test(name) + throw new Error("VimFx: Command names should only consist of a-z + (lowercase) and underscores. Got: #{ name }") + unless typeof description == 'string' and description != '' + throw new Error("VimFx: Commands must have a non-empty description. + Got: #{ description }") + unless utils.has(vimfx.modes, mode) + modes = Object.keys(vimfx.modes).join(', ') + throw new Error("VimFx: Unknown mode. Available modes are: #{ modes }. + Got: #{ mode }") + unless utils.has(vimfx.options.categories, category) + categories = Object.keys(vimfx.options.categories).join(', ') + throw new Error("VimFx: Unknown category. Available categories are: + #{ categories }. Got: #{ category }") + unless typeof order == 'number' + throw new Error("VimFx: Command order must be a number. Got: #{ order }") + unless typeof fn == 'function' + throw new Error("VimFx: Commands need a function to run. Got: #{ fn }") + + pref = "#{ defaults.BRANCH }custom.mode.#{ mode }.#{ name }" + prefs.root.default.set(pref, '') + vimfx.modes[mode].commands[name] = { + pref, category, order, run: fn, description: -> description + } + vimfx.createKeyTrees() + + addOptionOverrides: (rules...) -> + unless vimfx.optionOverrides + vimfx.optionOverrides = [] + vimfx.options = new Proxy(vimfx.options, { + get: (options, pref) -> + location = vimfx.getCurrentLocation() + # If there is no current location available yet, simply ignore the + # overrides. + if location + overrides = getOverrides(vimfx.optionOverrides, location) + return overrides?[pref] ? options[pref] + }) + vimfx.optionOverrides.push(rules...) + + addKeyOverrides: (rules...) -> + unless vimfx.keyOverrides + vimfx.keyOverrides = [] + vimfx.options.keyValidator = (keyStr, mode) -> + location = vimfx.getCurrentLocation() + # If there is no current location available yet, simply ignore the + # overrides. + if location + overrides = getOverrides(vimfx.keyOverrides, location, mode) + return keyStr not in (overrides ? []) + vimfx.keyOverrides.push(rules...) + + on: vimfx.on.bind(vimfx) + refresh: vimfx.createKeyTrees.bind(vimfx) + modes: vimfx.modes + categories: vimfx.options.categories + +getOverrides = (rules, args...) -> + for [match, overrides] in rules + return overrides if match(args...) + return null + +module.exports = createAPI diff --git a/extension/lib/button.coffee b/extension/lib/button.coffee index 110d883..c468e7f 100644 --- a/extension/lib/button.coffee +++ b/extension/lib/button.coffee @@ -1,6 +1,5 @@ ### -# Copyright Anton Khodakivskiy 2012, 2013, 2014. -# Copyright Simon Lydell 2013, 2014. +# Copyright Simon Lydell 2015. # # This file is part of VimFx. # @@ -18,234 +17,44 @@ # along with VimFx. If not, see . ### -{ getPref -, setPref } = require('./prefs') -{ injectHelp } = require('./help') -utils = require('./utils') -_ = require('./l10n') +help = require('./help') +translate = require('./l10n') -BUTTON_ID = 'vimfx-toolbar-button' -MENUPOPUP_ID = 'vimfx-menupopup' -MENU_ITEM_PREF = 'vimfx-menu-item-preferences' -MENU_ITEM_HELP = 'vimfx-menu-item-help' -TEXTBOX_BLACKLIST_ID = 'vimfx-textbox-blacklist-id' -BUTTON_BLACKLIST_ID = 'vimfx-button-blacklist-id' +BUTTON_ID = 'VimFxButton' -$ = (document, selector) -> document.getElementById(selector) -$$ = (document, selector) -> document.querySelectorAll(selector) - -positions = {} - -setButtonInstallPosition = (toolbarId, beforeId) -> - positions[BUTTON_ID] = {toolbarId, beforeId} - -addToolbarButton = (vimfx, window) -> - document = window.document - win = document.querySelector('window') - - button = createButton(vimfx, window) - - # Namespace to put the VimFx state on, for example. - button.VimFx = {} - - restorePosition(document, button) - - if tabWindow = utils.getCurrentTabWindow(window) - blacklisted = utils.isBlacklisted(tabWindow.location.href) - updateToolbarButton({rootWindow: window, state: {blacklisted}}) - - vimfx.on('modechange', updateToolbarButton) - vimfx.on('bucket.get', updateToolbarButton) - - module.onShutdown(-> - button.remove() - $(document, 'navigator-toolbox').palette.removeChild(button) - ) - -createButton = (vimfx, window) -> - document = window.document - - button = utils.createElement(document, 'toolbarbutton', { +injectButton = (vimfx, window) -> + cui = window.CustomizableUI + button = null + cui.createWidget({ id: BUTTON_ID - type: 'menu-button' + defaultArea: cui.AREA_NAVBAR label: 'VimFx' - class: 'toolbarbutton-1' - }) - - menupopup = createMenupopup(vimfx, window, button) - - onButtonCommand = (event) -> - switch - when button.VimFx.blacklisted - menupopup.openPopup(button, 'after_start') - when button.VimFx.insertMode - return unless currentTabWindow = utils.getEventCurrentTabWindow(event) - return unless vim = vimfx.vimBucket.get(currentTabWindow) - vim.enterMode('normal') + tooltiptext: translate('button.tooltip.normal') + onCommand: -> + mode = button.getAttribute('vimfx-mode') + if mode == 'normal' + help.injectHelp(window, vimfx) else - return unless currentTabWindow = utils.getEventCurrentTabWindow(event) - return unless vim = vimfx.vimBucket.get(currentTabWindow) - setPref('disabled', not getPref('disabled')) - updateToolbarButton(vim) - - event.stopPropagation() - - button.addEventListener('command', onButtonCommand, false) - - return button - -createMenupopup = (vimfx, window, button) -> - document = window.document - - blacklistTextbox = utils.createElement(document, 'textbox', { - id: TEXTBOX_BLACKLIST_ID - }) - blacklistButton = utils.createElement(document, 'toolbarbutton', { - id: BUTTON_BLACKLIST_ID - class: 'toolbarbutton-1' - }) - blacklistControls = utils.createElement(document, 'hbox') - blacklistControls.appendChild(blacklistTextbox) - blacklistControls.appendChild(blacklistButton) - - itemPreferences = utils.createElement(document, 'menuitem', { - id: MENU_ITEM_PREF - label: _('item_preferences') - }) - - itemHelp = utils.createElement(document, 'menuitem', { - id: MENU_ITEM_HELP - label: _('help_title') - }) - - menupopup = utils.createElement(document, 'menupopup', { - id: MENUPOPUP_ID - ignorekeys: true + vimfx.currentVim.enterMode('normal') + onCreated: (node) -> + button = node + button.setAttribute('vimfx-mode', 'normal') + vimfx.on('modeChange', updateButton.bind(null, button)) + vimfx.on('currentVimChange', updateButton.bind(null, button)) }) - menupopup.appendChild(blacklistControls) - menupopup.appendChild(itemPreferences) - menupopup.appendChild(itemHelp) + module.onShutdown(cui.destroyWidget.bind(cui, BUTTON_ID)) - onPopupShowing = (event) -> - return unless tabWindow = utils.getCurrentTabWindow(window) - - if button.VimFx.blacklisted - matchingRules = utils.getMatchingBlacklistRules(tabWindow.location.href) - blacklistTextbox.value = matchingRules.join(', ') - blacklistTextbox.setAttribute('readonly', true) - blacklistButton.setAttribute('tooltiptext', - _('item_blacklist_button_inverse_tooltip')) - blacklistButton.style.listStyleImage = iconUrl('blacklist_inverse') - else - blacklistTextbox.value = - # In `about:` pages, the `host` property is an empty string. Fall back - # to the whole URL. - if tabWindow.location.host != '' - "*#{ tabWindow.location.host }*" - else - tabWindow.location.href - blacklistTextbox.removeAttribute('readonly') - blacklistButton.setAttribute('tooltiptext', - _('item_blacklist_button_tooltip')) - blacklistButton.style.listStyleImage = iconUrl('blacklist') - - onBlacklistButtonCommand = (event) -> - return unless tabWindow = utils.getCurrentTabWindow(window) - - if button.VimFx.blacklisted - utils.updateBlacklist({remove: blacklistTextbox.value}) - else - utils.updateBlacklist({add: blacklistTextbox.value}) - - menupopup.hidePopup() - - tabWindow.location.reload(false) - - event.stopPropagation() - - onPreferencesCommand = (event) -> - id = encodeURIComponent(vimfx.ID) - window.BrowserOpenAddonsMgr("addons://detail/#{ id }/preferences") - - event.stopPropagation() - - onHelpCommand = (event) -> - if tabWindow = utils.getCurrentTabWindow(window) - injectHelp(tabWindow.document, vimfx) - - event.stopPropagation() - - menupopup.addEventListener('popupshowing', onPopupShowing, false) - blacklistButton.addEventListener('command', onBlacklistButtonCommand, false) - itemPreferences.addEventListener('command', onPreferencesCommand, false) - itemHelp.addEventListener('command', onHelpCommand, false) - - button.appendChild(menupopup) - return menupopup - -restorePosition = (document, button) -> - $(document, 'navigator-toolbox').palette.appendChild(button) - - for tb in $$(document, 'toolbar') - currentset = tb.getAttribute('currentset').split(',') - idx = currentset.indexOf(button.id) - if idx != -1 - toolbar = tb - break - - # Saved position not found, using the default one, after persisting it. - if not toolbar and button.id of positions - { toolbarId, beforeId } = positions[button.id] - if toolbar = $(document, toolbarId) - [ currentset, idx ] = persist(document, toolbar, button.id, beforeId) - - if toolbar and idx != -1 - # Inserting the button before the first item in `currentset` - # after `idx` that is present in the document. - for id in currentset[idx + 1..] - if before = $(document, id) - toolbar.insertItem(button.id, before) - return - - toolbar.insertItem(button.id) - -persist = (document, toolbar, buttonId, beforeId) -> - currentset = toolbar.currentSet.split(',') - idx = if beforeId then currentset.indexOf(beforeId) else -1 - if idx != -1 - currentset.splice(idx, 0, buttonId) - else - currentset.push(buttonId) - - toolbar.setAttribute('currentset', currentset.join(',')) - document.persist(toolbar.id, 'currentset') - return [currentset, idx] - -updateToolbarButton = (vim) -> - window = vim.rootWindow - return unless button = $(window.document, BUTTON_ID) - - button.VimFx.disabled = getPref('disabled') - button.VimFx.blacklisted = vim.state.blacklisted - button.VimFx.insertMode = (vim.mode == 'insert') - - [ icon, tooltip ] = switch - when button.VimFx.disabled - ['grey', _('button_tooltip_disabled')] - when button.VimFx.blacklisted - ['red', _('button_tooltip_blacklisted')] - when button.VimFx.insertMode - keys = require('./modes')['insert'].commands['exit'].keys().join(', ') - ['grey', _('button_tooltip_insertMode', keys)] +updateButton = (button, { mode }) -> + button.setAttribute('vimfx-mode', mode) + tooltip = + if mode == 'normal' + translate('button.tooltip.normal') else - ['normal', _('button_tooltip_enabled')] - - button.style.listStyleImage = iconUrl(icon) + translate('button.tooltip.other_mode', translate("mode.#{ mode }"), + translate('mode.normal')) button.setAttribute('tooltiptext', tooltip) -iconUrl = (kind) -> - url = utils.getResourceURI("resources/icon16-#{ kind }.png").spec - return "url(#{ url })" - -exports.setButtonInstallPosition = setButtonInstallPosition -exports.addToolbarButton = addToolbarButton +module.exports = { + injectButton + BUTTON_ID +} diff --git a/extension/lib/command.coffee b/extension/lib/command.coffee deleted file mode 100644 index f2bca33..0000000 --- a/extension/lib/command.coffee +++ /dev/null @@ -1,65 +0,0 @@ -### -# Copyright Anton Khodakivskiy 2013. -# Copyright Simon Lydell 2013, 2014, 2015. -# -# This file is part of VimFx. -# -# VimFx is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# VimFx is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with VimFx. If not, see . -### - -notation = require('vim-like-key-notation') -utils = require('./utils') -_ = require('./l10n') -{ getPref -, setPref } = require('./prefs') - -class Command - constructor: (@group, @name, @func) -> - unless @name.startsWith('mode.') - @name = "mode.normal.#{ @name }" - keyString = getPref(@name).trim() - @keyValues = - if keyString == '' - [] - else - keyString.split(/\s+/).map(notation.parseSequence) - - keys: (value) -> - if value == undefined - return @keyValues - else - @keyValues = value - setPref(@name, value.map((key) -> key.join('')).join(' ')) - - help: -> _(@name) - - match: (str, numbers = null) -> - for key in @keys() - key = utils.normalizedKey(key) - if key.startsWith(str) - # When letter 0 follows after a number, it is considered as number 0 - # instead of a valid command. - continue if key == '0' and numbers - count = if numbers then Number(numbers[numbers.length - 1]) else null - return {match: true, exact: (key == str), command: this, count} - - @searchForMatchingCommand: (commands, keys) -> - for index in [0...keys.length] by 1 - str = keys[index..].join('') - numbers = keys[0..index].join('').match(/[1-9]\d*/g) - for command in commands - return match if match = command.match(str, numbers) - return {match: false} - -module.exports = Command diff --git a/extension/lib/commands.coffee b/extension/lib/commands.coffee index 6dfa90b..1404595 100644 --- a/extension/lib/commands.coffee +++ b/extension/lib/commands.coffee @@ -19,26 +19,25 @@ # along with VimFx. If not, see . ### -notation = require('vim-like-key-notation') -Command = require('./command') +help = require('./help') { Marker } = require('./marker') utils = require('./utils') -help = require('./help') -{ getPref -, getFirefoxPref -, withFirefoxPrefAs } = require('./prefs') { isProperLink, isTextInputElement, isContentEditable } = utils { classes: Cc, interfaces: Ci, utils: Cu } = Components -XULDocument = Ci.nsIDOMXULDocument +XULDocument = Ci.nsIDOMXULDocument + +commands = {} + -command_focus_location_bar = (vim) -> + +commands.focus_location_bar = ({ vim }) -> # This function works even if the Address Bar has been removed. vim.rootWindow.focusAndSelectUrlBar() -command_focus_search_bar = (vim) -> +commands.focus_search_bar = ({ vim }) -> # The `.webSearch()` method opens a search engine in a tab if the Search Bar # has been removed. Therefore we first check if it exists. if vim.rootWindow.BrowserSearch.searchBar @@ -52,71 +51,76 @@ helper_paste = (vim) -> { postData } = submission return {url, postData} -command_paste_and_go = (vim) -> +commands.paste_and_go = ({ vim }) -> { url, postData } = helper_paste(vim) vim.rootWindow.gBrowser.loadURIWithFlags(url, {postData}) -command_paste_and_go_in_tab = (vim) -> +commands.paste_and_go_in_tab = ({ vim }) -> { url, postData } = helper_paste(vim) vim.rootWindow.gBrowser.selectedTab = vim.rootWindow.gBrowser.addTab(url, {postData}) -command_copy_current_url = (vim) -> +commands.copy_current_url = ({ vim }) -> utils.writeToClipboard(vim.window.location.href) # Go up one level in the URL hierarchy. -command_go_up_path = (vim, event, count = 1) -> +commands.go_up_path = ({ vim, count }) -> { pathname } = vim.window.location vim.window.location.pathname = pathname.replace( - /// (?: /[^/]+ ){1,#{ count }} /?$ ///, '' + /// (?: /[^/]+ ){1,#{ count ? 1 }} /?$ ///, '' ) # Go up to root of the URL hierarchy. -command_go_to_root = (vim) -> +commands.go_to_root = ({ vim }) -> vim.window.location.href = vim.window.location.origin -command_go_home = (vim) -> +commands.go_home = ({ vim }) -> vim.rootWindow.BrowserHome() -helper_go_history = (num, vim, event, count = 1) -> +helper_go_history = (num, { vim, count }) -> { index } = vim.rootWindow.getWebNavigation().sessionHistory { history } = vim.window - num *= count + num *= count ? 1 num = Math.max(num, -index) num = Math.min(num, history.length - 1 - index) return if num == 0 history.go(num) -command_history_back = helper_go_history.bind(undefined, -1) +commands.history_back = helper_go_history.bind(null, -1) -command_history_forward = helper_go_history.bind(undefined, +1) +commands.history_forward = helper_go_history.bind(null, +1) -command_reload = (vim) -> +commands.reload = ({ vim }) -> vim.rootWindow.BrowserReload() -command_reload_force = (vim) -> +commands.reload_force = ({ vim }) -> vim.rootWindow.BrowserReloadSkipCache() -command_reload_all = (vim) -> +commands.reload_all = ({ vim }) -> vim.rootWindow.gBrowser.reloadAllTabs() -command_reload_all_force = (vim) -> +commands.reload_all_force = ({ vim }) -> for tab in vim.rootWindow.gBrowser.visibleTabs window = tab.linkedBrowser.contentWindow window.location.reload(true) + return -command_stop = (vim) -> +commands.stop = ({ vim }) -> vim.window.stop() -command_stop_all = (vim) -> +commands.stop_all = ({ vim }) -> for tab in vim.rootWindow.gBrowser.visibleTabs window = tab.linkedBrowser.contentWindow window.stop() + return + + axisMap = x: ['left', 'scrollLeftMax', 'clientWidth', 'horizontalScrollDistance', 5] y: ['top', 'scrollTopMax', 'clientHeight', 'verticalScrollDistance', 20] -helper_scroll = (method, type, axis, amount, vim, event, count = 1) -> + +helper_scroll = (method, type, axis, amount, { vim, event, count }) -> frameDocument = event.target.ownerDocument element = if vim.state.scrollableElements.has(event.target) @@ -131,20 +135,20 @@ helper_scroll = (method, type, axis, amount, vim, event, count = 1) -> else unit = switch type when 'lines' - getFirefoxPref("toolkit.scrollbox.#{ distance }") * lineAmount + prefs.root.get("toolkit.scrollbox.#{ distance }") * lineAmount when 'pages' element[dimension] - amount *= unit * count + amount *= unit * (count ? 1) options = {} options[direction] = amount - if getFirefoxPref('general.smoothScroll') and - getFirefoxPref("general.smoothScroll.#{ type }") + if prefs.root.get('general.smoothScroll') and + prefs.root.get("general.smoothScroll.#{ type }") options.behavior = 'smooth' - withFirefoxPrefAs( + prefs.root.tmp( 'layout.css.scroll-behavior.spring-constant', - getPref("smoothScroll.#{ type }.spring-constant"), + vim.parent.options["smoothScroll.#{ type }.spring-constant"], -> element[method](options) # When scrolling the whole page, the body sometimes needs to be scrolled @@ -153,35 +157,27 @@ helper_scroll = (method, type, axis, amount, vim, event, count = 1) -> frameDocument.body?[method](options) ) -command_scroll_left = - helper_scroll.bind(undefined, 'scrollBy', 'lines', 'x', -1) -command_scroll_right = - helper_scroll.bind(undefined, 'scrollBy', 'lines', 'x', +1) -command_scroll_down = - helper_scroll.bind(undefined, 'scrollBy', 'lines', 'y', +1) -command_scroll_up = - helper_scroll.bind(undefined, 'scrollBy', 'lines', 'y', -1) -command_scroll_page_down = - helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', +1) -command_scroll_page_up = - helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', -1) -command_scroll_half_page_down = - helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', +0.5) -command_scroll_half_page_up = - helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', -0.5) -command_scroll_to_top = - helper_scroll.bind(undefined, 'scrollTo', 'other', 'y', 0) -command_scroll_to_bottom = - helper_scroll.bind(undefined, 'scrollTo', 'other', 'y', Infinity) -command_scroll_to_left = - helper_scroll.bind(undefined, 'scrollTo', 'other', 'x', 0) -command_scroll_to_right = - helper_scroll.bind(undefined, 'scrollTo', 'other', 'x', Infinity) - -command_tab_new = (vim) -> +scroll = Function::bind.bind(helper_scroll, null) + +commands.scroll_left = scroll('scrollBy', 'lines', 'x', -1) +commands.scroll_right = scroll('scrollBy', 'lines', 'x', +1) +commands.scroll_down = scroll('scrollBy', 'lines', 'y', +1) +commands.scroll_up = scroll('scrollBy', 'lines', 'y', -1) +commands.scroll_page_down = scroll('scrollBy', 'pages', 'y', +1) +commands.scroll_page_up = scroll('scrollBy', 'pages', 'y', -1) +commands.scroll_half_page_down = scroll('scrollBy', 'pages', 'y', +0.5) +commands.scroll_half_page_up = scroll('scrollBy', 'pages', 'y', -0.5) +commands.scroll_to_top = scroll('scrollTo', 'other', 'y', 0) +commands.scroll_to_bottom = scroll('scrollTo', 'other', 'y', Infinity) +commands.scroll_to_left = scroll('scrollTo', 'other', 'x', 0) +commands.scroll_to_right = scroll('scrollTo', 'other', 'x', Infinity) + + + +commands.tab_new = ({ vim }) -> vim.rootWindow.BrowserOpenTab() -command_tab_duplicate = (vim) -> +commands.tab_duplicate = ({ vim }) -> { gBrowser } = vim.rootWindow gBrowser.duplicateTab(gBrowser.selectedTab) @@ -202,20 +198,20 @@ absoluteTabIndex = (relativeIndex, gBrowser) -> return absoluteIndex -helper_switch_tab = (direction, vim, event, count = 1) -> +helper_switch_tab = (direction, { vim, count }) -> { gBrowser } = vim.rootWindow - gBrowser.selectTabAtIndex(absoluteTabIndex(direction * count, gBrowser)) + gBrowser.selectTabAtIndex(absoluteTabIndex(direction * (count ? 1), gBrowser)) -command_tab_select_previous = helper_switch_tab.bind(undefined, -1) +commands.tab_select_previous = helper_switch_tab.bind(null, -1) -command_tab_select_next = helper_switch_tab.bind(undefined, +1) +commands.tab_select_next = helper_switch_tab.bind(null, +1) -helper_move_tab = (direction, vim, event, count = 1) -> +helper_move_tab = (direction, { vim, count }) -> { gBrowser } = vim.rootWindow { selectedTab } = gBrowser { pinned } = selectedTab - index = absoluteTabIndex(direction * count, gBrowser) + index = absoluteTabIndex(direction * (count ? 1), gBrowser) if index < gBrowser._numPinnedTabs gBrowser.pinTab(selectedTab) unless pinned @@ -224,46 +220,48 @@ helper_move_tab = (direction, vim, event, count = 1) -> gBrowser.moveTabTo(selectedTab, index) -command_tab_move_backward = helper_move_tab.bind(undefined, -1) +commands.tab_move_backward = helper_move_tab.bind(null, -1) -command_tab_move_forward = helper_move_tab.bind(undefined, +1) +commands.tab_move_forward = helper_move_tab.bind(null, +1) -command_tab_select_first = (vim) -> +commands.tab_select_first = ({ vim }) -> vim.rootWindow.gBrowser.selectTabAtIndex(0) -command_tab_select_first_non_pinned = (vim) -> +commands.tab_select_first_non_pinned = ({ vim }) -> firstNonPinned = vim.rootWindow.gBrowser._numPinnedTabs vim.rootWindow.gBrowser.selectTabAtIndex(firstNonPinned) -command_tab_select_last = (vim) -> +commands.tab_select_last = ({ vim }) -> vim.rootWindow.gBrowser.selectTabAtIndex(-1) -command_tab_toggle_pinned = (vim) -> +commands.tab_toggle_pinned = ({ vim }) -> currentTab = vim.rootWindow.gBrowser.selectedTab - if currentTab.pinned vim.rootWindow.gBrowser.unpinTab(currentTab) else vim.rootWindow.gBrowser.pinTab(currentTab) -command_tab_close = (vim, event, count = 1) -> +commands.tab_close = ({ vim, count }) -> { gBrowser } = vim.rootWindow return if gBrowser.selectedTab.pinned currentIndex = gBrowser.visibleTabs.indexOf(gBrowser.selectedTab) - for tab in gBrowser.visibleTabs[currentIndex...(currentIndex + count)] + for tab in gBrowser.visibleTabs[currentIndex...(currentIndex + (count ? 1))] gBrowser.removeTab(tab) + return -command_tab_restore = (vim, event, count = 1) -> - vim.rootWindow.undoCloseTab() for [1..count] +commands.tab_restore = ({ vim, count }) -> + vim.rootWindow.undoCloseTab() for [1..count ? 1] by 1 -command_tab_close_to_end = (vim) -> +commands.tab_close_to_end = ({ vim }) -> { gBrowser } = vim.rootWindow gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab) -command_tab_close_other = (vim) -> +commands.tab_close_other = ({ vim }) -> { gBrowser } = vim.rootWindow gBrowser.removeAllTabsBut(gBrowser.selectedTab) + + # Combine links with the same href. combine = (hrefs, marker) -> if marker.type == 'link' @@ -278,7 +276,8 @@ combine = (hrefs, marker) -> return marker # Follow links, focus text inputs and click buttons with hint markers. -command_follow = (vim, event, count = 1) -> +commands.follow = ({ vim, count }) -> + count ?= 1 hrefs = {} filter = (element, getElementShape) -> document = element.ownerDocument @@ -349,7 +348,7 @@ command_follow = (vim, event, count = 1) -> relatedToCurrent: true }) else - if element.target == '_blank' + if element.target == '_blank' and vim.parent.options.prevent_target_blank targetReset = element.target element.target = '' utils.simulateClick(element) @@ -360,7 +359,8 @@ command_follow = (vim, event, count = 1) -> vim.enterMode('hints', filter, callback) # Follow links in a new background tab with hint markers. -command_follow_in_tab = (vim, event, count = 1, inBackground = true) -> +commands.follow_in_tab = ({ vim, count }, inBackground = true) -> + count ?= 1 hrefs = {} filter = (element, getElementShape) -> return unless isProperLink(element) @@ -379,15 +379,16 @@ command_follow_in_tab = (vim, event, count = 1, inBackground = true) -> vim.enterMode('hints', filter, callback) # Follow links in a new foreground tab with hint markers. -command_follow_in_focused_tab = (vim, event, count = 1) -> - command_follow_in_tab(vim, event, count, false) +commands.follow_in_focused_tab = (args) -> + commands.follow_in_tab(args, false) # Like command_follow but multiple times. -command_follow_multiple = (vim, event) -> - command_follow(vim, event, Infinity) +commands.follow_multiple = (args) -> + args.count = Infinity + commands.follow(args) # Copy the URL or text of a markable element to the system clipboard. -command_follow_copy = (vim) -> +commands.follow_copy = ({ vim }) -> hrefs = {} filter = (element, getElementShape) -> type = switch @@ -409,7 +410,7 @@ command_follow_copy = (vim) -> vim.enterMode('hints', filter, callback) # Focus element with hint markers. -command_follow_focus = (vim) -> +commands.follow_focus = ({ vim }) -> filter = (element, getElementShape) -> type = switch when element.tabIndex > -1 @@ -428,18 +429,11 @@ command_follow_focus = (vim) -> vim.enterMode('hints', filter, callback) -# Search for the prev/next patterns in the following attributes of the element. -# `rel` should be kept as the first attribute, since the standard way of marking -# up prev/next links (`rel="prev"` and `rel="next"`) should be favored. Even -# though some of these attributes only allow a fixed set of keywords, we -# pattern-match them anyways since lots of sites don’t follow the spec and use -# the attributes arbitrarily. -attrs = ['rel', 'role', 'data-tooltip', 'aria-label'] -helper_follow_pattern = (type, vim) -> +helper_follow_pattern = (type, { vim }) -> { document } = vim.window # If there’s a `` element we use that. - for link in document.head.getElementsByTagName('link') + for link in document.head?.getElementsByTagName('link') # Also support `rel=previous`, just like Google. if type == link.rel.toLowerCase().replace(/^previous$/, 'prev') vim.rootWindow.gBrowser.loadURI(link.href) @@ -447,17 +441,43 @@ helper_follow_pattern = (type, vim) -> # Otherwise we look for a link or button on the page that seems to go to the # previous or next page. - candidates = document.querySelectorAll('a, button') - patterns = utils.splitListString(getPref("#{ type }_patterns")) - if matchingLink = utils.getBestPatternMatch(patterns, attrs, candidates) - utils.simulateClick(matchingLink) + candidates = document.querySelectorAll(vim.parent.options.pattern_selector) + + # Note: Earlier patterns should be favored. + patterns = vim.parent.options["#{ type }_patterns"] + + # Search for the prev/next patterns in the following attributes of the + # element. `rel` should be kept as the first attribute, since the standard way + # of marking up prev/next links (`rel="prev"` and `rel="next"`) should be + # favored. Even though some of these attributes only allow a fixed set of + # keywords, we pattern-match them anyways since lots of sites don’t follow the + # spec and use the attributes arbitrarily. + attrs = vim.parent.options.pattern_attrs + + matchingLink = do -> + # Helper function that matches a string against all the patterns. + matches = (text) -> patterns.some((regex) -> regex.test(text)) + + # First search in attributes (favoring earlier attributes) as it's likely + # that they are more specific than text contexts. + for attr in attrs + for element in candidates + return element if matches(element.getAttribute(attr)) + + # Then search in element contents. + for element in candidates + return element if matches(element.textContent) + + return null -command_follow_previous = helper_follow_pattern.bind(undefined, 'prev') + utils.simulateClick(matchingLink) if matchingLink -command_follow_next = helper_follow_pattern.bind(undefined, 'next') +commands.follow_previous = helper_follow_pattern.bind(null, 'prev') + +commands.follow_next = helper_follow_pattern.bind(null, 'next') # Focus last focused or first text input and enter text input mode. -command_text_input = (vim, event, count) -> +commands.text_input = ({ vim, count }) -> { lastFocusedTextInput } = vim.state inputs = Array.filter( vim.window.document.querySelectorAll('input, textarea'), (element) -> @@ -467,14 +487,21 @@ command_text_input = (vim, event, count) -> inputs.push(lastFocusedTextInput) return unless inputs.length > 0 inputs.sort((a, b) -> a.tabIndex - b.tabIndex) - if count == null and lastFocusedTextInput - count = inputs.indexOf(lastFocusedTextInput) + 1 - inputs[count - 1].select() - vim.enterMode('text-input', inputs) + unless count? + count = + if lastFocusedTextInput + inputs.indexOf(lastFocusedTextInput) + 1 + else + 1 + index = Math.min(count, inputs.length) - 1 + inputs[index].select() + vim.enterMode('text_input', inputs) + + findStorage = {lastSearchString: ''} -helper_find = (highlight, vim) -> +helper_find = (highlight, { vim }) -> findBar = vim.rootWindow.gBrowser.getFindBar() findBar.onFindCommand() @@ -486,44 +513,46 @@ helper_find = (highlight, vim) -> highlightButton.click() # Open the find bar, making sure that hightlighting is off. -command_find = helper_find.bind(undefined, false) +commands.find = helper_find.bind(null, false) # Open the find bar, making sure that hightlighting is on. -command_find_highlight_all = helper_find.bind(undefined, true) +commands.find_highlight_all = helper_find.bind(null, true) -helper_find_again = (direction, vim) -> +helper_find_again = (direction, { vim }) -> findBar = vim.rootWindow.gBrowser.getFindBar() if findStorage.lastSearchString.length > 0 findBar._findField.value = findStorage.lastSearchString findBar.onFindAgainCommand(direction) -command_find_next = helper_find_again.bind(undefined, false) +commands.find_next = helper_find_again.bind(null, false) -command_find_previous = helper_find_again.bind(undefined, true) +commands.find_previous = helper_find_again.bind(null, true) -command_enter_mode_insert = (vim) -> + + +commands.enter_mode_insert = ({ vim }) -> vim.enterMode('insert') # Quote next keypress (pass it through to the page). -command_quote = (vim, event, count = 1) -> - vim.enterMode('insert', count) +commands.quote = ({ vim, count }) -> + vim.enterMode('insert', count ? 1) # Display the Help Dialog. -command_help = (vim) -> - help.injectHelp(vim.window.document, vim.parent) +commands.help = ({ vim }) -> + help.injectHelp(vim.rootWindow, vim.parent) # Open and focus the Developer Toolbar. -command_dev = (vim) -> +commands.dev = ({ vim }) -> vim.rootWindow.DeveloperToolbar.show(true) # `true` to focus. -command_esc = (vim, event) -> +commands.esc = ({ vim, event }) -> utils.blurActiveElement(vim.window) # Blur active XUL control. callback = -> event.originalTarget?.ownerDocument?.activeElement?.blur() vim.window.setTimeout(callback, 0) - help.removeHelp(vim.window.document) + help.removeHelp(vim.rootWindow) vim.rootWindow.DeveloperToolbar.hide() @@ -538,78 +567,8 @@ command_esc = (vim, event) -> document.mozCancelFullScreen() -# coffeelint: disable=max_line_length -commands = [ - new Command('location', 'focus_location_bar', command_focus_location_bar) - new Command('location', 'focus_search_bar', command_focus_search_bar) - new Command('location', 'paste_and_go', command_paste_and_go) - new Command('location', 'paste_and_go_in_tab', command_paste_and_go_in_tab) - new Command('location', 'copy_current_url', command_copy_current_url) - new Command('location', 'go_up_path', command_go_up_path) - new Command('location', 'go_to_root', command_go_to_root) - new Command('location', 'go_home', command_go_home) - new Command('location', 'history_back', command_history_back) - new Command('location', 'history_forward', command_history_forward) - new Command('location', 'reload', command_reload) - new Command('location', 'reload_force', command_reload_force) - new Command('location', 'reload_all', command_reload_all) - new Command('location', 'reload_all_force', command_reload_all_force) - new Command('location', 'stop', command_stop) - new Command('location', 'stop_all', command_stop_all) - - new Command('scrolling', 'scroll_left', command_scroll_left) - new Command('scrolling', 'scroll_right', command_scroll_right ) - new Command('scrolling', 'scroll_down', command_scroll_down) - new Command('scrolling', 'scroll_up', command_scroll_up) - new Command('scrolling', 'scroll_page_down', command_scroll_page_down) - new Command('scrolling', 'scroll_page_up', command_scroll_page_up) - new Command('scrolling', 'scroll_half_page_down', command_scroll_half_page_down) - new Command('scrolling', 'scroll_half_page_up', command_scroll_half_page_up) - new Command('scrolling', 'scroll_to_top', command_scroll_to_top ) - new Command('scrolling', 'scroll_to_bottom', command_scroll_to_bottom) - new Command('scrolling', 'scroll_to_left', command_scroll_to_left ) - new Command('scrolling', 'scroll_to_right', command_scroll_to_right) - - new Command('tabs', 'tab_new', command_tab_new) - new Command('tabs', 'tab_duplicate', command_tab_duplicate) - new Command('tabs', 'tab_select_previous', command_tab_select_previous) - new Command('tabs', 'tab_select_next', command_tab_select_next) - new Command('tabs', 'tab_move_backward', command_tab_move_backward) - new Command('tabs', 'tab_move_forward', command_tab_move_forward) - new Command('tabs', 'tab_select_first', command_tab_select_first) - new Command('tabs', 'tab_select_first_non_pinned', command_tab_select_first_non_pinned) - new Command('tabs', 'tab_select_last', command_tab_select_last) - new Command('tabs', 'tab_toggle_pinned', command_tab_toggle_pinned) - new Command('tabs', 'tab_close', command_tab_close) - new Command('tabs', 'tab_restore', command_tab_restore) - new Command('tabs', 'tab_close_to_end', command_tab_close_to_end) - new Command('tabs', 'tab_close_other', command_tab_close_other) - - new Command('browsing', 'follow', command_follow) - new Command('browsing', 'follow_in_tab', command_follow_in_tab) - new Command('browsing', 'follow_in_focused_tab', command_follow_in_focused_tab) - new Command('browsing', 'follow_multiple', command_follow_multiple) - new Command('browsing', 'follow_copy', command_follow_copy) - new Command('browsing', 'follow_focus', command_follow_focus) - new Command('browsing', 'follow_previous', command_follow_previous) - new Command('browsing', 'follow_next', command_follow_next) - new Command('browsing', 'text_input', command_text_input) - - new Command('find', 'find', command_find) - new Command('find', 'find_highlight_all', command_find_highlight_all) - new Command('find', 'find_next', command_find_next) - new Command('find', 'find_previous', command_find_previous) - - new Command('misc', 'enter_mode_insert', command_enter_mode_insert) - new Command('misc', 'quote', command_quote) - new Command('misc', 'help', command_help) - new Command('misc', 'dev', command_dev) - - escapeCommand = - new Command('misc', 'esc', command_esc) -] -# coffeelint: enable=max_line_length - -exports.commands = commands -exports.escapeCommand = escapeCommand -exports.findStorage = findStorage + +module.exports = { + commands + findStorage +} diff --git a/extension/lib/defaults.coffee b/extension/lib/defaults.coffee index 790d351..de6c7bd 100644 --- a/extension/lib/defaults.coffee +++ b/extension/lib/defaults.coffee @@ -17,122 +17,181 @@ # along with VimFx. If not, see . ### -notation = require('vim-like-key-notation') - - - shortcuts = 'normal': - # Location - 'o': 'focus_location_bar' - 'O': 'focus_search_bar' - 'p': 'paste_and_go' - 'P': 'paste_and_go_in_tab' - 'yy': 'copy_current_url' - 'gu': 'go_up_path' - 'gU': 'go_to_root' - 'gh': 'go_home' - 'H': 'history_back' - 'L': 'history_forward' - 'r': 'reload' - 'R': 'reload_force' - 'ar': 'reload_all' - 'aR': 'reload_all_force' - 's': 'stop' - 'as': 'stop_all' - - # Scrolling - 'h': 'scroll_left' - 'l': 'scroll_right' - 'j': 'scroll_down' - 'k': 'scroll_up' - '': 'scroll_page_down' - '': 'scroll_page_up' - 'd': 'scroll_half_page_down' - 'u': 'scroll_half_page_up' - 'gg': 'scroll_to_top' - 'G': 'scroll_to_bottom' - '0 ^': 'scroll_to_left' - '$': 'scroll_to_right' - - # Tabs - 't': 'tab_new' - 'yt': 'tab_duplicate' - 'J gT': 'tab_select_previous' - 'K gt': 'tab_select_next' - 'gJ': 'tab_move_backward' - 'gK': 'tab_move_forward' - 'gH g0': 'tab_select_first' - 'g^': 'tab_select_first_non_pinned' - 'gL g$': 'tab_select_last' - 'gp': 'tab_toggle_pinned' - 'x': 'tab_close' - 'X': 'tab_restore' - 'gx$': 'tab_close_to_end' - 'gxa': 'tab_close_other' - - # Browsing - 'f': 'follow' - 'F': 'follow_in_tab' - 'gf': 'follow_in_focused_tab' - 'af': 'follow_multiple' - 'yf': 'follow_copy' - 'vf': 'follow_focus' - '[': 'follow_previous' - ']': 'follow_next' - 'gi': 'text_input' - - # Find - '/': 'find' - 'a/': 'find_highlight_all' - 'n': 'find_next' - 'N': 'find_previous' - - # Misc - 'i': 'enter_mode_insert' - 'I': 'quote' - '?': 'help' - ':': 'dev' - '': 'esc' + 'location': + 'o': 'focus_location_bar' + 'O': 'focus_search_bar' + 'p': 'paste_and_go' + 'P': 'paste_and_go_in_tab' + 'yy': 'copy_current_url' + 'gu': 'go_up_path' + 'gU': 'go_to_root' + 'gh': 'go_home' + 'H': 'history_back' + 'L': 'history_forward' + 'r': 'reload' + 'R': 'reload_force' + 'ar': 'reload_all' + 'aR': 'reload_all_force' + 's': 'stop' + 'as': 'stop_all' + + 'scrolling': + 'h': 'scroll_left' + 'l': 'scroll_right' + 'j': 'scroll_down' + 'k': 'scroll_up' + '': 'scroll_page_down' + '': 'scroll_page_up' + 'd': 'scroll_half_page_down' + 'u': 'scroll_half_page_up' + 'gg': 'scroll_to_top' + 'G': 'scroll_to_bottom' + '0 ^': 'scroll_to_left' + '$': 'scroll_to_right' + + 'tabs': + 't': 'tab_new' + 'yt': 'tab_duplicate' + 'J gT': 'tab_select_previous' + 'K gt': 'tab_select_next' + 'gJ': 'tab_move_backward' + 'gK': 'tab_move_forward' + 'g0': 'tab_select_first' + 'g^': 'tab_select_first_non_pinned' + 'g$': 'tab_select_last' + 'gp': 'tab_toggle_pinned' + 'x': 'tab_close' + 'X': 'tab_restore' + 'gx$': 'tab_close_to_end' + 'gxa': 'tab_close_other' + + 'browsing': + 'f': 'follow' + 'F': 'follow_in_tab' + 'gf': 'follow_in_focused_tab' + 'af': 'follow_multiple' + 'yf': 'follow_copy' + 'vf': 'follow_focus' + '[': 'follow_previous' + ']': 'follow_next' + 'gi': 'text_input' + + 'find': + '/': 'find' + 'a/': 'find_highlight_all' + 'n': 'find_next' + 'N': 'find_previous' + + 'misc': + 'i': 'enter_mode_insert' + 'I': 'quote' + '?': 'help' + ':': 'dev' + '': 'esc' + + 'hints': + '': + '': 'exit' + '': 'rotate_markers_forward' + '': 'rotate_markers_backward' + '': 'delete_hint_char' 'insert': - '': 'exit' + '': + '': 'exit' - 'hints': - '': 'exit' - '': 'rotate_markers_forward' - '': 'rotate_markers_backward' - '': 'delete_hint_char' + 'text_input': + '': + '': 'exit' + '': 'input_previous' + '': 'input_next' 'find': - ' ': 'exit' + '': + ' ': 'exit' options = 'hint_chars': 'fjdkslaghrueiwovncm' - 'prev_patterns': 'prev,previous,‹,«,◀,←,<<,<,back,newer' - 'next_patterns': 'next,›,»,▶,→,>>,>,more,older' + 'prev_patterns': 'prev previous ‹ « ◀ ← << < back newer' + 'next_patterns': 'next › » ▶ → >> > more older' 'black_list': '' 'prevent_autofocus': true 'ignore_keyboard_layout': false + 'timeout': 2000 advanced_options = + 'prevent_target_blank': true 'autofocus_limit': 100 + 'hints_timeout': 200 'smoothScroll.lines.spring-constant': '1000' 'smoothScroll.pages.spring-constant': '2500' 'smoothScroll.other.spring-constant': '2500' - 'translations': '{}' - - - -key_options = {} -for modeName, modeShortcuts of shortcuts - for keys, name of modeShortcuts - key_options["mode.#{ modeName }.#{ name }"] = keys - -all = Object.assign({}, options, advanced_options, key_options) - -exports.options = options -exports.advanced_options = advanced_options -exports.key_options = key_options -exports.all = all -exports.BRANCH = 'extensions.VimFx.' + 'pattern_selector': 'a, button' + 'pattern_attrs': 'rel role data-tooltip aria-label' + 'activatable_element_keys': '' + 'adjustable_element_keys': ' + ' + 'options.key.quote': '' + 'options.key.insert_default': '' + 'options.key.reset_default': '' + +parsed_options = + 'translations': {} + 'categories': {} # Will be filled in below. + + + +translate = require('./l10n') +utils = require('./utils') + +addCategory = (category, order) -> + uncategorized = (category == '') + categoryName = + if uncategorized + -> '' + else + translate.bind(null, "category.#{ category }") + parsed_options.categories[category] = { + name: categoryName + order: if uncategorized then 0 else order + } + +shortcut_prefs = {} +categoryMap = {} +mode_order = {} +command_order = {} + +createCounter = -> new utils.Counter({step: 100}) +modeCounter = createCounter() +categoryCounter = createCounter() + +for modeName, modeCategories of shortcuts + mode_order[modeName] = modeCounter.tick() + for categoryName, modeShortcuts of modeCategories + addCategory(categoryName, categoryCounter.tick()) + commandIndex = createCounter() + for shortcut, commandName of modeShortcuts + pref = "mode.#{ modeName }.#{ commandName }" + shortcut_prefs[pref] = shortcut + command_order[pref] = commandIndex.tick() + categoryMap[pref] = categoryName + +# All options, excluding shortcut customizations. +all_options = Object.assign({}, options, advanced_options, parsed_options) +# All things that are saved in Firefox’s prefs system. +all_prefs = Object.assign({}, options, advanced_options, shortcut_prefs) + +module.exports = { + options + advanced_options + parsed_options + all_options + shortcut_prefs + all_prefs + categoryMap + mode_order + command_order + BRANCH: 'extensions.VimFx.' +} diff --git a/extension/lib/events.coffee b/extension/lib/events.coffee index 402c219..d7f6b22 100644 --- a/extension/lib/events.coffee +++ b/extension/lib/events.coffee @@ -18,9 +18,8 @@ # along with VimFx. If not, see . ### -notation = require('vim-like-key-notation') -utils = require('./utils') -{ getPref } = require('./prefs') +button = require('./button') +utils = require('./utils') { interfaces: Ci } = Components @@ -28,20 +27,9 @@ HTMLDocument = Ci.nsIDOMHTMLDocument HTMLInputElement = Ci.nsIDOMHTMLInputElement # Will be set by `addEventListeners`. It’s a bit hacky, but will do for now. +vimfx = null vimBucket = null -keyStrFromEvent = (event) -> - return notation.stringify(event, { - # Check that `event.code` really is available before using the - # 'ignore_keyboard_layout' option. If not `event.key` will be used anyway, - # and the user needs to enable `event.code` support (which can be done from - # VimFx’s settings page). - ignoreKeyboardLayout: getPref('ignore_keyboard_layout') and event.code? - # Advanced setting for advanced users. Currently requires you to modifiy it - # from about:config and check the error console for errors. - translations: JSON.parse(getPref('translations')) - }) - # When a menu or panel is shown VimFx should temporarily stop processing # keyboard input, allowing accesskeys to be used. popupPassthrough = false @@ -56,14 +44,10 @@ suppressEvent = (event) -> event.preventDefault() event.stopPropagation() -# Returns the appropriate vim instance for `event`, but only if it’s okay to do -# so. VimFx must not be disabled or blacklisted. +# Returns the appropriate vim instance for `event`. getVimFromEvent = (event) -> - return if getPref('disabled') return unless window = utils.getEventCurrentTabWindow(event) return unless vim = vimBucket.get(window) - return if vim.state.blacklisted - return vim # Save the time of the last user interaction. This is used to determine whether @@ -112,21 +96,13 @@ windowsListeners = markLastInteraction(event, vim) - return unless keyStr = keyStrFromEvent(event) - suppress = vim.onInput(keyStr, event) + suppress = vim.onInput(event) suppressEvent(event) if suppress catch error console.error(utils.formatError(error)) - # Note that the below event listeners can suppress the event even in - # blacklisted sites. That's intentional. For example, if you press 'x' to - # close the current tab, it will close before keyup fires. So keyup (and - # perhaps keypress) will fire in another tab. Even if that particular tab is - # blacklisted, we must suppress the event, so that 'x' isn't sent to the page. - # The rule is simple: If the `suppress` flag is `true`, the event should be - # suppressed, no matter what. It has the highest priority. keypress: (event) -> suppressEvent(event) if suppress keyup: (event) -> suppressEvent(event) if suppress @@ -171,12 +147,12 @@ windowsListeners = # buttons which inserts a `