From ae106db94dec4cbc81f52f2012dcb1ebd5e29356 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 27 Aug 2016 17:58:44 +0200 Subject: [PATCH] Implement filtering hints by text and related changes MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This brings several more or less breaking changes: - Non-hint chars now filter hint markers by the text of their elements, using an algorithm similar to the location bar (split the search term on whitespace; all the items must occur in the search text, case insensitively, and may overlap). The matched text is highlighted on screen. If all remaining hints are the same, they are automatically activated. All of this can be turned off via prefs, though. - Since space is used to filter hint markers by element text, the already existing `` shortcut for rotating hint markers had to be changed. It is now ``. (`` remains unchanged.) - Uppercase hint chars are now allowed. This is so that people who prefer filtering by text in the first hand can use uppercase hint chars. If mixed case is used, `text-transform: uppercase;` is not applied, to avoid confusion. - Since using uppercase characters for filtering hint markers by element text and lowercase characters for hint chars (or vice versa) is now a thing, holding shift no longer lets you peek through the hint markers, because that felt like the markers blinking all the time. Instead, you now have to hold shift+control to peek through. - Hint markers are now placed immediately to left of its element's text if it would cover the text otherwise. This is because when filtering hints by text, it can be quite difficult to see what to type otherwise. This also turned out to be helpful even when only using the hints (like before). It’s nice being able to see all the link text in many cases. - Hint markers now get their `z-index` assigned based on their element's area, not their weight. It's confusing when a smaller element's hint cover the hint of a larger element. This could be the case where there are lots of small profile image links all with the same `href`. Previously, all those links got `z-index` based on their combined area. Now, their individual areas are used instead. This problem became apparent because of the above bullet point. - The hint marker(s) with the best hint are now highlighted in blue. - `` now activates the highlighted marker(s). `` and `` can be used to toggle where links open, just as when using those modifiers with the last hint char. However, these shortcuts were already taken, so the old ones had to be given new shortcuts: - "Increase count" now uses `` (instead of ``). - "Toggle complementary" now uses `` (instead of ``). - All existing hints prefs were renamed from starting with `hint_` or `hints_` to starting with `hints.`, for consistency and organization. A few new prefs starting with `hints.` were added as well. Migrations are written for this. This also unveiled a problem. If a config file tries to set an old pref and VimFx is upgraded, the config file will throw errors. This is bad user experience, so a system for allowing old names was added. However, that might mean that users never notice that they use an outdated name and never update their config files. Therefore, old names are only allowed when the config file is loaded automatically (on startup), but _not_ when it is reloaded using `gC`. The idea is that people use `gC` while working on their config file, which usually involves fixing errors. Then they could just as well fix the old pref names as well. - The options in VimFx's settings page in the Add-ons Manager have been slightly re-ordered to play better with the new options added, and to promote some very important prefs to the top. All of the above required some significant refactoring of Hints mode, that should make it more robust. Fixes #340. A big thanks to @doy who got this all started with #789. --- documentation/api.md | 14 +- documentation/commands.md | 48 ++-- documentation/config-file.md | 4 +- documentation/options.md | 320 +++++++++++++-------- documentation/questions-and-answers.md | 8 + extension/lib/api.coffee | 91 ++++-- extension/lib/commands-frame.coffee | 12 +- extension/lib/commands.coffee | 17 +- extension/lib/config.coffee | 4 +- extension/lib/defaults.coffee | 32 ++- extension/lib/events-frame.coffee | 13 + extension/lib/events.coffee | 9 +- extension/lib/help.coffee | 12 +- extension/lib/hints-mode.coffee | 100 +++++++ extension/lib/main.coffee | 10 +- extension/lib/markable-elements.coffee | 34 ++- extension/lib/marker-container.coffee | 353 +++++++++++++++++++----- extension/lib/marker.coffee | 77 ++++-- extension/lib/migrations.coffee | 14 + extension/lib/modes.coffee | 83 +++--- extension/lib/parse-prefs.coffee | 4 +- extension/lib/utils.coffee | 101 ++++++- extension/lib/viewport.coffee | 18 +- extension/lib/vimfx.coffee | 9 +- extension/locale/de/vimfx.properties | 31 ++- extension/locale/en-US/vimfx.properties | 31 ++- extension/locale/es/vimfx.properties | 31 ++- extension/locale/fr/vimfx.properties | 31 ++- extension/locale/id/vimfx.properties | 31 ++- extension/locale/it/vimfx.properties | 31 ++- extension/locale/ja/vimfx.properties | 31 ++- extension/locale/nl/vimfx.properties | 31 ++- extension/locale/pt-BR/vimfx.properties | 31 ++- extension/locale/ru/vimfx.properties | 31 ++- extension/locale/sv-SE/vimfx.properties | 31 ++- extension/locale/zh-CN/vimfx.properties | 31 ++- extension/locale/zh-TW/vimfx.properties | 31 ++- extension/skin/style.css | 14 +- extension/test/test-api.coffee | 18 +- extension/test/test-parse-prefs.coffee | 4 +- extension/test/test-utils.coffee | 129 +++++++++ 41 files changed, 1417 insertions(+), 538 deletions(-) create mode 100644 extension/lib/hints-mode.coffee diff --git a/documentation/api.md b/documentation/api.md index cc57a13..28b8f1c 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -31,8 +31,8 @@ and `vimfx.set(...)`—in fact, this is the _only_ way of accessing those option Gets the value of the VimFx pref `pref`. ```js -// Get the value of the Hint chars option: -vimfx.get('hint_chars') +// Get the value of the Hint characters option: +vimfx.get('hints.chars') // Get all keyboard shortcuts (as a string) for the `f` command: vimfx.get('mode.normal.follow') ``` @@ -48,8 +48,8 @@ Useful when you wish to extend a default, rather than replacing it. See below. Sets the value of the VimFx pref `pref` to `value`. ```js -// Set the value of the Hint chars option: -vimfx.set('hint_chars', 'abcdefghijklmnopqrstuvwxyz') +// Set the value of the Hint characters option: +vimfx.set('hints.chars', 'abcdefghijklmnopqrstuvwxyz') // Add yet a keyboard shortcut for the `f` command: vimfx.set('mode.normal.follow', vimfx.getDefault('mode.normal.follow') + ' ee') ``` @@ -704,6 +704,10 @@ A `match` object has the following properties: - unmodifiedKey: `String`. `keyStr` without modifiers. +- rawKey: `String`. Unchanged [`event.key`]. + +- rawCode: `String`. Unchanged [`event.code`]. + - toplevel: `Boolean`. Whether or not the match was a toplevel match in the shortcut key tree. This is `true` unless the match is part of the tail of a multi-key shortcut. @@ -940,6 +944,8 @@ backwards compatibility will be a priority and won’t be broken until VimFx [commands.coffee]: ../extension/lib/commands.coffee [vim.coffee]: ../extension/lib/vim.coffee +[`event.key`]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key +[`event.code`]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window [`Browser`]: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/browser [`window.location`]: https://developer.mozilla.org/en-US/docs/Web/API/Location diff --git a/documentation/commands.md b/documentation/commands.md index d71326b..0bd3e23 100644 --- a/documentation/commands.md +++ b/documentation/commands.md @@ -198,7 +198,8 @@ elements as they usually do. (See also the [`focus_previous_key` and When invoking one of the hint commands (such as `f`, `et` or one of the [`v` commands]) you enter Hints mode. In Hints mode, markers with hints are shown for some elements. By typing the letters of a hint something is done to that -element, depending on the command. +element, depending on the command. You can also type the text of an element with +a hint marker: See the [Hint characters] option for more information. Another way to find links on the page is to use `g/`. It’s like the regular find command (`/`), except that it searches links only. @@ -229,13 +230,22 @@ times. VimFx also tries to give you shorter hints for elements that you are more likely to click. This is done by the surprisingly simple rule: The larger the element, the shorter the hint. To learn more about hint characters and hint length, read -about the [hint chars] option. +about the [Hint characters] option. Hints are added on top of the corresponding element. If they obscure the display -too much you can hold shift to make them transparent, letting you peek through -them. (See [Styling] and the [hints\_peek\_through] pref if you’d like to change -that.) The hints can also sometimes cover each other. Press `` and -`` to switch which one should be on top. +too much you can hold down ctrl and shift simultaneously to make them +transparent, letting you peek through them. (See [Styling] and the +[`hints.peek_through`] pref if you’d like to change that.) The hints can also +sometimes cover each other. Press `` and `` to switch which +one should be on top. + +Yet another way to deal with areas crowded with hint markers is to type part of +a marker’s element text. That will filter out hint markers whose elements +_don’t_ match what you’ve typed. Pagination links are good examples, like these +(fake) ones: [1](#1) [2](#2) [3](#3) [4](#4) [5](#5) [6](#6). It’s very hard to +tell which hint to use to go to page three. But if you type “3” things will be +much clearer. (It might even [auto-activate][Hint auto-activation] the hint +marker!) When giving a count to a hint command, all markers will be re-shown after you’ve typed the hint characters of one of them, _count_ minus one times. All but the @@ -255,7 +265,7 @@ accept a count itself. The `et`, `ef`, `yf` and `eb` commands do not accept counts. -Press `` to increase the count by one. This is useful when you’ve already +Press `` to increase the count by one. This is useful when you’ve already entered Hints mode but realize that you want to interact with yet a marker. This can be faster than going into Hints mode once more. @@ -269,18 +279,21 @@ the beginning, and holding alt works as if you’d pressed `et`. For the `F` and `et` commands, holding ctrl makes them open links in the same tab instead, as if you’d used the `f` command. Holding alt toggles whether to open tabs in the background or foreground—it makes `F` work like `et`, and `et` -like `F`. +like `F`. As mentioned in [Hint auto-activation], the best hint is highlighted +with a different color, and can be activated by pressing ``. Holding alt +or ctrl works there too: `` toggles same/new tab and `` +toggles background/foreground tab. -(Also see the advanced prefs [hints\_toggle\_in\_tab] and -[hints\_toggle\_in\_background].) +(Also see the advanced prefs [`hints.toggle_in_tab`] and +[`hints.toggle_in_background`].) Finally, if the element you wanted to interact with didn’t get a hint marker you -can try pressing `` while the hints are still shown. That will give +can try pressing `` while the hints are still shown. That will give hint markers to all _other_ elements. Warning: This can be very slow, and result in an overwhelming amount of hint markers (making it difficult to know which hint to activate sometimes). See this as an escape hatch if you _really_ want to -avoid using the mouse at all costs. (Press `` again to toggle back to -the previous hints.) +avoid using the mouse at all costs. (Press `` again to toggle back +to the previous hints.) ### Mnemonics and choice of default hint command shortcuts @@ -308,11 +321,12 @@ The second key after `e` was chosen based on mnemonics: There’s `et` as in [`v` commands]: #the-v-commands--caret-mode [hint-matcher]: api.md#vimfxsethintmatcherhintmatcher -[hint chars]: options.md#hint-chars +[Hint characters]: options.md#hint-characters +[Hint auto-activation]: options.md#hint-auto-activation [Styling]: styling.md -[hints\_peek\_through]: options.md#hints_peek_through -[hints\_toggle\_in\_tab]: options.md#hints_toggle_in_tab -[hints\_toggle\_in\_background]: options.md#hints_toggle_in_background +[`hints.peek_through`]: options.md#hints.peek_through +[`hints.toggle_in_tab`]: options.md#hints.toggle_in_tab +[`hints.toggle_in_background`]: options.md#hints.toggle_in_background ## The `v` commands / Caret mode diff --git a/documentation/config-file.md b/documentation/config-file.md index 0165e6a..9bf5806 100644 --- a/documentation/config-file.md +++ b/documentation/config-file.md @@ -114,7 +114,7 @@ you add custom commands and set options, or whatever you’d like to do. Example code: ```js -vimfx.set('hint_chars', 'abcdefghijklmnopqrstuvwxyz') +vimfx.set('hints.chars', 'abcdefghijklmnopqrstuvwxyz') vimfx.set('custom.mode.normal.zoom_in', 'zi') ``` @@ -126,7 +126,7 @@ Add-ons Manager, you can use the “Export all” button there to copy all prefs JSON. Paste it in your config file and either edit it, or iterate of it: ```js -let prefs = {"hint_chars": "1234567 89"} // Pasted exported prefs. +let prefs = {"hints.chars": "1234567 89"} // Pasted exported prefs. Object.entries(prefs).forEach(([pref, value]) => vimfx.set(pref, value)) ``` diff --git a/documentation/options.md b/documentation/options.md index 9f4e8df..7ed0c21 100644 --- a/documentation/options.md +++ b/documentation/options.md @@ -23,79 +23,62 @@ These options are available in VimFx’s settings page in the Add-ons Manager [keyboard shortcuts]: shortcuts.md -### Hint chars - -The characters used for the hints in Hints mode, which can be entered using one -of the many [hint commands]. - -Quick suggestion: Put more easily reachable keys longer to the left. Put two -pretty good (but not the best) keys at the end, after the space. - -Some hint characters are easier to type than others. Many people think that the -ones on the home row are the best. VimFx favors keys to the left. That’s why you -should put better keys longer to the left. - -The hint characters always contain a single space. This splits them into two -groups: _primary_ hint characters (before the space), and _secondary_ hint -characters (after the space). Read on to find out why. - -Some markable elements are quicker to find than others. Therefore, VimFx looks -for markable elements in two passes for some commands, such as the `f` command. -(This is why all hints don’t always appear on screen at the same time). If two -passes are used, hints from the _first_ pass can only begin with _primary_ hint -characters. In all other cases hints may start with _any_ hint character. - -When choosing how many secondary hint characters you want (there are two by -default), think about this: Usually most markable elements are found in the -first pass, while fewer are found in the second pass. So it makes sense to have -more primary hint characters than secondary. It’s a tradeoff. If you think the -hints from the first pass are too long, you probably need more primary hint -characters. On the other hand, if you think the hints from the _second_ pass are -too long, you might need a few extra secondary hint characters, but remember -that it might be at the expense of longer hints in the first pass. +### Prevent autofocus -All of this also help you understand why hints may be slow on some pages: +Many sites autofocus their search box, for example. This might be annoying when +browsing using the keyboard, as you do with VimFx, because it often feels like +VimFx isn’t responding, until you realize that you are typing in a text box—not +running VimFx commands! -- One reason could be that most hints come from a second pass, which are slower - to compute (and are worse than first pass hints). +For this reason VimFx can prevent autofocus. It’s not enabled by default, +though, since one of VimFx’s key features is to be nice to your browser and your +habits. - If a site gets an unusual amount of second pass hints, it might be because the - site is badly coded accessibility-wise. If so, consider contacting the site - and telling them so, which improves their accessibility for everyone! +If enabled, all focusing that occurs on page load, or after you’ve just switched +back to a tab from another, until you interact with the page is prevented. -- Another reason could be that a page has a _huge_ amount of links. If that - bothers you regularly, feel free to send a pull request with faster code! +#### Technical notes and trivia -[hint commands]: commands.md#the-hint-commands--hints-mode +Autofocus on page load and when coming back to a tab are the two most common +cases. Some sites, though, automatically focus a text input in other cases as +well. Trying to catch those cases as well, VimFx used to prevent all focusing +that didn’t occur within a fixed number of milliseconds after your last +interaction (click or keypress). However, this proved to be too aggressive, +preventing too much focusing. In other words, the time-based check was not +sufficient to distinguish between intended focusing and automatic unwanted +focusing. It made things worse more than it helped. Since these cases are so +difficult (if not impossible) to detect, it is better to leave them. Thankfully +they are not very common. -### “Previous”/“Next” link patterns +On page load or when coming back to a tab, before you have interacted with the +page in any way, we can be _sure_ that any focusing is automatic (not caused by +you), which makes it safe to prevent all focusing in those time spans. -Space separated lists of patterns that match links to the previous/next page. -Used by the `[` and `]` commands. +### Ignore keyboard layout -There is a standardized way for websites to tell browsers the URLs to the -previous and next page. VimFx looks for that information in the first place. -Unfortunately, many websites don’t provide this information. Then VimFx falls -back on looking for links on the page that seem to go to the previous/next page -using patterns. +If you use more than one keyboard layout, you probably want to enable this +option. -The patterns are matched at the beginning and end of link text (and the -attributes defined by the advanced setting [`pattern_attrs`]). The patterns do -not match in the middle of words, so “previous” does not match “previously”. -The matching is case insensitive. +People who use a keyboard layout _without_ the letters A–Z usually also use the +standard en-US QWERTY layout as well. -Actually, the patterns are regular expressions. If you do not know what a -regular expression is, that’s fine. You can type simple patterns like the -default ones without problems. If you do know what it is, though, you have the -possibility to create more advanced patterns if needed. +This option makes VimFx ignore your current layout and pretend that the standard +en-US QWERTY layout is _always_ used. This way the default shortcuts work even +if your layout doesn’t contain the letters A–Z and all shortcuts can be typed by +the same physical keys on your keyboard regardless of your current keyboard +layout. -Some of the default patterns are English words. You might want to add -alternatives in your own language. +Note that when filtering hints markers by element text (but not when typing hint +characters) in [Hints mode], your current layout _is_ used, even if you’ve +enabled ignoring of it. That’s because otherwise you wouldn’t be able to filter +by element text in any other language than English. -Note: If you need to include a space in your pattern, use `\s`. For example: -`next\spage`. +(If you’d like VimFx to pretend that some other keyboard layout than the +standard en-US QWERTY is always used, you may do so with the special option +[`translations`].) -[`pattern_attrs`]: #pattern_attrs +[Hints mode]: commands.md#the-hint-commands--hints-mode +[`translations`]: #translations ### Blacklist @@ -154,56 +137,115 @@ such a feature could be added if there’s demand for it. [codemirror-vim]: https://codemirror.net/demo/vim.html [``]: commands.md#ignore-mode-s-f1 -### Prevent autofocus +### Hint characters -Many sites autofocus their search box, for example. This might be annoying when -browsing using the keyboard, as you do with VimFx, because it often feels like -VimFx isn’t responding, until you realize that you are typing in a text box—not -running VimFx commands! +The characters used for the hints in Hints mode, which can be entered using one +of the many [hint commands]. -For this reason VimFx can prevent autofocus. It’s not enabled by default, -though, since one of VimFx’s key features is to be nice to your browser and your -habits. +Tip: Prefer filtering hints by element text? Use only uppercase hint characters, +or only numbers. -If enabled, all focusing that occurs on page load, or after you’ve just switched -back to a tab from another, until you interact with the page is prevented. +#### Easy-to-type and performant hints -#### Technical notes and trivia +Quick suggestion: Put more easily reachable keys longer to the left. Put two +pretty good (but not the best) keys at the end, after the space. -Autofocus on page load and when coming back to a tab are the two most common -cases. Some sites, though, automatically focus a text input in other cases as -well. Trying to catch those cases as well, VimFx used to prevent all focusing -that didn’t occur within a fixed number of milliseconds after your last -interaction (click or keypress). However, this proved to be too aggressive, -preventing too much focusing. In other words, the time-based check was not -sufficient to distinguish between intended focusing and automatic unwanted -focusing. It made things worse more than it helped. Since these cases are so -difficult (if not impossible) to detect, it is better to leave them. Thankfully -they are not very common. +Some hint characters are easier to type than others. Many people think that the +ones on the home row are the best. VimFx favors keys to the left. That’s why you +should put better keys longer to the left. -On page load or when coming back to a tab, before you have interacted with the -page in any way, we can be _sure_ that any focusing is automatic (not caused by -you), which makes it safe to prevent all focusing in those time spans. +The hint characters always contain a single space. This splits them into two +groups: _primary_ hint characters (before the space), and _secondary_ hint +characters (after the space). Read on to find out why. -### Ignore keyboard layout +Some markable elements are quicker to find than others. Therefore, VimFx looks +for markable elements in two passes for some commands, such as the `f` command. +(This is why all hints don’t always appear on screen at the same time). If two +passes are used, hints from the _first_ pass can only begin with _primary_ hint +characters. In all other cases hints may start with _any_ hint character. -If you use more than one keyboard layout, you probably want to enable this -option. +When choosing how many secondary hint characters you want (there are two by +default), think about this: Usually most markable elements are found in the +first pass, while fewer are found in the second pass. So it makes sense to have +more primary hint characters than secondary. It’s a tradeoff. If you think the +hints from the first pass are too long, you probably need more primary hint +characters. On the other hand, if you think the hints from the _second_ pass are +too long, you might need a few extra secondary hint characters, but remember +that it might be at the expense of longer hints in the first pass. -People who use a keyboard layout _without_ the letters A–Z usually also use the -standard en-US QWERTY layout as well. +All of this also help you understand why hints may be slow on some pages: -This option makes VimFx ignore your current layout and pretend that the standard -en-US QWERTY layout is _always_ used. This way the default shortcuts work even -if your layout doesn’t contain the letters A–Z and all shortcuts can be typed by -the same physical keys on your keyboard regardless of your current keyboard -layout. +- One reason could be that most hints come from a second pass, which are slower + to compute (and are worse than first pass hints). -(If you’d like VimFx to pretend that some other keyboard layout than the -standard en-US QWERTY is always used, you may do so with the special option -[`translations`].) + If a site gets an unusual amount of second pass hints, it might be because the + site is badly coded accessibility-wise. If so, consider contacting the site + and telling them so, which improves their accessibility for everyone! -[`translations`]: #translations +- Another reason could be that a page has a _huge_ amount of links. If that + bothers you regularly, feel free to send a pull request with faster code! + +#### Filtering hints by element text + +All characters other than the hint characters are used to filter hint markers by +element text. + +The filtering works like in Firefox’s location bar. In short, that means: + +- It is case insensitive. +- Your typed characters are split on spaces. Each part must be present in the + element text (in any order, and they may overlap). + +By default, “f” is a hint character. If you type an “f”, that character is used +to match the hints on screen. If you type an “F” (uppercase), though, which is +_not_ a hint character by default, you will filter the hints based on element +text, causing some hints markers to disappear, and the rest to be replaced. Only +the markable elements with text containing an “f” or “F” will now get a hint +marker. All the “f”s and “F”s are highlighted on the page, to help you keep +track of what’s going on. Keep typing other non-hint characters to further +reduce the number of hint markers, and make the hints shorter all the time. + +Hint markers are usually displayed in uppercase, because it looks nicer. +However, if you mix both lowercase and uppercase hint characters, they will be +displayed as-is, so you can tell them apart. It is recommended to either use +_only_ lowercase or _only_ uppercase characters, though. + +Some people prefer to filter hint markers by element text in the first hand, +rather than typing hint characters. If so, it is a good idea to choose all +uppercase hint characters, or only numbers. This way, you can press `f` and then +simply begin typing the text of the link you wish to follow. + +[hint commands]: commands.md#the-hint-commands--hints-mode + +### Hint auto-activation + +The marker (or markers in the case where several links go to the same place and +have gotten the same hint) with the best hint are highlighted in a different +color. You may at any time press `` to activate those markers. + +One workflow is to type non-hint characters until the hint marker of the element +you want to activate gets highlighted, and then hit ``. However, if _all_ +hint markers end up highlighted (because the text you’ve typed uniquely +identifies a single link) the highlighted markers will be activated +_automatically._ + +If you dislike that, disable this option. Then, you either have to press +`` or a hint character to activate hint markers. + +### Auto-activation timeout + +If you type quickly, you might find that you will keep typing even after a hint +marker has been automatically activated (see [Hint auto-activation]). You might +simply not react that quickly. This might cause you to accidentally trigger +VimFx commands. Therefore, VimFx ignores all your keypresses for a certain +number of milliseconds when having automatically activated a hint marker after +filtering by element text. This option controls exactly how many milliseconds +that is. + +If you can’t find a timeout that works for you, you might want to disable [Hint +auto-activation] instead. + +[Hint auto-activation]: #hint-auto-activation ### Timeout @@ -215,6 +257,35 @@ might be surprised that all search results are highlighted when you a bit later try to search using the `/` command. (That’s what `a/` does.) _With_ a timeout, the `a` would be cancelled when the timeout has passed. +### “Previous”/“Next” link patterns + +Space separated lists of patterns that match links to the previous/next page. +Used by the `[` and `]` commands. + +There is a standardized way for websites to tell browsers the URLs to the +previous and next page. VimFx looks for that information in the first place. +Unfortunately, many websites don’t provide this information. Then VimFx falls +back on looking for links on the page that seem to go to the previous/next page +using patterns. + +The patterns are matched at the beginning and end of link text (and the +attributes defined by the advanced setting [`pattern_attrs`]). The patterns do +not match in the middle of words, so “previous” does not match “previously”. +The matching is case insensitive. + +Actually, the patterns are regular expressions. If you do not know what a +regular expression is, that’s fine. You can type simple patterns like the +default ones without problems. If you do know what it is, though, you have the +possibility to create more advanced patterns if needed. + +Some of the default patterns are English words. You might want to add +alternatives in your own language. + +Note: If you need to include a space in your pattern, use `\s`. For example: +`next\spage`. + +[`pattern_attrs`]: #pattern_attrs + ## Advanced options @@ -243,7 +314,8 @@ You can also choose to show notifications any way you want by listening for the If enabled, a [notification] is shown with the keys you have entered so far of a command. This is only noticeable if you type a multi-key shortcut or use a -count. +count, or if you filter hint markers by element text (then, the text you’ve +typed will be shown). [notification]: notifications.md @@ -331,24 +403,6 @@ shortcut instead of typing into the text input, which can be quite annoying. To avoid the problem, VimFx waits a bit before checking if you have left the text input. -### `hints_timeout` - -The number of milliseconds a matched hint marker should stay on screen before -disappearing (or resetting). - -### `hints_sleep` - -In Hints mode, VimFx continually checks if the element for a hint marker has -moved. If so, the marker is moved as well. This pref controls how many -milliseconds VimFx should “sleep” between each check. The shorter, the more CPU -usage, the longer, the more stuttery marker movement. - -The default value should work fine, but if you have a low-performing computer -and you notice bothering CPU usage during Hints mode you might want to raise the -sleep time. - -Set it to -1 to disable the marker movement feature entirely. - ### Scrolling prefs Apart from its own prefs, VimFx also respects a few built-in Firefox prefs. @@ -433,7 +487,33 @@ should be matched against. [“Previous”/“Next” link patterns]: #previousnext-link-patterns -### `hints_peek_through` +### `hints.matched_timeout` + +The number of milliseconds a matched hint marker should stay on screen before +disappearing (or resetting). + +### `hints.sleep` + +In Hints mode, VimFx continually checks if the element for a hint marker has +moved. If so, the marker is moved as well. This pref controls how many +milliseconds VimFx should “sleep” between each check. The shorter, the more CPU +usage, the longer, the more stuttery marker movement. + +The default value should work fine, but if you have a low-performing computer +and you notice bothering CPU usage during Hints mode you might want to raise the +sleep time. + +Set it to -1 to disable the marker movement feature entirely. + +### `hints.match_text` + +If you strongly dislike that typing non-[Hint characters] filters hint markers +by element text, disable this pref. (That’ll make things work like it did in +VimFx 0.18.x and older.) + +[Hint characters]: #hint-characters + +### `hints.peek_through` This pref doesn’t do much. If you’ve used custom [styling] to change which modifier lets you peek through markers in [Hints mode], you might want to change @@ -443,13 +523,13 @@ you to press shift for this task. [styling]: styling.md [Hints mode]: commands.md#the-hint-commands--hints-mode -### `hints_toggle_in_tab` +### `hints.toggle_in_tab` If the keypress that matched a hint starts with this string, toggle whether to open the matched link in the current tab or a new tab. See the [hint commands] for more information. -### `hints_toggle_in_background` +### `hints.toggle_in_background` If the keypress that matched a hint starts with this string, open the matched link in a new tab and toggle whether to open that tab in the background or diff --git a/documentation/questions-and-answers.md b/documentation/questions-and-answers.md index 1475702..0a212b4 100644 --- a/documentation/questions-and-answers.md +++ b/documentation/questions-and-answers.md @@ -85,6 +85,14 @@ No, but clicking on any command in it opens VimFx’s settings page in the Add-o Manager and automatically selects the text input for that command. Tip: Use the `eb` command to click without using the mouse. +## Can I make Hints mode work with element text? + +… like the modes Vimium, Vimperator and Pentadactyl provide? + +Yes! Have a look at [Filtering hints by element text] for more information. + +[Filtering hints by element text]: options.md#filtering-hints-by-element-text + ## Will VimFx provide advanced Find features? One of VimFx’s key features is to embrace standard Firefox features. As long as diff --git a/extension/lib/api.coffee b/extension/lib/api.coffee index 5a10f1e..696484e 100644 --- a/extension/lib/api.coffee +++ b/extension/lib/api.coffee @@ -26,34 +26,44 @@ Vim = require('./vim') counter = new utils.Counter({start: 10000, step: 100}) -createConfigAPI = (vimfx) -> { - get: (pref) -> switch - when pref of defaults.parsed_options - vimfx.options[pref] - when pref of defaults.all_prefs or pref?.startsWith('custom.') - prefs.get(pref) - else - throw new Error("VimFx: Unknown pref: #{pref}") - - getDefault: (pref) -> switch - when pref of defaults.parsed_options or pref?.startsWith('custom.') - throw new Error("VimFx: No default for pref: #{pref}") - when pref of defaults.all_prefs - defaults.all_prefs[pref] - else - throw new Error("VimFx: Unknown pref: #{pref}") - - set: (pref, value) -> switch - when pref of defaults.parsed_options - previousValue = vimfx.options[pref] - vimfx.options[pref] = value - onShutdown(vimfx, -> vimfx.options[pref] = previousValue) - when pref of defaults.all_prefs or pref?.startsWith('custom.') - previousValue = if prefs.has(pref) then prefs.get(pref) else null - prefs.set(pref, value) - onShutdown(vimfx, -> prefs.set(pref, previousValue)) - else - throw new Error("VimFx: Unknown pref: #{pref}") +createConfigAPI = (vimfx, {allowDeprecated = true} = {}) -> { + get: (inputPref) -> + pref = alias(inputPref, allowDeprecated) + if pref != inputPref + try return prefs.get(inputPref) + return switch + when pref of defaults.parsed_options + vimfx.options[pref] + when pref of defaults.all_prefs or pref?.startsWith('custom.') + prefs.get(pref) + else + throw new Error("VimFx: Unknown pref: #{pref}") + + getDefault: (inputPref) -> + pref = alias(inputPref, allowDeprecated) + if pref != inputPref + try return prefs.default.get(inputPref) + return switch + when pref of defaults.parsed_options or pref?.startsWith('custom.') + throw new Error("VimFx: No default for pref: #{pref}") + when pref of defaults.all_prefs + defaults.all_prefs[pref] + else + throw new Error("VimFx: Unknown pref: #{pref}") + + set: (inputPref, value) -> + pref = alias(inputPref, allowDeprecated) + switch + when pref of defaults.parsed_options + previousValue = vimfx.options[pref] + vimfx.options[pref] = value + onShutdown(vimfx, -> vimfx.options[pref] = previousValue) + when pref of defaults.all_prefs or pref?.startsWith('custom.') + previousValue = if prefs.has(pref) then prefs.get(pref) else null + prefs.set(pref, value) + onShutdown(vimfx, -> prefs.set(pref, previousValue)) + else + throw new Error("VimFx: Unknown pref: #{pref}") addCommand: ({name, description, mode, category, order} = {}, fn) -> mode ?= 'normal' @@ -148,6 +158,31 @@ createConfigAPI = (vimfx) -> { modes: vimfx.modes } +# Don’t crash the users’s entire config file on startup if they happen to try to +# set a renamed pref (only warn), but do throw an error if they reload the +# config file; then they could update while editing the file anyway. +alias = (pref, allowDeprecated) -> + if pref of renamedPrefs + newPref = renamedPrefs[pref] + message = "VimFx: `#{pref}` has been renamed to `#{newPref}`." + if allowDeprecated + console.warn(message) + return newPref + else + throw new Error(message) + else + return pref + +renamedPrefs = { + 'hint_chars': 'hints.chars' + 'hints_sleep': 'hints.sleep' + 'hints_timeout': 'hints.matched_timeout' + 'hints_peek_through': 'hints.peek_through' + 'hints_toggle_in_tab': 'hints.toggle_in_tab' + 'hints_toggle_in_background': 'hints.toggle_in_background' + 'mode.hints.delete_hint_char': 'mode.hints.delete_char' +} + getOverrides = (rules, args...) -> for [match, overrides] in rules return overrides if match(args...) diff --git a/extension/lib/commands-frame.coffee b/extension/lib/commands-frame.coffee index 220983a..1eaf276 100644 --- a/extension/lib/commands-frame.coffee +++ b/extension/lib/commands-frame.coffee @@ -168,9 +168,16 @@ helper_follow = (options, matcher, {vim, pass}) -> return unless shape.nonCoveredPoint + text = utils.getText(element) + originalRect = element.getBoundingClientRect() length = vim.state.markerElements.push({element, originalRect}) - wrapper = {type, shape, elementIndex: length - 1} + wrapper = { + type, text, shape, + combinedArea: shape.area + elementIndex: length - 1 + parentIndex: null + } if wrapper.type == 'link' {href} = element @@ -189,10 +196,9 @@ helper_follow = (options, matcher, {vim, pass}) -> if href of hrefs parent = hrefs[href] wrapper.parentIndex = parent.elementIndex - parent.shape.area += wrapper.shape.area + parent.combinedArea += wrapper.shape.area parent.numChildren += 1 else - wrapper.numChildren = 0 hrefs[href] = wrapper return wrapper diff --git a/extension/lib/commands.coffee b/extension/lib/commands.coffee index 78a4dd1..49be823 100644 --- a/extension/lib/commands.coffee +++ b/extension/lib/commands.coffee @@ -438,7 +438,7 @@ helper_follow = ({name, callback}, {vim, count, callbackOverride = null}) -> markerContainer = new MarkerContainer({ window - hintChars: vim.options.hint_chars + hintChars: vim.options['hints.chars'] getComplementaryWrappers: (callback) -> vim._run(name, {pass: 'complementary'}, ({wrappers, viewport}) -> # `markerContainer.container` is `null`ed out when leaving Hints mode. @@ -468,7 +468,8 @@ helper_follow = ({name, callback}, {vim, count, callbackOverride = null}) -> vim._enterMode('hints', { markerContainer, count callback: chooseCallback - sleep: vim.options.hints_sleep + matchText: vim.options['hints.match_text'] + sleep: vim.options['hints.sleep'] }) injectHints = ({wrappers, viewport, pass}) -> @@ -498,9 +499,9 @@ helper_follow_clickable = (options, args) -> {window} = vim switch - when keyStr.startsWith(vim.options.hints_toggle_in_tab) + when keyStr.startsWith(vim.options['hints.toggle_in_tab']) inTab = not inTab - when keyStr.startsWith(vim.options.hints_toggle_in_background) + when keyStr.startsWith(vim.options['hints.toggle_in_background']) inTab = true inBackground = not inBackground else @@ -689,9 +690,11 @@ commands.click_browser_element = ({vim}) -> if wrappers.length > 0 viewport = viewportUtils.getWindowViewport(window) + hintChars = + utils.removeDuplicateChars(vim.options['hints.chars'].toLowerCase()) markerContainer = new MarkerContainer({ window - hintChars: vim.options.hint_chars + hintChars adjustZoom: false getComplementaryWrappers: (callback) -> newWrappers = markableElements.find( @@ -706,7 +709,7 @@ commands.click_browser_element = ({vim}) -> ) markerContainer.injectHints(wrappers, viewport, 'single') - vim._enterMode('hints', {markerContainer, callback}) + vim._enterMode('hints', {markerContainer, callback, matchText: false}) else vim.notify(translate('notification.follow.none')) @@ -873,7 +876,7 @@ commands.enter_reader_view = ({vim}) -> commands.reload_config_file = ({vim}) -> vim._parent.emit('shutdown') - config.load(vim._parent, (status) -> switch status + config.load(vim._parent, {allowDeprecated: false}, (status) -> switch status when null vim.notify(translate('notification.reload_config_file.none')) when true diff --git a/extension/lib/config.coffee b/extension/lib/config.coffee index 535cde0..bf5ff25 100644 --- a/extension/lib/config.coffee +++ b/extension/lib/config.coffee @@ -25,14 +25,14 @@ utils = require('./utils') {OS} = Components.utils.import('resource://gre/modules/osfile.jsm', {}) -load = (vimfx, callback = ->) -> +load = (vimfx, options = null, callback = ->) -> configDir = vimfx.options.config_file_directory unless configDir callback(null) return - scope = {vimfx: createConfigAPI(vimfx)} + scope = {vimfx: createConfigAPI(vimfx, options)} # Calling `vimfx.createKeyTrees()` after each `vimfx.set()` that modifies a # shortcut is absolutely redundant and may make Firefox start slower. Do it diff --git a/extension/lib/defaults.coffee b/extension/lib/defaults.coffee index c9004fa..465528e 100644 --- a/extension/lib/defaults.coffee +++ b/extension/lib/defaults.coffee @@ -135,11 +135,14 @@ shortcuts = 'hints': '': '': 'exit' - '': 'rotate_markers_forward' + ' \ + \ + ': 'activate_highlighted' + '': 'rotate_markers_forward' '': 'rotate_markers_backward' - '': 'delete_hint_char' - '': 'increase_count' - '': 'toggle_complementary' + '': 'delete_char' + '': 'toggle_complementary' + '': 'increase_count' 'ignore': '': @@ -155,13 +158,15 @@ shortcuts = ' ': 'exit' options = - 'hint_chars': 'fjdkslaghrueiwonc mv' - 'prev_patterns': 'prev previous ‹ « ◀ ← << < back newer' - 'next_patterns': 'next › » ▶ → >> > more older' - 'blacklist': '*example.com* http://example.org/editor/*' 'prevent_autofocus': false 'ignore_keyboard_layout': false + 'blacklist': '*example.com* http://example.org/editor/*' + 'hints.chars': 'fjdkslaghrueiwonc mv' + 'hints.auto_activate': true + 'hints.timeout': 400 'timeout': 2000 + 'prev_patterns': 'prev previous ‹ « ◀ ← << < back newer' + 'next_patterns': 'next › » ▶ → >> > more older' advanced_options = 'notifications_enabled': true @@ -173,8 +178,6 @@ advanced_options = 'prevent_autofocus_modes': 'normal' 'config_file_directory': '' 'blur_timeout': 50 - 'hints_timeout': 200 - 'hints_sleep': 15 'smoothScroll.lines.spring-constant': '1000' 'smoothScroll.pages.spring-constant': '2500' 'smoothScroll.other.spring-constant': '2500' @@ -186,9 +189,12 @@ advanced_options = a, button, input[type="button"] ):not([role="menu"]):not([role="tab"])' 'pattern_attrs': 'rel role data-tooltip aria-label' - 'hints_peek_through': '' - 'hints_toggle_in_tab': '' + 'hints.toggle_in_tab': '' 'adjustable_element_keys': ' ' diff --git a/extension/lib/events-frame.coffee b/extension/lib/events-frame.coffee index 807f682..017994c 100644 --- a/extension/lib/events-frame.coffee +++ b/extension/lib/events-frame.coffee @@ -115,6 +115,19 @@ class FrameEventManager callback(diffs) ) + messageManager.listen('highlightMarkableElements', (data) => + {elementIndices, strings} = data + utils.clearSelectionDeep(@vim.content) + return if strings.length == 0 + for elementIndex in elementIndices + {element} = @vim.state.markerElements[elementIndex] + for string in strings + utils.selectAllSubstringMatches( + element, string, {caseSensitive: false} + ) + return + ) + @listen('overflow', (event) => target = event.originalTarget @vim.state.scrollableElements.addChecked(target) diff --git a/extension/lib/events.coffee b/extension/lib/events.coffee index 33b7261..4a1ca0b 100644 --- a/extension/lib/events.coffee +++ b/extension/lib/events.coffee @@ -206,6 +206,10 @@ class UIEventManager consumeKeyEvent: (vim, event) -> match = vim._consumeKeyEvent(event) + if typeof match == 'boolean' + @suppress = match + return + if match if @vimfx.options.notify_entered_keys and vim.mode != 'ignore' if match.type in ['none', 'full'] or match.likelyConflict @@ -289,9 +293,10 @@ class EnteredKeysManager @timeout = null clear: (notifier) -> - @keys = [] @clearTimeout() - notifier.hideNotification() + if @keys.length > 0 + @keys = [] + notifier.hideNotification() push: (notifier, keyStr, duration) -> @keys.push(keyStr) diff --git a/extension/lib/help.coffee b/extension/lib/help.coffee index 4ad039a..2cced26 100644 --- a/extension/lib/help.coffee +++ b/extension/lib/help.coffee @@ -153,18 +153,18 @@ getExtraCommands = (vimfx) -> 'hints': { '': { 'peek_through': - if vimfx.options.hints_peek_through - [vimfx.options.hints_peek_through] + if vimfx.options['hints.peek_through'] + [vimfx.options['hints.peek_through']] else [] 'toggle_in_tab': - if vimfx.options.hints_toggle_in_tab - ["#{vimfx.options.hints_toggle_in_tab}#{lastHintChar}>"] + if vimfx.options['hints.toggle_in_tab'] + ["#{vimfx.options['hints.toggle_in_tab']}#{lastHintChar}>"] else [] 'toggle_in_background': - if vimfx.options.hints_toggle_in_background - ["#{vimfx.options.hints_toggle_in_background}#{lastHintChar}>"] + if vimfx.options['hints.toggle_in_background'] + ["#{vimfx.options['hints.toggle_in_background']}#{lastHintChar}>"] else [] } diff --git a/extension/lib/hints-mode.coffee b/extension/lib/hints-mode.coffee new file mode 100644 index 0000000..d0c64bc --- /dev/null +++ b/extension/lib/hints-mode.coffee @@ -0,0 +1,100 @@ +### +# Copyright Simon Lydell 2016. +# +# 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 . +### + +# This file contains a few helper functions for Hints mode, that didn’t really +# fit in modes.coffee. + +activateMatch = (vim, storage, match, matchedMarkers, callback) -> + {markerContainer} = storage + + marker.markMatched(true) for marker in matchedMarkers + + # Prevent `onLeave` cleanup if the callback enters another mode. + storage.skipOnLeaveCleanup = true + again = callback(matchedMarkers[0], storage.count, match.keyStr) + storage.skipOnLeaveCleanup = false + + switchedMode = (vim.mode != 'hints') + + if again and not switchedMode + storage.count -= 1 + vim.window.setTimeout((-> + marker.markMatched(false) for marker in matchedMarkers + updateVisualFeedback(vim, markerContainer, []) + return + ), vim.options['hints.matched_timeout']) + markerContainer.reset() + + else + vim.window.setTimeout((-> + # Don’t clean up if Hints mode has been re-entered before the + # timeout has passed. + cleanup(vim, storage) unless vim.mode == 'hints' + ), vim.options['hints.matched_timeout']) + + unless switchedMode + storage.skipOnLeaveCleanup = true + vim._enterMode('normal') + storage.skipOnLeaveCleanup = false + +cleanup = (vim, storage) -> + {markerContainer, matchText} = storage + markerContainer?.remove() + vim._run('clear_selection') if matchText and vim.mode != 'caret' + vim.hideNotification() if vim.options.notify_entered_keys + storage.clearInterval?() + for key of storage + storage[key] = null + return + +getChar = (match, {markerContainer, matchText}) -> + {unmodifiedKey} = match + unmodifiedKey = unmodifiedKey.toLowerCase() unless matchText + + isHintChar = switch + when not matchText + true + when unmodifiedKey.length == 1 + markerContainer.isHintChar(unmodifiedKey) + else + false + + char = if isHintChar then unmodifiedKey else match.rawKey + if char.length == 1 + return {char, isHintChar} + else + return {char: null, isHintChar: false} + +updateVisualFeedback = (vim, markerContainer, visibleMarkers) -> + if vim.options.notify_entered_keys + if markerContainer.enteredText == '' + vim.hideNotification() + else + vim.notify(markerContainer.enteredText) + + elementIndices = visibleMarkers.map((marker) -> marker.wrapper.elementIndex) + strings = markerContainer.splitEnteredText() + vim._send('highlightMarkableElements', {elementIndices, strings}) + +module.exports = { + activateMatch + cleanup + getChar + updateVisualFeedback +} diff --git a/extension/lib/main.coffee b/extension/lib/main.coffee index 244f3b5..605c930 100644 --- a/extension/lib/main.coffee +++ b/extension/lib/main.coffee @@ -138,10 +138,12 @@ module.exports = (data, reason) -> messageManager.load("#{ADDON_PATH}/content/bootstrap-frame-#{BUILD_TIME}.js") # @if TESTS - test(vimfx) - runFrameTests = true + runTests = true messageManager.listen('runTests', (data, callback) -> - callback(runFrameTests) - runFrameTests = false + # Running the regular tests inside this callback means that there will be a + # `window` available for tests, if they need one. + test(vimfx) if runTests + callback(runTests) + runTests = false ) # @endif diff --git a/extension/lib/markable-elements.coffee b/extension/lib/markable-elements.coffee index 027daaa..43b4b52 100644 --- a/extension/lib/markable-elements.coffee +++ b/extension/lib/markable-elements.coffee @@ -118,16 +118,20 @@ getRects = (element, viewport) -> # frame, as well as the rectangle that the coordinates occur in. It is `null` # if the element is outside `viewport` or entirely covered by other elements. # - `area`: The area of the part of the element that is inside the viewport. +# - `width`: The width of the visible rect at `nonCoveredPoint`. +# - `textOffset`: The distance between the left edge of the element and the left +# edge of its text. getElementShape = (elementData, tryRight, rects = null) -> {viewport, element} = elementData - result = {nonCoveredPoint: null, area: 0} + result = {nonCoveredPoint: null, area: 0, width: 0, textOffset: null} rects ?= getRects(element, viewport) totalArea = 0 visibleRects = [] - for rect in rects + for rect, index in rects visibleRect = viewportUtils.adjustRectToViewport(rect, viewport) continue if visibleRect.area == 0 + visibleRect.index = index totalArea += visibleRect.area visibleRects.push(visibleRect) @@ -150,13 +154,37 @@ getElementShape = (elementData, tryRight, rects = null) -> result.area = totalArea # Even if `element` has a visible rect, it might be covered by other elements. + nonCoveredPoint = null + nonCoveredPointRect = null for visibleRect in visibleRects nonCoveredPoint = getFirstNonCoveredPoint( elementData, visibleRect, tryRight ) - break if nonCoveredPoint + if nonCoveredPoint + nonCoveredPointRect = visibleRect + break + return result unless nonCoveredPoint result.nonCoveredPoint = nonCoveredPoint + + result.width = nonCoveredPointRect.width + + boxQuads = utils.getFirstNonEmptyTextNodeBoxQuads(element) + if boxQuads?.length > 0 + # Care is taken below to ignore negative offsets, such as when text is + # hidden using `text-indent: -9999px`. + offset = null + if rects.length == 1 + lefts = boxQuads + .map(({bounds}) -> bounds.left) + .filter((left) -> left >= nonCoveredPointRect.left) + offset = if lefts.length == 0 then null else Math.min(lefts...) + else + {bounds: {left}} = + boxQuads[Math.min(nonCoveredPointRect.index, boxQuads.length - 1)] + offset = Math.max(nonCoveredPointRect.left, left) + result.textOffset = offset - nonCoveredPointRect.left if offset? + return result getFirstNonCoveredPoint = (elementData, elementRect, tryRight) -> diff --git a/extension/lib/marker-container.coffee b/extension/lib/marker-container.coffee index eed284e..52f7b92 100644 --- a/extension/lib/marker-container.coffee +++ b/extension/lib/marker-container.coffee @@ -22,6 +22,7 @@ huffman = require('n-ary-huffman') Marker = require('./marker') +utils = require('./utils') CONTAINER_ID = 'VimFxMarkersContainer' @@ -29,6 +30,8 @@ CONTAINER_ID = 'VimFxMarkersContainer' # value Firefox handles. MAX_Z_INDEX = 2147483647 +SPACE = ' ' + class MarkerContainer constructor: (options) -> { @@ -40,16 +43,22 @@ class MarkerContainer [@primaryHintChars, @secondaryHintChars] = hintChars.split(' ') @alphabet = @primaryHintChars + @secondaryHintChars - @numEnteredChars = 0 + @enteredHint = '' + @enteredText = '' @isComplementary = false - @hasLookedForComplementaryWrappers = false + @complementaryState = 'NOT_REQUESTED' + + @visualFeedbackUpdater = null @markers = [] @markerMap = {} + @highlightedMarkers = [] @container = @window.document.createElement('box') @container.id = CONTAINER_ID + if @alphabet not in [@alphabet.toLowerCase(), @alphabet.toUpperCase()] + @container.classList.add('has-mixedcase') # This static method looks for an element with the container ID and removes # it. This is more fail-safe than `@container?.remove()`, because we might @@ -63,52 +72,64 @@ class MarkerContainer @container = null reset: -> - @numEnteredChars = 0 - marker.reset() for marker in @markers when marker.hintIndex > 0 - @refreshComplementaryVisiblity() + @enteredHint = '' + @enteredText = '' + @resetMarkers() - refreshComplementaryVisiblity: -> + resetMarkers: -> + @resetHighlightedMarkers() for marker in @markers - marker.setVisibility(marker.isComplementary == @isComplementary) + if marker.isComplementary == @isComplementary + marker.reset() + @updateHighlightedMarkers(marker) + else + marker.hide() + @markHighlightedMarkers() + + resetHighlightedMarkers: -> + marker.markHighlighted(false) for marker in @highlightedMarkers + @highlightedMarkers = [] + + markHighlightedMarkers: -> + marker.markHighlighted(true) for marker in @highlightedMarkers return + updateHighlightedMarkers: (marker) -> + return unless marker.visible + + if @highlightedMarkers.length == 0 + @highlightedMarkers = [marker] + return + + [firstHighlightedMarker] = @highlightedMarkers + comparison = compareHints(marker, firstHighlightedMarker, @alphabet) + + if comparison == 0 and firstHighlightedMarker.visible + @highlightedMarkers.push(marker) + return + + if comparison < 0 or not firstHighlightedMarker.visible + @highlightedMarkers = [marker] + return + # Create `Marker`s for every element (represented by a regular object of data # about the element—a “wrapper,” a stand-in for the real element, which is # only accessible in frame scripts) in `wrappers`, and insert them into # `@window`. injectHints: (wrappers, viewport, pass) -> isComplementary = (pass == 'complementary') - combined = [] - markers = [] + markers = Array(wrappers.length) markerMap = {} - for wrapper in wrappers + for wrapper, index in wrappers marker = new Marker(wrapper, @window.document, {isComplementary}) - if wrapper.parentIndex? - combined.push(marker) - else - markers.push(marker) + markers[index] = marker markerMap[wrapper.elementIndex] = marker - if marker.isComplementary == @isComplementary and @numEnteredChars > 0 + if marker.isComplementary != @isComplementary or @enteredHint != '' marker.hide() - # Both the `z-index` assignment and the Huffman algorithm below require the - # markers to be sorted. - markers.sort((a, b) -> a.weight - b.weight) - - # Each marker gets a unique `z-index`, so that it can be determined if a - # marker overlaps another. More important markers (higher weight) should - # have higher `z-index`, in order not to start out overlapped. Existing - # markers should also have higher `z-index` than newer markers, which is why - # we start out large and not at zero. - zIndex = - MAX_Z_INDEX - markers.length - combined.length - @markers.length + 1 - for marker in markers - marker.markerElement.style.zIndex = zIndex - zIndex += 1 - # Add `z-index` space for all the children of the marker. - zIndex += marker.wrapper.numChildren if marker.wrapper.numChildren? - + nonCombinedMarkers = + markers.filter((marker) -> not marker.wrapper.parentIndex?) prefixes = switch pass when 'first' @primaryHintChars @@ -119,20 +140,19 @@ class MarkerContainer diff = @alphabet.length - prefixes.length paddedMarkers = if diff > 0 - # Dummy nodes with infinite weight are be guaranteed to be first-level + # Dummy nodes with infinite weight are guaranteed to be first-level # children of the Huffman tree. When there are less prefixes than # characters in the alphabet, adding a few such dummy nodes makes sure # that there is one child per prefix in the first level (discarding the # dummy children). - markers.concat(Array(diff).fill({weight: Infinity})) + nonCombinedMarkers.concat(Array(diff).fill({weight: Infinity})) else # Otherwise, nothing needs to be done. Simply use as many prefixes as # needed (and ignore any remaining ones). - markers + nonCombinedMarkers - tree = huffman.createTree(paddedMarkers, @alphabet.length, {sorted: true}) + tree = huffman.createTree(paddedMarkers, @alphabet.length) - setHint = (marker, hint) -> marker.setHint(hint) index = 0 for node in tree.children by -1 when node.weight != Infinity prefix = prefixes[index] @@ -142,17 +162,24 @@ class MarkerContainer setHint(node, prefix) index += 1 - # Markers for links with the same href can be combined to use the same hint. - # They should all have the same `z-index` (because they all have the same - # combined weight), but in case any of them cover another they still get a - # unique `z-index` (space for this was added above). - for marker in combined - parent = markerMap[marker.wrapper.parentIndex] - parentZIndex = Number(parent.markerElement.style.zIndex) - marker.markerElement.style.zIndex = parentZIndex - parent.markerElement.style.zIndex = parentZIndex + 1 - marker.setHint(parent.hint) - markers.push(combined...) + # Each marker gets a unique `z-index`, so that it can be determined if a + # marker overlaps another. Larger elements should have higher `z-index`, + # because it looks odd when the hint for a smaller element overlaps the hint + # for a larger element. Existing markers should also have higher `z-index` + # than newer markers, which is why we start out large and not at zero. + zIndex = MAX_Z_INDEX - markers.length - @markers.length + 1 + markers.sort((a, b) -> a.wrapper.area - b.wrapper.area) + for marker in markers + marker.markerElement.style.zIndex = zIndex + zIndex += 1 + + if marker.wrapper.parentIndex? + parent = markerMap[marker.wrapper.parentIndex] + marker.setHint(parent.hint) + + @updateHighlightedMarkers(marker) + + @markHighlightedMarkers() zoom = 1 if @adjustZoom @@ -174,49 +201,198 @@ class MarkerContainer @markers.push(markers...) Object.assign(@markerMap, markerMap) + if @enteredText != '' + [matchingMarkers, nonMatchingMarkers] = @matchText(@enteredText) + marker.hide() for marker in nonMatchingMarkers + @setHintsForTextFilteredMarkers() + @updateVisualFeedback(matchingMarkers) + + setHintsForTextFilteredMarkers: -> + markers = [] + combined = [] + visibleParentMap = {} + + visibleMarkers = @markers.filter((marker) -> marker.visible) + + for marker in visibleMarkers + wrappedMarker = wrapTextFilteredMarker(marker) + {parentIndex} = marker.wrapper + + if parentIndex? + parent = + if parentIndex of visibleParentMap + visibleParentMap[parentIndex] + else + wrapTextFilteredMarker(@markerMap[parentIndex]) + + # If the parent isn’t visible, it’s because it didn’t match + # `@enteredText`. If so, promote this marker as the parent. + visibleParent = if parent.marker.visible then parent else wrappedMarker + visibleParentIndex = visibleParent.marker.wrapper.elementIndex + visibleParentMap[visibleParentIndex] = visibleParent + + if visibleParent == wrappedMarker + markers.push(wrappedMarker) + else + combined.push(wrappedMarker) + # Make sure that all combined markers use the highest weight. + if visibleParent.weight < wrappedMarker.weight + visibleParent.weight = wrappedMarker.weight + + else + markers.push(wrappedMarker) + + tree = huffman.createTree(markers, @alphabet.length) + tree.assignCodeWords(@alphabet, ({marker}, hint) -> marker.setHint(hint)) + + for {marker} in combined + {marker: parent} = visibleParentMap[marker.wrapper.parentIndex] + marker.setHint(parent.hint) + + @resetHighlightedMarkers() + for {marker} in markers.concat(combined) + @updateHighlightedMarkers(marker) + marker.refreshPosition() + @markHighlightedMarkers() + + return + toggleComplementary: -> - if not @isComplementary and not @hasLookedForComplementaryWrappers + if not @isComplementary and + @complementaryState in ['NOT_REQUESTED', 'NOT_FOUND'] @isComplementary = true - @hasLookedForComplementaryWrappers = true + @complementaryState = 'PENDING' @getComplementaryWrappers(({wrappers, viewport}) => if wrappers.length > 0 + @complementaryState = 'FOUND' + @enteredText = '' if @isComplementary @injectHints(wrappers, viewport, 'complementary') if @isComplementary @reset() - else - @refreshComplementaryVisiblity() + @updateVisualFeedback([]) else @isComplementary = false - @hasLookedForComplementaryWrappers = false + @complementaryState = 'NOT_FOUND' ) + else @isComplementary = not @isComplementary - @reset() + unless @complementaryState == 'PENDING' + @reset() + @updateVisualFeedback([]) - matchHintChar: (char) -> - matchedMarkers = [] + matchHint: (hint) -> + matchingMarkers = [] + nonMatchingMarkers = [] - for marker in @markers - if marker.isComplementary == @isComplementary and - marker.hintIndex == @numEnteredChars - matched = marker.matchHintChar(char) - marker.hide() unless matched - if marker.isMatched() - marker.markMatched(true) - matchedMarkers.push(marker) + for marker in @markers when marker.visible + if marker.matchHint(hint) + matchingMarkers.push(marker) + else + nonMatchingMarkers.push(marker) + + return [matchingMarkers, nonMatchingMarkers] + + matchText: (text) -> + matchingMarkers = [] + nonMatchingMarkers = [] + + splitEnteredText = @splitEnteredText(text) + for marker in @markers when marker.visible + if marker.matchText(splitEnteredText) + matchingMarkers.push(marker) + else + nonMatchingMarkers.push(marker) - @numEnteredChars += 1 - return matchedMarkers + return [matchingMarkers, nonMatchingMarkers] + + splitEnteredText: (text = @enteredText) -> + return text.trim().split(SPACE) + + isHintChar: (char) -> + return (@enteredHint != '' or char in @alphabet) + + addChar: (char, isHintChar = null) -> + @isComplementary = false if @complementaryState == 'PENDING' + isHintChar ?= @isHintChar(char) + hint = @enteredHint + char + text = @enteredText + char.toLowerCase() + + if not isHintChar and char == SPACE + matchingMarkers = @markers.filter((marker) -> marker.visible) + unless @enteredText == '' or @enteredText.endsWith(SPACE) + @enteredText = text + @updateVisualFeedback(matchingMarkers) + return matchingMarkers + + [matchingMarkers, nonMatchingMarkers] = + if isHintChar + @matchHint(hint) + else + @matchText(text) + + return nonMatchingMarkers if matchingMarkers.length == 0 + + marker.hide() for marker in nonMatchingMarkers + + if isHintChar + @enteredHint = hint + @resetHighlightedMarkers() + for marker in matchingMarkers + marker.markMatchedPart(hint) + @updateHighlightedMarkers(marker) + @markHighlightedMarkers() + else + @enteredText = text + @setHintsForTextFilteredMarkers() unless nonMatchingMarkers.length == 0 + + @updateVisualFeedback(matchingMarkers) + return matchingMarkers + + deleteChar: -> + @isComplementary = false if @complementaryState == 'PENDING' + return @deleteHintChar() or @deleteTextChar() deleteHintChar: -> - for marker in @markers - switch marker.hintIndex - @numEnteredChars - when 0 - marker.deleteHintChar() - when -1 + return false if @enteredHint == '' + hint = @enteredHint[...-1] + matchingMarkers = [] + + @resetHighlightedMarkers() + for marker in @markers when marker.isComplementary == @isComplementary + marker.markMatchedPart(hint) + if marker.matchHint(hint) + marker.show() + matchingMarkers.push(marker) + @updateHighlightedMarkers(marker) + @markHighlightedMarkers() + + @enteredHint = hint + @updateVisualFeedback(matchingMarkers) + return true + + deleteTextChar: -> + return false if @enteredText == '' + text = @enteredText[...-1] + matchingMarkers = [] + + if text == '' + @resetMarkers() + matchingMarkers = @markers.filter((marker) -> marker.visible) + else + splitEnteredText = @splitEnteredText(text) + for marker in @markers when marker.isComplementary == @isComplementary + if marker.matchText(splitEnteredText) marker.show() - @numEnteredChars -= 1 unless @numEnteredChars == 0 + matchingMarkers.push(marker) + @setHintsForTextFilteredMarkers() + + @enteredText = text + @updateVisualFeedback(matchingMarkers) + return true + updateVisualFeedback: (matchingMarkers) -> + @visualFeedbackUpdater?(this, matchingMarkers) rotateOverlapping: (forward) -> rotateOverlappingMarkers(@markers, forward) @@ -280,4 +456,37 @@ getStackFor = (marker, markers) -> return stack +setHint = (marker, hint) -> marker.setHint(hint) + +# When creating hints after having filtered the markers by their text, it makes +# sense to give the elements with the _smallest_ area the best hints. The idea +# is that the more of the element’s text is matched, the more likely it is to be +# the intended target. +wrapTextFilteredMarker = (marker) -> + return {marker, weight: -marker.wrapper.shape.area} + +compareHints = (markerA, markerB, alphabet) -> + lengthDiff = markerA.hint.length - markerB.hint.length + return lengthDiff unless lengthDiff == 0 + + return 0 if markerA.hint == markerB.hint + + scoresA = getHintCharScores(markerA.hint, alphabet) + scoresB = getHintCharScores(markerB.hint, alphabet) + + sumA = utils.sum(scoresA) + sumB = utils.sum(scoresB) + sumDiff = sumA - sumB + return sumDiff unless sumDiff == 0 + + for scoreA, index in scoresA by -1 + scoreB = scoresB[index] + scoreDiff = scoreA - scoreB + return scoreDiff unless scoreDiff == 0 + + return 0 + +getHintCharScores = (hint, alphabet) -> + return hint.split('').map((char) -> alphabet.indexOf(char) + 1) + module.exports = MarkerContainer diff --git a/extension/lib/marker.coffee b/extension/lib/marker.coffee index a97a2c4..eba4416 100644 --- a/extension/lib/marker.coffee +++ b/extension/lib/marker.coffee @@ -30,20 +30,24 @@ class Marker @elementShape = @wrapper.shape @markerElement = utils.createBox(@document, 'marker') @markerElement.setAttribute('data-type', @wrapper.type) - @weight = @elementShape.area + @weight = @wrapper.combinedArea @width = 0 @height = 0 @hint = '' - @hintIndex = 0 + @originalHint = null + @text = @wrapper.text?.toLowerCase() ? '' @visible = true @zoom = 1 @viewport = null @position = null @originalPosition = null + @dx = 0 + @dy = 0 reset: -> - @setHint(@hint) + @setHint(@originalHint) @show() + @refreshPosition() show: -> @setVisibility(true) hide: -> @setVisibility(false) @@ -55,22 +59,41 @@ class Marker # into the DOM, and thus gotten a width and height. setPosition: (@viewport, @zoom) -> { - markerElement: {clientWidth, clientHeight} - elementShape: {nonCoveredPoint: {x: left, y: top, offset}} - } = this + textOffset + width: elementWidth + nonCoveredPoint: {x: left, y: top, offset} + } = @elementShape - @width = clientWidth / @zoom - @height = clientHeight / @zoom + rect = @markerElement.getBoundingClientRect() + + @width = rect.width / @zoom + @height = rect.height / @zoom # Center the marker vertically on the non-covered point. top -= Math.ceil(@height / 2) + if textOffset? + # Move the marker just to the left of the text of its element. + left -= Math.max(0, @width - textOffset) + else + # Otherwise make sure that it doesn’t flow outside the right side of its + # element. This is to avoid the following situation (where `+` is a small + # button, `Link text` is a (larger) link and `DAG` and `E` are the hints + # placed on top of them.) This makes it clearer which hint does what. + # Example site: Hackernews. + # + # z-layer before after + # bottom +Link text +Link text + # middle DAG DAG + # top E E + left -= Math.max(0, @width - elementWidth) + # Make the position relative to the top frame. left += offset.left top += offset.top @originalPosition = {left, top} - @moveTo(left, top) + @moveTo(left + @dx, top + @dy) moveTo: (left, top) -> # Make sure that the marker stays within the viewport. @@ -93,35 +116,35 @@ class Marker top, bottom: top + @height, } - updatePosition: (dx, dy) -> - @moveTo(@originalPosition.left + dx, @originalPosition.top + dy) + updatePosition: (@dx, @dy) -> + @moveTo(@originalPosition.left + @dx, @originalPosition.top + @dy) + + refreshPosition: -> + @setPosition(@viewport, @zoom) setHint: (@hint) -> - @hintIndex = 0 + @originalHint ?= @hint @markerElement.textContent = '' fragment = @document.createDocumentFragment() utils.createBox(@document, 'marker-char', fragment, char) for char in @hint @markerElement.appendChild(fragment) - matchHintChar: (char) -> - if char == @hint[@hintIndex] - @toggleLastHintChar(true) - @hintIndex += 1 - return true - return false + matchHint: (hint) -> + return @hint.startsWith(hint) - deleteHintChar: -> - if @hintIndex > 0 - @hintIndex -= 1 - @toggleLastHintChar(false) + matchText: (strings) -> + return strings.every((string) => @text.includes(string)) - toggleLastHintChar: (visible) -> - @markerElement.children[@hintIndex] - .classList.toggle('marker-char--matched', visible) - - isMatched: -> (@hintIndex == @hint.length) + markMatchedPart: (hint) -> + matchEnd = if @matchHint(hint) then hint.length else 0 + for child, index in @markerElement.children + child.classList.toggle('marker-char--matched', index < matchEnd) + return markMatched: (matched) -> @markerElement.classList.toggle('marker--matched', matched) + markHighlighted: (highlighted) -> + @markerElement.classList.toggle('marker--highlighted', highlighted) + module.exports = Marker diff --git a/extension/lib/migrations.coffee b/extension/lib/migrations.coffee index f14cde4..12c3f50 100644 --- a/extension/lib/migrations.coffee +++ b/extension/lib/migrations.coffee @@ -163,4 +163,18 @@ migrations[5] = -> unless oldValue == '' prefs.set('blacklist', oldValue.replace(/,(?:\s+|(?=\*))/g, ' ')) +migrations[6] = -> + prefMap = { + 'hint_chars': 'hints.chars' + 'hints_sleep': 'hints.sleep' + 'hints_timeout': 'hints.matched_timeout' + 'hints_peek_through': 'hints.peek_through' + 'hints_toggle_in_tab': 'hints.toggle_in_tab' + 'hints_toggle_in_background': 'hints.toggle_in_background' + 'mode.hints.delete_hint_char': 'mode.hints.delete_char' + } + + for pref, newPref of prefMap when prefs.has(pref) + prefs.set(newPref, prefs.get(pref)) + module.exports = migrations diff --git a/extension/lib/modes.coffee b/extension/lib/modes.coffee index 6c0eac8..b888ab9 100644 --- a/extension/lib/modes.coffee +++ b/extension/lib/modes.coffee @@ -1,7 +1,5 @@ ### -# Copyright Anton Khodakivskiy 2013, 2014. -# Copyright Simon Lydell 2013, 2014, 2015, 2016. -# Copyright Wang Zhuochun 2014. +# Copyright Simon Lydell 2014, 2015, 2016. # # This file is part of VimFx. # @@ -25,6 +23,7 @@ {commands, findStorage} = require('./commands') defaults = require('./defaults') help = require('./help') +hintsMode = require('./hints-mode') prefs = require('./prefs') SelectionManager = require('./selection') translate = require('./translate') @@ -193,10 +192,19 @@ mode('caret', { mode('hints', { onEnter: ({vim, storage}, options) -> - {markerContainer, callback, count = 1, sleep = -1} = options + { + markerContainer, callback, matchText = true, count = 1, sleep = -1 + } = options storage.markerContainer = markerContainer storage.callback = callback + storage.matchText = matchText storage.count = count + storage.skipOnLeaveCleanup = false + + if matchText + markerContainer.visualFeedbackUpdater = + hintsMode.updateVisualFeedback.bind(null, vim) + vim._run('clear_selection') if sleep >= 0 storage.clearInterval = utils.interval(vim.window, sleep, (next) -> @@ -211,50 +219,32 @@ mode('hints', { ) onLeave: ({vim, storage}) -> - storage.markerContainer?.remove() - storage.clearInterval?() - for key of storage - storage[key] = null - return + hintsMode.cleanup(vim, storage) unless storage.skipOnLeaveCleanup onInput: (args, match) -> {vim, storage} = args - {markerContainer, callback} = storage + {markerContainer, callback, matchText} = storage + changed = false + visibleMarkers = null if match.type == 'full' - match.command.run(args) - - else if match.unmodifiedKey in vim.options.hint_chars - matchedMarkers = markerContainer.matchHintChar(match.unmodifiedKey) - - if matchedMarkers.length > 0 - # Prevent `onLeave` from removing the markers immediately. (The callback - # might enter another mode.) - storage.markerContainer = null - - again = callback(matchedMarkers[0], storage.count, match.keyStr) - storage.count -= 1 + match.command.run(Object.assign({match}, args)) - if again - # Add the container back again. - storage.markerContainer = markerContainer + else + {char, isHintChar} = hintsMode.getChar(match, storage) + return true unless char - vim.window.setTimeout((-> - marker.markMatched(false) for marker in matchedMarkers - return - ), vim.options.hints_timeout) - markerContainer.reset() + visibleMarkers = markerContainer.addChar(char, isHintChar) - else - vim.window.setTimeout((-> - # Don’t remove the marker container if we have re-entered Hints mode - # before the timeout has passed. - markerContainer.remove() unless vim.mode == 'hints' - ), vim.options.hints_timeout) + if (vim.options['hints.auto_activate'] or isHintChar) and + new Set(visibleMarkers.map((marker) -> marker.hint)).size == 1 + hintsMode.activateMatch( + vim, storage, match, visibleMarkers, callback + ) - # The callback might have entered another mode. Only go back to Normal - # mode if we’re still in Hints mode. - vim._enterMode('normal') if vim.mode == 'hints' + unless isHintChar + vim._parent.ignoreKeyEventsUntilTime = + Date.now() + vim.options['hints.timeout'] return true @@ -262,14 +252,25 @@ mode('hints', { exit: ({vim}) -> vim._enterMode('normal') + activate_highlighted: ({vim, storage, match}) -> + {markerContainer: {markers, highlightedMarkers}, callback} = storage + return if highlightedMarkers.length == 0 + + for marker in markers when marker.visible + marker.hide() unless marker in highlightedMarkers + + hintsMode.activateMatch( + vim, storage, match, highlightedMarkers, callback + ) + rotate_markers_forward: ({storage}) -> storage.markerContainer.rotateOverlapping(true) rotate_markers_backward: ({storage}) -> storage.markerContainer.rotateOverlapping(false) - delete_hint_char: ({storage}) -> - storage.markerContainer.deleteHintChar() + delete_char: ({storage}) -> + storage.markerContainer.deleteChar() increase_count: ({storage}) -> storage.count += 1 diff --git a/extension/lib/parse-prefs.coffee b/extension/lib/parse-prefs.coffee index 89d8ac6..06762fc 100644 --- a/extension/lib/parse-prefs.coffee +++ b/extension/lib/parse-prefs.coffee @@ -51,7 +51,7 @@ parseSpaceDelimitedString = (value) -> parseHintChars = (value, defaultValue) -> [leading..., end] = value.trim().split(/\s+/) parsed = if leading.length > 0 then "#{leading.join('')} #{end}" else end - parsed = utils.removeDuplicateCharacters(parsed) + parsed = utils.removeDuplicateChars(parsed) # Make sure that hint chars contain at least the required amount of chars. diff = MIN_NUM_HINT_CHARS - parsed.length @@ -95,7 +95,7 @@ parseBlacklist = (value) -> return result parsers = { - hint_chars: parseHintChars + 'hints.chars': parseHintChars prev_patterns: parsePatterns next_patterns: parsePatterns diff --git a/extension/lib/utils.coffee b/extension/lib/utils.coffee index 4b90c97..1ebeb0a 100644 --- a/extension/lib/utils.coffee +++ b/extension/lib/utils.coffee @@ -396,6 +396,17 @@ createBox = (document, className = '', parent = null, text = null) -> parent.appendChild(box) if parent? return box +getFirstNonEmptyTextNodeBoxQuads = (element) -> + for node in element.childNodes then switch node.nodeType + when 3 # TextNode. + unless node.data.trim() == '' + boxQuads = node.getBoxQuads() + return boxQuads if boxQuads?.length > 0 + when 1 # Element. + result = getFirstNonEmptyTextNodeBoxQuads(node) + return result if result + return null + # In quirks mode (when the page lacks a doctype), such as on Hackernews, # `` is considered the root element rather than ``. getRootElement = (document) -> @@ -404,6 +415,10 @@ getRootElement = (document) -> else return document.documentElement +getText = (element) -> + text = element.textContent or element.value or element.placeholder or '' + return text.trim().replace(/\s+/, ' ') + getTopOffset = (element) -> window = element.ownerGlobal {left: x, top: y} = element.getBoundingClientRect() @@ -443,6 +458,49 @@ querySelectorAllDeep = (window, selector) -> elements.push(querySelectorAllDeep(frame, selector)...) return elements +selectAllSubstringMatches = (element, substring, {caseSensitive = true} = {}) -> + window = element.ownerGlobal + selection = window.getSelection() + {textContent} = element + + format = (string) -> if caseSensitive then string else string.toLowerCase() + offsets = + getAllNonOverlappingRangeOffsets(format(textContent), format(substring)) + offsetsLength = offsets.length + return if offsetsLength == 0 + + textIndex = 0 + offsetsIndex = 0 + [currentOffset] = offsets + searchIndex = currentOffset.start + start = null + + walkTextNodes(element, (textNode) -> + {length} = textNode.data + return false if length == 0 + + while textIndex + length > searchIndex + if start + range = window.document.createRange() + range.setStart(start.textNode, start.offset) + range.setEnd(textNode, currentOffset.end - textIndex) + selection.addRange(range) + + offsetsIndex += 1 + return true if offsetsIndex >= offsetsLength + currentOffset = offsets[offsetsIndex] + + start = null + searchIndex = currentOffset.start + + else + start = {textNode, offset: currentOffset.start - textIndex} + searchIndex = currentOffset.end - 1 + + textIndex += length + return false + ) + setAttributes = (element, attributes) -> for attribute, value of attributes element.setAttribute(attribute, value) @@ -455,6 +513,16 @@ setHover = (element, hover) -> element = element.parentElement return +walkTextNodes = (element, fn) -> + for node in element.childNodes then switch node.nodeType + when 3 # TextNode. + stop = fn(node) + return true if stop + when 1 # Element. + stop = walkTextNodes(node, fn) + return true if stop + return false + # Language helpers @@ -514,6 +582,25 @@ bisect = (min, max, fn) -> else [null, null] +getAllNonOverlappingRangeOffsets = (string, substring) -> + {length} = substring + return [] if length == 0 + + offsets = [] + lastOffset = {start: -Infinity, end: -Infinity} + index = -1 + + loop + index = string.indexOf(substring, index + 1) + break if index == -1 + if index > lastOffset.end + lastOffset = {start: index, end: index + length} + offsets.push(lastOffset) + else + lastOffset.end = index + length + + return offsets + has = (obj, prop) -> Object::hasOwnProperty.call(obj, prop) # Check if `search` exists in `string` (case insensitively). Returns `false` if @@ -539,12 +626,12 @@ nextTick = (window, fn) -> window.setTimeout((-> fn()) , 0) regexEscape = (s) -> s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') -# Remove duplicate characters from string (case insensitive). -removeDuplicateCharacters = (str) -> - return removeDuplicates( str.toLowerCase().split('') ).join('') +removeDuplicateChars = (string) -> removeDuplicates(string.split('')).join('') removeDuplicates = (array) -> Array.from(new Set(array)) +sum = (numbers) -> numbers.reduce(((sum, number) -> sum + number), 0) + # Misc helpers @@ -644,7 +731,9 @@ module.exports = { clearSelectionDeep containsDeep createBox + getFirstNonEmptyTextNodeBoxQuads getRootElement + getText getTopOffset injectTemporaryPopup insertText @@ -652,19 +741,23 @@ module.exports = { isNonEmptyTextNode isPositionFixed querySelectorAllDeep + selectAllSubstringMatches setAttributes setHover + walkTextNodes Counter EventEmitter bisect + getAllNonOverlappingRangeOffsets has includes interval nextTick regexEscape - removeDuplicateCharacters + removeDuplicateChars removeDuplicates + sum expandPath formatError diff --git a/extension/lib/viewport.coffee b/extension/lib/viewport.coffee index 0899ad1..151c69f 100644 --- a/extension/lib/viewport.coffee +++ b/extension/lib/viewport.coffee @@ -96,15 +96,15 @@ getAllRangesInsideViewport = (window, viewport, offset = {left: 0, top: 0}) -> getFirstNonWhitespace = (element) -> window = element.ownerGlobal viewport = getWindowViewport(window) - for node in element.childNodes then switch node.nodeType - when 3 # TextNode. - continue unless /\S/.test(node.data) - offset = getFirstVisibleNonWhitespaceOffset(node, viewport) - return [node, offset] if offset >= 0 - when 1 # Element. - result = getFirstNonWhitespace(node) - return result if result - return null + result = null + utils.walkTextNodes(element, (textNode) -> + return false unless /\S/.test(textNode.data) + offset = getFirstVisibleNonWhitespaceOffset(textNode, viewport) + if offset >= 0 + result = [textNode, offset] + return true + ) + return result getFirstVisibleNonWhitespaceOffset = (textNode, viewport) -> firstVisibleOffset = getFirstVisibleOffset(textNode, viewport) diff --git a/extension/lib/vimfx.coffee b/extension/lib/vimfx.coffee index d5898cc..2af9be2 100644 --- a/extension/lib/vimfx.coffee +++ b/extension/lib/vimfx.coffee @@ -35,6 +35,7 @@ class VimFx extends utils.EventEmitter @vims = new WeakMap() @lastClosedVim = null @goToCommand = null + @ignoreKeyEventsUntilTime = 0 @skipCreateKeyTrees = false @createKeyTrees() @reset() @@ -86,6 +87,9 @@ class VimFx extends utils.EventEmitter return unless keyStr = @stringifyKeyEvent(event) now = Date.now() + + return true if now <= @ignoreKeyEventsUntilTime + @reset(mode) if now - @lastInputTime >= @options.timeout @lastInputTime = now @@ -134,8 +138,9 @@ class VimFx extends utils.EventEmitter @reset(mode) if type == 'full' return { - type, command, count, specialKeys, keyStr, unmodifiedKey, toplevel - likelyConflict + type, command, count, toplevel + specialKeys, keyStr, unmodifiedKey, likelyConflict + rawKey: event.key, rawCode: event.code discard: @reset.bind(this, mode) } diff --git a/extension/locale/de/vimfx.properties b/extension/locale/de/vimfx.properties index 202026b..1f2893b 100644 --- a/extension/locale/de/vimfx.properties +++ b/extension/locale/de/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Zum Normalmodus zurückkehren mode.hints=Hinweismodus mode.hints.exit=Zum Normalmodus zurückkehren +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Überlappende Markierungen vorwärts drehen mode.hints.rotate_markers_backward=Überlappende Markierungen rückwärts drehen -mode.hints.delete_hint_char=Lösche zuletzt eingegebenes Hinweiszeichen +mode.hints.delete_char=Lösche zuletzt eingegebenes Zeichen mode.hints.increase_count=Anzahl erhöhen mode.hints.toggle_complementary=Alle anderen Elemente markieren mode.hints.peek_through=Gedrückt halten um durch Markierungen zu spähen @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Konfigurationsfile konnte nicht neu gela notification.toggle_selection.enter=Bewegen des Carets selektiert nun Text notification.copy_selection_and_exit.none=Kein Text fürs Kopieren selektiert -pref.hint_chars.title=Zeichen für Hinweise -pref.hint_chars.desc= +pref.prevent_autofocus.title=Eingabefelder nicht automatisch fokussieren +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignoriere Tastaturbelegung +pref.ignore_keyboard_layout.desc=So tun, als ob die US-amerikanische QWERTY-Belegung immer benutzt wird pref.blacklist.title=Schwarze Liste pref.blacklist.desc=Liste von URLs, für welche VimFX automatisch in the Ignoriermodus wechseln soll. Benütze Leerstellen as Trennzeichen und * als Platzhalter. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title="Vorheriger" Link Muster -pref.prev_patterns.desc= - -pref.next_patterns.title="Nächster" Link Muster -pref.next_patterns.desc= +pref.hints.chars.title=Zeichen für Hinweise +pref.hints.chars.desc= -pref.prevent_autofocus.title=Eingabefelder nicht automatisch fokussieren -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignoriere Tastaturbelegung -pref.ignore_keyboard_layout.desc=So tun, als ob die US-amerikanische QWERTY-Belegung immer benutzt wird +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Zeitbeschränkung pref.timeout.desc=Die maximale Anzahl von Millisekunden zwischen zwei Tastendrücken eines Tastaturkürzels +pref.prev_patterns.title="Vorheriger" Link Muster +pref.prev_patterns.desc= + +pref.next_patterns.title="Nächster" Link Muster +pref.next_patterns.desc= + prefs.instructions.title=Tastaturkürzel in Textfeldern auf dieser Seite prefs.instructions.desc=%S Nächsten Tastendruck als Text einfügen\n%S Voreinstellung einfügen\n%S Auf Voreinstellung zurücksetzen\n%S Rückgängig machen diff --git a/extension/locale/en-US/vimfx.properties b/extension/locale/en-US/vimfx.properties index af5d660..9072c6d 100644 --- a/extension/locale/en-US/vimfx.properties +++ b/extension/locale/en-US/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Return to Normal mode mode.hints=Hints mode mode.hints.exit=Return to Normal mode +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Rotate overlapping markers forward mode.hints.rotate_markers_backward=Rotate overlapping markers backward -mode.hints.delete_hint_char=Delete last typed hint character +mode.hints.delete_char=Delete last typed character mode.hints.increase_count=Increase count mode.hints.toggle_complementary=Mark all other elements mode.hints.peek_through=Hold to peek through markers @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Error reloading config file; check the b notification.toggle_selection.enter=Moving the caret now selects text notification.copy_selection_and_exit.none=No text selection to copy -pref.hint_chars.title=Hint chars -pref.hint_chars.desc= +pref.prevent_autofocus.title=Prevent autofocus of text inputs +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignore keyboard layout +pref.ignore_keyboard_layout.desc=Pretend that the en-US QWERTY layout is always used. pref.blacklist.title=Blacklist pref.blacklist.desc=List of URL patterns where VimFx should automatically enter Ignore mode. Use spaces as delimiter and * as a wildcard. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=“Previous” link patterns -pref.prev_patterns.desc= - -pref.next_patterns.title=“Next” link patterns -pref.next_patterns.desc= +pref.hints.chars.title=Hint characters +pref.hints.chars.desc=The set of characters for hint markers. Some prefer all uppercase, or numbers only. All other characters are used to filter by element text (case insensitively). -pref.prevent_autofocus.title=Prevent autofocus of text inputs -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignore keyboard layout -pref.ignore_keyboard_layout.desc=Pretend that the en-US QWERTY layout is always used. +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Timeout pref.timeout.desc=The maximum number of milliseconds between two keys of a shortcut. +pref.prev_patterns.title=“Previous” link patterns +pref.prev_patterns.desc= + +pref.next_patterns.title=“Next” link patterns +pref.next_patterns.desc= + prefs.instructions.title=Keyboard shortcuts in text boxes on this page prefs.instructions.desc=%S Insert next keypress as text\n%S Insert default\n%S Reset to default\n%S Undo diff --git a/extension/locale/es/vimfx.properties b/extension/locale/es/vimfx.properties index afa2e43..3393e45 100644 --- a/extension/locale/es/vimfx.properties +++ b/extension/locale/es/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Volver al modo Normal mode.hints=Modo Indicaciones mode.hints.exit=Volver al modo Normal +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Rotar indicaciones superpuestas hacia delante mode.hints.rotate_markers_backward=Rotar indicaciones superpuestas hacia atrás -mode.hints.delete_hint_char=Borrar último caracter de indicación tecleado +mode.hints.delete_char=Borrar último caracter tecleado mode.hints.increase_count=Incrementar contador mode.hints.toggle_complementary=Marcar todos los demás elementos mode.hints.peek_through=Mantener pulsado para ojear por las marcas Hold to peek through markers @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Error al volver a cargar el fichero de c notification.toggle_selection.enter=Mover el caret/circunflejo (^) ahora selecciona texto notification.copy_selection_and_exit.none=No hay selección de texto que copiar -pref.hint_chars.title=Caracteres de indicación -pref.hint_chars.desc= +pref.prevent_autofocus.title=Evitar autofoco de los campos de entrada de texto +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignorar disposición del teclado +pref.ignore_keyboard_layout.desc=Presuponer siempre el uso de teclado QWERTY en-US. pref.blacklist.title=Lista negra pref.blacklist.desc=Lista de URLs donde VimFx debe entrar automáticamente en modo Ignorar. Use espacios como delimitador y * como comodín. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Patrones de enlaces tipo “Anterior" -pref.prev_patterns.desc= - -pref.next_patterns.title=Patrones de enlaces tipo “Siguiente” -pref.next_patterns.desc= +pref.hints.chars.title=Caracteres de indicación +pref.hints.chars.desc= -pref.prevent_autofocus.title=Evitar autofoco de los campos de entrada de texto -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignorar disposición del teclado -pref.ignore_keyboard_layout.desc=Presuponer siempre el uso de teclado QWERTY en-US. +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Intervalo pref.timeout.desc=Número máximo de milisegundos entre dos pulsaciones de un atajo de teclado. +pref.prev_patterns.title=Patrones de enlaces tipo “Anterior" +pref.prev_patterns.desc= + +pref.next_patterns.title=Patrones de enlaces tipo “Siguiente” +pref.next_patterns.desc= + prefs.instructions.title=Atajos de teclado en cuadros de texto en esta página prefs.instructions.desc=%S Insertar las próximas pulsaciones de teclado como texto\n%S Insertar predeterminado\n%S Restablecer valores predeterminados\n%S Deshacer diff --git a/extension/locale/fr/vimfx.properties b/extension/locale/fr/vimfx.properties index 624b63b..61dfc62 100644 --- a/extension/locale/fr/vimfx.properties +++ b/extension/locale/fr/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Retourner en mode Normal mode.hints=Mode de sélection de lien mode.hints.exit=Retourner au mode initial +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Faire tourner vers l'avant les marqueurs superposés mode.hints.rotate_markers_backward=Faire tourner en arrière -mode.hints.delete_hint_char=Supprimer le dernier caractère frappé lors de la sélection d'un marqueur +mode.hints.delete_char=Supprimer le dernier caractère frappé lors de la sélection d'un marqueur mode.hints.increase_count=Augmenter le nombre mode.hints.toggle_complementary=Marquer tous les autres éléments mode.hints.peek_through=Maintenir pour voir à travers les marqueurs @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Erreur lors du rechargement du fichier d notification.toggle_selection.enter=Déplacer le curseur sélectionne maintenant du texte notification.copy_selection_and_exit.none=Pas de texte sélectionné à copier -pref.hint_chars.title=Marqueurs de liens -pref.hint_chars.desc= +pref.prevent_autofocus.title=Éviter la focalisation automatique sur entées de formulaires +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignorer la disposition du clavier +pref.ignore_keyboard_layout.desc=Faire comme si le clavier était un clavier QWERTY en-US. pref.blacklist.title=Liste noire pref.blacklist.desc=Liste d'adresses pour lesquelles VimFx doit automatiquement entrer en mode Ignorer. Utilisez les espaces comme séparateurs et « * » comme joker. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Motifs pour les liens « Précédent » -pref.prev_patterns.desc= - -pref.next_patterns.title=Motifs pour les liens « Suivant » -pref.next_patterns.desc= +pref.hints.chars.title=Marqueurs de liens +pref.hints.chars.desc= -pref.prevent_autofocus.title=Éviter la focalisation automatique sur entées de formulaires -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignorer la disposition du clavier -pref.ignore_keyboard_layout.desc=Faire comme si le clavier était un clavier QWERTY en-US. +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Délai pref.timeout.desc=Le temps maximum en millisecondes entre deux touches d'un raccourci clavier. +pref.prev_patterns.title=Motifs pour les liens « Précédent » +pref.prev_patterns.desc= + +pref.next_patterns.title=Motifs pour les liens « Suivant » +pref.next_patterns.desc= + prefs.instructions.title=Raccourcis clavier dans les boîtes d'entrée de texte de cette page prefs.instructions.desc=%S Insérer la combinaison de touches suivante comme du texte\n%S Insérer la valeur par défaut\n%S Remettre la valeur par défaut\n%S Annuler diff --git a/extension/locale/id/vimfx.properties b/extension/locale/id/vimfx.properties index 3314c7d..083d593 100644 --- a/extension/locale/id/vimfx.properties +++ b/extension/locale/id/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Kembali ke mode Normal mode.hints=Mode Petunjuk mode.hints.exit=Kembali ke mode Normal +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Putar maju penanda bertumpukan mode.hints.rotate_markers_backward=Putar mundur penanda bertumpukan -mode.hints.delete_hint_char=Hapus karakter petunjuk terakhir diketik +mode.hints.delete_char=Hapus karakter terakhir diketik mode.hints.increase_count=Naikkan hitungan mode.hints.toggle_complementary=Tandai semua elemen lain mode.hints.peek_through=Tahan untuk melirik melalui penanda @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Kesalahan memuat berkas pengaturan; cek notification.toggle_selection.enter=Memindahkan tanda sisipan (caret) akan memilih teks notification.copy_selection_and_exit.none=Tidak ada pilihan teks untuk disalin -pref.hint_chars.title=Karakter Petunjuk -pref.hint_chars.desc= +pref.prevent_autofocus.title=Cegah autofokus pada masukan teks +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Abaikan layout keyboard +pref.ignore_keyboard_layout.desc=Anggap layout en-US QWERTY selalu digunakan. pref.blacklist.title=Daftar Hitam pref.blacklist.desc=Daftar URL dimana VimFx harus masuk mode Abai secara otomatis. Gunakan spasi sebagai pemisah dan * sebagai wildcard. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Pola link “Previous” -pref.prev_patterns.desc= - -pref.next_patterns.title=Pola link “Next” -pref.next_patterns.desc= +pref.hints.chars.title=Karakter Petunjuk +pref.hints.chars.desc= -pref.prevent_autofocus.title=Cegah autofokus pada masukan teks -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Abaikan layout keyboard -pref.ignore_keyboard_layout.desc=Anggap layout en-US QWERTY selalu digunakan. +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Waktu Timeout pref.timeout.desc=Jumlah maksimum dalam milidetik diantara penekanan dua tombol shortcut. +pref.prev_patterns.title=Pola link “Previous” +pref.prev_patterns.desc= + +pref.next_patterns.title=Pola link “Next” +pref.next_patterns.desc= + prefs.instructions.title=Shortcut keyboard ke kotak teks pada halaman ini prefs.instructions.desc=%S Masukkan tekan tombol berikut sebagai teks\n%S Masukkan default\n%S Setel ulang ke default\n%S Kembalikan diff --git a/extension/locale/it/vimfx.properties b/extension/locale/it/vimfx.properties index 6216fab..73fd481 100644 --- a/extension/locale/it/vimfx.properties +++ b/extension/locale/it/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Ritorna alla modalità normale mode.hints=Modalità suggerimenti mode.hints.exit=Ritorna alla modalità normale +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Ruota i marcatori sovrappossti in avanti mode.hints.rotate_markers_backward=Ruota i marcatori sovrappossti all'indietro -mode.hints.delete_hint_char=Cancella l'ultimo carattere di suggerimento digitato +mode.hints.delete_char=Cancella l'ultimo carattere digitato mode.hints.increase_count=Aumenta il conteggio mode.hints.toggle_complementary=Marca tutti gli altri elementi mode.hints.peek_through=Tieni premuto per dare un'occhiata alle marcature @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Errore ricaricando il file di configuraz notification.toggle_selection.enter=Spostando il cursore verrà selezionato il testo notification.copy_selection_and_exit.none=Nessuna selezione di testo da copiare -pref.hint_chars.title=Caratteri di suggerimento -pref.hint_chars.desc= +pref.prevent_autofocus.title=Previene il focus automatico delle caselle di input +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignora il layout della tastiera +pref.ignore_keyboard_layout.desc=Fai finta che il layout en-US QWERTY sia sempre attivo pref.blacklist.title=Lista nera pref.blacklist.desc=Lista di URL per cui VimFx deve impostare automatica la modalità Ignore. Utilizza gli spazi come delimitatore e il simbolo * come carattere jolly. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Pattern link "Precedente" -pref.prev_patterns.desc= - -pref.next_patterns.title=Pattern link “Successivo” -pref.next_patterns.desc= +pref.hints.chars.title=Caratteri di suggerimento +pref.hints.chars.desc= -pref.prevent_autofocus.title=Previene il focus automatico delle caselle di input -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignora il layout della tastiera -pref.ignore_keyboard_layout.desc=Fai finta che il layout en-US QWERTY sia sempre attivo +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Timeout pref.timeout.desc=Il numero massimo di millisecondi che può interrcorrere fra la pressione di due tasti che compongono una scorciatoia +pref.prev_patterns.title=Pattern link "Precedente" +pref.prev_patterns.desc= + +pref.next_patterns.title=Pattern link “Successivo” +pref.next_patterns.desc= + prefs.instructions.title=Scorciatoie di tastiera per i campi di testo in questa pagina prefs.instructions.desc=%S Inserisci il prossimo carattere come testo\n%S Inserisci il carattere predefinito\n%S Ripristina il carattere predefinito\n%S Annulla diff --git a/extension/locale/ja/vimfx.properties b/extension/locale/ja/vimfx.properties index b2aae34..f0fbd26 100644 --- a/extension/locale/ja/vimfx.properties +++ b/extension/locale/ja/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=ノーマルモードに戻る mode.hints=ヒントモード mode.hints.exit=ノーマルモードへ戻る +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=重なったマーカーを前へ入れ替え mode.hints.rotate_markers_backward=重なったマーカーを後へ入れ替え -mode.hints.delete_hint_char=最後に入力したヒント文字を削除 +mode.hints.delete_char=最後に入力したヒント文字を削除 mode.hints.increase_count=カウントを増やす mode.hints.toggle_complementary=他の全ての要素を選択 mode.hints.peek_through=長押ししている間マーカーを覗き見る @@ -160,28 +161,34 @@ notification.reload_config_file.failure=設定ファイルの再読込みに失 notification.toggle_selection.enter=キャレットを現在選択中のテキストに移動します notification.copy_selection_and_exit.none=コピーする選択範囲がありません -pref.hint_chars.title=ヒント機能で使用する文字 -pref.hint_chars.desc= +pref.prevent_autofocus.title=入力欄によるフォーカスの奪取を妨害 +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=キーボードレイアウトを無視 +pref.ignore_keyboard_layout.desc=いつもUS-QWERTYキーボードが使われていると仮定します pref.blacklist.title=ブラックリスト pref.blacklist.desc=VimFxが自動的に無視モードに入るURLのリスト。スペースで区切り、*をワイルドカードに使えます The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=「前へ」リンクのパターン -pref.prev_patterns.desc= - -pref.next_patterns.title=「次へ」リンクのパターン -pref.next_patterns.desc= +pref.hints.chars.title=ヒント機能で使用する文字 +pref.hints.chars.desc= -pref.prevent_autofocus.title=入力欄によるフォーカスの奪取を妨害 -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=キーボードレイアウトを無視 -pref.ignore_keyboard_layout.desc=いつもUS-QWERTYキーボードが使われていると仮定します +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=タイムアウト pref.timeout.desc=一つのショートカットでのキー入力間の許容時間(ミリ秒) +pref.prev_patterns.title=「前へ」リンクのパターン +pref.prev_patterns.desc= + +pref.next_patterns.title=「次へ」リンクのパターン +pref.next_patterns.desc= + prefs.instructions.title=このページのテキストボックスでのキーボードショートカット prefs.instructions.desc=%S 次のキー入力をテキストとして挿入\n%S 既定の挿入\n%S 既定にリセット\n%S 元に戻す diff --git a/extension/locale/nl/vimfx.properties b/extension/locale/nl/vimfx.properties index 1339c2f..c448640 100644 --- a/extension/locale/nl/vimfx.properties +++ b/extension/locale/nl/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Ga terug naar normale modus mode.hints=Navigeermodus mode.hints.exit=Ga terug naar normale modus +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Ga vooruit in markervolgorde mode.hints.rotate_markers_backward=Ga achteruit in markervolgorde -mode.hints.delete_hint_char=Verwijder het laatst getypte markerkarakter +mode.hints.delete_char=Verwijder het laatst getypte karakter mode.hints.increase_count=Verhoog aantal mode.hints.toggle_complementary=Selecteer alle andere elementen mode.hints.peek_through=Houd ingedrukt om door markeringen heen te kijken @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Error bij het opnieuw laden van het conf notification.toggle_selection.enter=Het bewegen van de cursor selecteert nu tekst notification.copy_selection_and_exit.none=Geen geselecteerde tekst om te kopiëren -pref.hint_chars.title=Gebruikte karakters -pref.hint_chars.desc= +pref.prevent_autofocus.title=Voorkom autofocus van invoervelden +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Negeer toetsenbordindeling +pref.ignore_keyboard_layout.desc=Neem aan dat de toetsenbordindeling altijd en-US QWERTY is pref.blacklist.title=Zwarte lijst pref.blacklist.desc=Lijst van URLS waar VimFx automatisch negeermodus aan moet zetten. Gebruik spatie als scheiding en * als wildcard. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Voorbeelden van "vorige"-links -pref.prev_patterns.desc= - -pref.next_patterns.title=Voorbeelden van "volgende"-links -pref.next_patterns.desc= +pref.hints.chars.title=Gebruikte karakters +pref.hints.chars.desc= -pref.prevent_autofocus.title=Voorkom autofocus van invoervelden -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Negeer toetsenbordindeling -pref.ignore_keyboard_layout.desc=Neem aan dat de toetsenbordindeling altijd en-US QWERTY is +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Vertraging pref.timeout.desc=Het maximale aantal milliseconden tussen twee toetsen in een sneltoets +pref.prev_patterns.title=Voorbeelden van "vorige"-links +pref.prev_patterns.desc= + +pref.next_patterns.title=Voorbeelden van "volgende"-links +pref.next_patterns.desc= + prefs.instructions.title=Sneltoetsen in invoervelden op deze pagina prefs.instructions.desc=%S Voer volgende toetsaanslag in als text\n%S Voer standaard in\n%S Reset naar standaard\n%S Ongedaan maken diff --git a/extension/locale/pt-BR/vimfx.properties b/extension/locale/pt-BR/vimfx.properties index 3337269..a7281d1 100644 --- a/extension/locale/pt-BR/vimfx.properties +++ b/extension/locale/pt-BR/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Voltar ao modo Normal mode.hints=Modo Sugestões mode.hints.exit=Voltar ao modo Normal +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Rotacionar marcadores sobrepostos para frente mode.hints.rotate_markers_backward=Rotacionar marcadores sobrepostos para trás -mode.hints.delete_hint_char=Deletar o último caractere digitado da sugestão +mode.hints.delete_char=Deletar o último caractere digitado mode.hints.increase_count=Incrementar contador mode.hints.toggle_complementary=Marcar todos os outros elementos mode.hints.peek_through=Segure para espiar através de marcadores @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Erro ao recarregar arquivo de configura notification.toggle_selection.enter=Mover o cursor agora seleciona texto notification.copy_selection_and_exit.none=Nenhuma seleção de texto a ser copiada -pref.hint_chars.title=Caracteres de Sugestão -pref.hint_chars.desc= +pref.prevent_autofocus.title=Previnir autofocus em campos texto +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignorar disposição do teclado +pref.ignore_keyboard_layout.desc=Simular que a disposição en-US QWERTY é sempre usada. pref.blacklist.title=Lista Negra pref.blacklist.desc=Lista de URLs que o VimFx deve automaticamente entrar em modo Ignorar. Use espaço como delimitador e * como curinga. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Padrões de link “Anterior” -pref.prev_patterns.desc= - -pref.next_patterns.title=Padrões de link “Próximo” -pref.next_patterns.desc= +pref.hints.chars.title=Caracteres de Sugestão +pref.hints.chars.desc= -pref.prevent_autofocus.title=Previnir autofocus em campos texto -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Ignorar disposição do teclado -pref.ignore_keyboard_layout.desc=Simular que a disposição en-US QWERTY é sempre usada. +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Timeout pref.timeout.desc=O número máximo de milisegundos entre duas teclas de um atalho. +pref.prev_patterns.title=Padrões de link “Anterior” +pref.prev_patterns.desc= + +pref.next_patterns.title=Padrões de link “Próximo” +pref.next_patterns.desc= + prefs.instructions.title=Teclas de atalho em caixas de texto nesta página prefs.instructions.desc=%S Inserir próximo keypress como texto\n%S Inserir padrão\n%S Redefinir para padrão\n%S Desfazer diff --git a/extension/locale/ru/vimfx.properties b/extension/locale/ru/vimfx.properties index 8761582..b564420 100644 --- a/extension/locale/ru/vimfx.properties +++ b/extension/locale/ru/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Вернуться в нормальный режим mode.hints=Режим маркеров mode.hints.exit=Вернуться в нормальный режим +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=Переставить перекрывающиеся маркеры mode.hints.rotate_markers_backward=Переставить перекрывающиеся маркеры в обратном порядке -mode.hints.delete_hint_char=Удалить последний введённый символ +mode.hints.delete_char=Удалить последний введённый символ mode.hints.increase_count=Увеличить количество mode.hints.toggle_complementary=Маркировать все прочие элементы mode.hints.peek_through=Удерживайте, чтобы заглянуть под маркеры @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Ошибка при перезагруз notification.toggle_selection.enter=Для выделения текста перемещайте курсор notification.copy_selection_and_exit.none=Нет выделения текста для копирования -pref.hint_chars.title=Символы на маркерах -pref.hint_chars.desc= +pref.prevent_autofocus.title=Не фокусировать поля ввода автоматически +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Игнорировать раскладку клавиатуры +pref.ignore_keyboard_layout.desc=Считать, что всегда используется раскладка en-US QWERTY pref.blacklist.title=Стоп список pref.blacklist.desc=Список URL, где VimFx автоматически входит в режим игнорирования. Используйте пробелы в качестве разделителей, а * в качестве символа-джокера. The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=Шаблоны «предыдущих» ссылок -pref.prev_patterns.desc= - -pref.next_patterns.title=Шаблоны «следующих» ссылок -pref.next_patterns.desc= +pref.hints.chars.title=Символы на маркерах +pref.hints.chars.desc= -pref.prevent_autofocus.title=Не фокусировать поля ввода автоматически -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=Игнорировать раскладку клавиатуры -pref.ignore_keyboard_layout.desc=Считать, что всегда используется раскладка en-US QWERTY +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=Таймаут pref.timeout.desc=Максимальное количество миллисекунд между двумя нажатиями клавиш одной команды. +pref.prev_patterns.title=Шаблоны «предыдущих» ссылок +pref.prev_patterns.desc= + +pref.next_patterns.title=Шаблоны «следующих» ссылок +pref.next_patterns.desc= + prefs.instructions.title=Горячие клавиши в текстовых полях на этой странице prefs.instructions.desc=%S Вставить следующее нажатие как текст\n%S Вставить значение по умолчанию\n%S Сбрость к значению по умолчанию\n%S Отменить diff --git a/extension/locale/sv-SE/vimfx.properties b/extension/locale/sv-SE/vimfx.properties index 6b53966..01f3b6e 100644 --- a/extension/locale/sv-SE/vimfx.properties +++ b/extension/locale/sv-SE/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=Återvänd till Normalläge mode.hints=Etikettsläge mode.hints.exit=Återvänd till Normalläge +mode.hints.activate_highlighted=Aktivera markerade etiketter mode.hints.rotate_markers_forward=Rotera överlappande etiketter framåt mode.hints.rotate_markers_backward=Rotera överlappande etiketter bakåt -mode.hints.delete_hint_char=Radera senast inmatade tecken +mode.hints.delete_char=Radera senast inmatade tecken mode.hints.increase_count=Öka antal mode.hints.toggle_complementary=Ge etiketter till alla andra element mode.hints.peek_through=Håll ned för att se igenom etiketter @@ -160,28 +161,34 @@ notification.reload_config_file.failure=Fel vid omladdning av config-fil; kolla notification.toggle_selection.enter=Förflyttning av markören markerar nu text notification.copy_selection_and_exit.none=Ingen markering att kopiera -pref.hint_chars.title=Etikettstecken -pref.hint_chars.desc= +pref.prevent_autofocus.title=Hindra autofokus för textrutor +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=Ignorera tangentbordslayout +pref.ignore_keyboard_layout.desc=Låtsas att en-US QWERTY-layouten alltid används. pref.blacklist.title=Svartlista pref.blacklist.desc=Lista med URL-mönster där VimFx automatiskt ska gå in i Ignoreringsläge. Använd mellanslag som avgränsare och * som wildcard. Mönstren måste matcha hela URL:en. pref.blacklist.extra=%S har lagts till i början. -pref.prev_patterns.title=Mönster för ”Föregående”-länkar -pref.prev_patterns.desc= - -pref.next_patterns.title=Mönster för ”Nästa”-länkar -pref.next_patterns.desc= +pref.hints.chars.title=Etikettstecken +pref.hints.chars.desc= -pref.prevent_autofocus.title=Hindra autofokus för textrutor -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Auto-aktivering av etiketter +pref.hints.auto_activate.desc=Aktivera etiketter automatically om det bara finns ett alternativ kvar efter att ha filtrerat på elementtext. -pref.ignore_keyboard_layout.title=Ignorera tangentbordslayout -pref.ignore_keyboard_layout.desc=Låtsas att en-US QWERTY-layouten alltid används. +pref.hints.timeout.title=Auto-aktiverings-timeout +pref.hints.timeout.desc=Antal millisekunder att ignorera alla tangenttryckningar efter auto-aktivering av etiketter, för att undvika oönskade kommandon. pref.timeout.title=Timeout pref.timeout.desc=Maximalt antal millisekunder mellan två tangenttryckningar i en genväg. +pref.prev_patterns.title=Mönster för ”Föregående”-länkar +pref.prev_patterns.desc= + +pref.next_patterns.title=Mönster för ”Nästa”-länkar +pref.next_patterns.desc= + prefs.instructions.title=Tangentbordskommandon i textrutor på denna sida prefs.instructions.desc=%S Infoga nästa tangenttryckning som text\n%S Infoga standardvärde\n%S Återställ till standardvärde\n%S Ångra diff --git a/extension/locale/zh-CN/vimfx.properties b/extension/locale/zh-CN/vimfx.properties index edab8cb..8aac9a9 100644 --- a/extension/locale/zh-CN/vimfx.properties +++ b/extension/locale/zh-CN/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=返回普通模式 mode.hints=Hint 模式 mode.hints.exit=返回普通模式 +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=向前旋转重叠的标记 mode.hints.rotate_markers_backward=向后旋转重叠的标记 -mode.hints.delete_hint_char=删除最后输入的提示符 +mode.hints.delete_char=删除最后输入的提示符 mode.hints.increase_count=递增计数 mode.hints.toggle_complementary=标记所有其他元素 mode.hints.peek_through=按下按键以显示被标记遮盖的部分 @@ -160,28 +161,34 @@ notification.reload_config_file.failure=重新载入配置文件出错; 检查 notification.toggle_selection.enter=移动光标并开始选择文本 notification.copy_selection_and_exit.none=无可供复制的文本 -pref.hint_chars.title=提示符 -pref.hint_chars.desc= +pref.prevent_autofocus.title=阻止自动聚焦输入框 +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=忽略键盘布局 +pref.ignore_keyboard_layout.desc=假装总是使用美式标准键盘布局。 pref.blacklist.title=黑名单 pref.blacklist.desc=匹配下列 URL 时 VimFx 将自动进入忽略模式。使用空格作为界定符 * 作为通配符。 The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=匹配“上一页”链接 -pref.prev_patterns.desc= - -pref.next_patterns.title=匹配“下一页”链接 -pref.next_patterns.desc= +pref.hints.chars.title=提示符 +pref.hints.chars.desc= -pref.prevent_autofocus.title=阻止自动聚焦输入框 -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=忽略键盘布局 -pref.ignore_keyboard_layout.desc=假装总是使用美式标准键盘布局。 +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=超时 pref.timeout.desc=按下一个快捷键中两个按键的最大时间间隔(单位:毫秒) +pref.prev_patterns.title=匹配“上一页”链接 +pref.prev_patterns.desc= + +pref.next_patterns.title=匹配“下一页”链接 +pref.next_patterns.desc= + prefs.instructions.title=作用于本页输入框里的键盘快捷键 prefs.instructions.desc=%S 将下一个按键当作文本插入 \n%S 插入默认值 \n%S 重置为默认值 \n%S 撤销 diff --git a/extension/locale/zh-TW/vimfx.properties b/extension/locale/zh-TW/vimfx.properties index a6e79b9..e1d48bf 100644 --- a/extension/locale/zh-TW/vimfx.properties +++ b/extension/locale/zh-TW/vimfx.properties @@ -116,9 +116,10 @@ mode.caret.exit=返回普通模式 mode.hints=提示模式 mode.hints.exit=回到正常模式 +mode.hints.activate_highlighted=Activate highlighted markers mode.hints.rotate_markers_forward=向前旋轉重疊的標誌 mode.hints.rotate_markers_backward=向後旋轉重疊的標誌 -mode.hints.delete_hint_char=刪除最後輸入的提示字元 +mode.hints.delete_char=刪除最後輸入的提示字元 mode.hints.increase_count=遞增計數 mode.hints.toggle_complementary=標記其他所有元素 mode.hints.peek_through=按住以檢視標誌 @@ -160,28 +161,34 @@ notification.reload_config_file.failure=載入設定檔時發生錯誤; 查看 notification.toggle_selection.enter=移動目前游標選取文字 notification.copy_selection_and_exit.none=無選擇文字可複製 -pref.hint_chars.title=提示字元(Hint Chars) -pref.hint_chars.desc=進入 Hints 模式時,用哪些符號進行標記。 +pref.prevent_autofocus.title=不要自動聚焦文字輸入欄位 +pref.prevent_autofocus.desc= + +pref.ignore_keyboard_layout.title=忽略鍵盤佈局 +pref.ignore_keyboard_layout.desc=假定總是使用美式 QWERTY 鍵盤佈局 pref.blacklist.title=黑名單 pref.blacklist.desc=在下列 URL 清單中 VimFx 自動進入忽略模式。(以空白分隔, * 作為萬用字元) The patterns must match the entire URL. pref.blacklist.extra=%S has been added at the start. -pref.prev_patterns.title=“上一頁” 連結規則 -pref.prev_patterns.desc= - -pref.next_patterns.title=“下一頁” 連結規則 -pref.next_patterns.desc= +pref.hints.chars.title=提示字元(Hint Chars) +pref.hints.chars.desc=進入 Hints 模式時,用哪些符號進行標記。 -pref.prevent_autofocus.title=不要自動聚焦文字輸入欄位 -pref.prevent_autofocus.desc= +pref.hints.auto_activate.title=Hint auto-activation +pref.hints.auto_activate.desc=Automatically activate hints if there’s only one choice left after filtering by element text. -pref.ignore_keyboard_layout.title=忽略鍵盤佈局 -pref.ignore_keyboard_layout.desc=假定總是使用美式 QWERTY 鍵盤佈局 +pref.hints.timeout.title=Auto-activation timeout +pref.hints.timeout.desc=The number of milliseconds to ignore all keypresses after hint auto-activation, to avoid triggering accidental commands. pref.timeout.title=逾時 pref.timeout.desc=兩個快速鍵輸入的最大間隔(單位:毫秒) +pref.prev_patterns.title=“上一頁” 連結規則 +pref.prev_patterns.desc= + +pref.next_patterns.title=“下一頁” 連結規則 +pref.next_patterns.desc= + prefs.instructions.title=在本頁文字框中的鍵盤快速鍵 prefs.instructions.desc=%S 下一個插入按鍵當作文字\n%S 預設插入\n%S 回復預設值\n%S 復原 diff --git a/extension/skin/style.css b/extension/skin/style.css index fa7dac7..a47ed44 100644 --- a/extension/skin/style.css +++ b/extension/skin/style.css @@ -105,13 +105,16 @@ toolbarpaletteitem[place="palette"] > #VimFxButton { font-size: 0.8em; line-height: 1; font-weight: bold; - text-transform: uppercase; white-space: nowrap; /* Some light-weight themes set a `text-shadow` that the hint markers inherit, * making them almost unreadable. */ text-shadow: none; } +#VimFxMarkersContainer:not(.has-mixedcase) .marker { + text-transform: uppercase; +} + #VimFxMarkersContainer .marker-char {} /* Keep as documentation. */ #VimFxMarkersContainer .marker--matched, @@ -119,12 +122,17 @@ toolbarpaletteitem[place="palette"] > #VimFxButton { color: #ffa22a; } +#VimFxMarkersContainer .marker--highlighted { + filter: hue-rotate(180deg) brightness(85%); +} + #VimFxMarkersContainer .marker--hidden { display: none; } -/* alt, ctrl and meta can also be targeted. */ -#main-window[vimfx-held-modifiers~="shift"] #VimFxMarkersContainer { +/* alt, ctrl, meta and shift can be targeted. */ +#main-window[vimfx-held-modifiers~="ctrl"][vimfx-held-modifiers~="shift"] + #VimFxMarkersContainer { opacity: 0.2; } diff --git a/extension/test/test-api.coffee b/extension/test/test-api.coffee index 5bdfd5b..67f2b4b 100644 --- a/extension/test/test-api.coffee +++ b/extension/test/test-api.coffee @@ -43,7 +43,7 @@ exports['test exports'] = (assert, $vimfx) -> exports['test vimfx.get and vimfx.set'] = (assert, $vimfx, teardown) -> vimfx = createConfigAPI($vimfx) - resetHintChars = prefs.tmp('hint_chars', 'ab cd') + resetHintChars = prefs.tmp('hints.chars', 'ab cd') resetBlacklist = prefs.tmp('blacklist', null) originalOptions = Object.assign({}, $vimfx.options) teardown(-> @@ -52,11 +52,11 @@ exports['test vimfx.get and vimfx.set'] = (assert, $vimfx, teardown) -> $vimfx.options = originalOptions ) - assert.equal(vimfx.get('hint_chars'), 'ab cd') + assert.equal(vimfx.get('hints.chars'), 'ab cd') assert.ok(not prefs.has('blacklist')) - vimfx.set('hint_chars', 'xy z') - assert.equal(vimfx.get('hint_chars'), 'xy z') + vimfx.set('hints.chars', 'xy z') + assert.equal(vimfx.get('hints.chars'), 'xy z') vimfx.set('blacklist', 'test') assert.equal(vimfx.get('blacklist'), 'test') @@ -65,19 +65,19 @@ exports['test vimfx.get and vimfx.set'] = (assert, $vimfx, teardown) -> assert.deepEqual(vimfx.get('translations'), {KeyQ: ['ö', 'Ö']}) $vimfx.emit('shutdown') - assert.equal(vimfx.get('hint_chars'), 'ab cd') + assert.equal(vimfx.get('hints.chars'), 'ab cd') assert.ok(not prefs.has('blacklist')) assert.deepEqual(vimfx.get('translations'), {}) exports['test vimfx.getDefault'] = (assert, $vimfx, teardown) -> vimfx = createConfigAPI($vimfx) - reset = prefs.tmp('hint_chars', 'ab cd') + reset = prefs.tmp('hints.chars', 'ab cd') teardown(-> reset?() ) - assert.equal(vimfx.getDefault('hint_chars'), defaults.options.hint_chars) + assert.equal(vimfx.getDefault('hints.chars'), defaults.options['hints.chars']) exports['test customization'] = (assert, $vimfx, teardown) -> vimfx = createConfigAPI($vimfx) @@ -419,11 +419,11 @@ exports['test vimfx.[gs]et(Default)? errors'] = (assert, $vimfx) -> ) throws(assert, /boolean, number, string or null/i, 'undefined', -> - vimfx.set('hint_chars') + vimfx.set('hints.chars') ) throws(assert, /boolean, number, string or null/i, 'object', -> - vimfx.set('hint_chars', ['a', 'b', 'c']) + vimfx.set('hints.chars', ['a', 'b', 'c']) ) exports['test vimfx.addCommand errors'] = (assert, $vimfx) -> diff --git a/extension/test/test-parse-prefs.coffee b/extension/test/test-parse-prefs.coffee index 1856b95..255c898 100644 --- a/extension/test/test-parse-prefs.coffee +++ b/extension/test/test-parse-prefs.coffee @@ -48,7 +48,7 @@ testPrefParsed = (pref, fn) -> fn2($vimfx.options[pref]) fn(assert, test) -exports['test hint_chars'] = testPref('hint_chars', (test) -> +exports['test hints.chars'] = testPref('hints.chars', (test) -> # Invalid values. test('', /^([a-z]) (?!\1)[a-z]$/) test(' ', /^([a-z]) (?!\1)[a-z]$/) @@ -71,7 +71,7 @@ exports['test hint_chars'] = testPref('hint_chars', (test) -> test('ab cde\tf ', 'abcde f') # Remove duplicates. - test('aba fcAde\tf!.!e ', 'abfcde !.') + test('aba fcAde\tf!.!e ', 'abfcAde !.') ) spaceDelimitedStringPrefs = [ diff --git a/extension/test/test-utils.coffee b/extension/test/test-utils.coffee index 6c3c4d8..2189bad 100644 --- a/extension/test/test-utils.coffee +++ b/extension/test/test-utils.coffee @@ -19,6 +19,135 @@ utils = require('../lib/utils') +exports['test selectAllSubstringMatches'] = (assert) -> + window = utils.getCurrentWindow() + {document} = window + selection = window.getSelection() + + # Element creation helper. + e = (tagName, childNodes = []) -> + element = document.createElement(tagName) + element.appendChild(childNode) for childNode in childNodes + return element + + # Text node creation helper. + t = (text) -> document.createTextNode(text) + + test = (name, element, string, options, expected) -> + msg = (message) -> "#{name}: #{message}" + + selection.removeAllRanges() + utils.selectAllSubstringMatches(element, string, options) + + assert.equal(selection.rangeCount, expected.length, msg('rangeCount')) + + for index in [0...selection.rangeCount] by 1 + range = selection.getRangeAt(index) + [ + startContainer, startOffset + endContainer, endOffset + expectedString = string + ] = expected[index] + assert.equal(range.startContainer, startContainer, msg('startContainer')) + assert.equal(range.startOffset, startOffset, msg('startOffset')) + assert.equal(range.endContainer, endContainer, msg('endContainer')) + assert.equal(range.endOffset, endOffset, msg('endOffset')) + assert.equal(range.toString(), expectedString, msg('toString()')) + + return + + do (name = 'simple case') -> + element = e('p', [ + (t1 = t('test')) + ]) + test(name, element, 'es', null, [ + [t1, 1, t1, 3] + ]) + + do (name = 'several matches per text node') -> + element = e('p', [ + (t1 = t('es test best es')) + ]) + test(name, element, 'es', null, [ + [t1, 0, t1, 2] + [t1, 4, t1, 6] + [t1, 9, t1, 11] + [t1, 13, t1, 15] + ]) + + do (name = 'split across two text nodes') -> + element = e('p', [ + (t1 = t('te')) + (t2 = t('st')) + ]) + test(name, element, 'es', null, [ + [t1, 1, t2, 1] + ]) + + do (name = 'split across three text nodes') -> + element = e('p', [ + (t1 = t('te')) + t('s') + (t2 = t('t')) + ]) + test(name, element, 'test', null, [ + [t1, 0, t2, 1] + ]) + + do (name = 'empty text nodes skipped') -> + element = e('p', [ + t('') + (t1 = t('a te')) + t('') + t('') + t('s') + t('') + (t2 = t('t!')) + t('') + ]) + test(name, element, 'test', null, [ + [t1, 2, t2, 1] + ]) + + do (name = 'across several elements') -> + element = e('p', [ + t('\n ') + e('span', [ + (t1 = t('\tte')) + e('i', [ + t('s') + ]) + ]) + e('span') + (t2 = t('t')) + ]) + test(name, element, 'test', null, [ + [t1, 1, t2, 1] + ]) + + do (name = 'overlapping matches') -> + element = e('p', [ + (t1 = t('ababaabacaba')) + ]) + test(name, element, 'aba', null, [ + [t1, 0, t1, 8, 'ababaaba'] + [t1, 9, t1, 12] + ]) + + do (name = 'case sensitivity') -> + element = e('p', [ + (t1 = t('tESt')) + ]) + test(name, element, 'TesT', null, []) + + do (name = 'case insensitivity') -> + element = e('p', [ + (t1 = t('tESt')) + ]) + test(name, element, 'TesT', {caseSensitive: false}, [ + [t1, 0, t1, 4, 'tESt'] + ]) + exports['test bisect'] = (assert) -> fn = (num) -> num > 7 -- 2.39.3