diff --git a/README.md b/README.md
index 584656f..59c4345 100644
--- a/README.md
+++ b/README.md
@@ -6,41 +6,43 @@
[![CircleCI](https://circleci.com/gh/ueokande/vim-vixen.svg?style=svg)](https://circleci.com/gh/ueokande/vim-vixen)
[![devDependencies Status](https://david-dm.org/ueokande/vim-vixen/dev-status.svg)](https://david-dm.org/ueokande/vim-vixen?type=dev)
-Vim Vixen is a Firefox add-on which allows you to navigate with the keyboard on the browser.
-Firefox started to support WebExtensions API and will stop supporting add-ons using legacy APIs from version 57.
-For this reason, many legacy add-ons do not work on Firefox 57.
-Vim Vixen is a new choice for Vim users since Vim Vixen uses the WebExtensions API.
+Vim Vixen is a Firefox add-on which allows you to easily navigate the web by
+keyboard. Since version 57, Firefox has migrated to the WebExtensions API and
+has dropped support for legacy add-ons. Vim Vixen is a new choice for Vim users
+since it uses the WebExtensions API.
## Basic usage
-### Key-maps
+### Keymaps
-The key-maps are configurable in the add-ons preferences by navigating to `about:addons` and selecting "Extensions".
+Keymaps are configurable in the add-on's preferences by navigating to `about:addons` and selecting "Extensions".
The default mappings are as follows:
#### Console
-- :: open console
-- o, t, w: open a page in current tab, new tab, or new window
-- O, T, W: similar to o, t, w, but that contains current URL
-- b: Select tabs by URL or title
-- a: add current page to the bookmarks
+- :: open the console
+- o, t, w: open a page in the current tab, a new tab, or new window
+- O, T, W: similar to o, t, w, but using the current URL
+- b: select tabs by URL or title
+- a: add the current page to your bookmarks
-See [console commands](#console-commands) section for more detailed description
+See the [console commands](#console-commands) section for a more detailed description.
#### Tabs
-- d: delete tab
-- !d: delete pinned tab
-- u: reopen close tab
-- r: reload current tab
-- R: reload current tab without cache
-- K or gT: select previous tab
-- J or gt: select next tab
-- g0: select first tab
-- g$: select last tab
-- Ctrl+6: open previously selected tab
-- zp: pin tab
-- zd: duplicate tab
+
+- d: delete the current tab and select the tab to its right
+- D: delete the current tab and select the tab to its left
+- !d: delete a pinned tab
+- u: reopen a close tab
+- r: reload the current tab
+- R: reload the current tab, bypassing the cache
+- K or gT: select the previous tab
+- J or gt: select the next tab
+- g0: select the first tab
+- g$: select the last tab
+- Ctrl+6: open the previously-selected tab
+- zp: pin the curent tab tab
+- zd: duplicate the current tab
#### Scrolling
@@ -48,18 +50,19 @@ See [console commands](#console-commands) section for more detailed description
- j: scroll down
- h: scroll left
- l: scroll right
-- Ctrl+U: scroll up for a half page
-- Ctrl+D: scroll down for a half page
-- Ctrl+B: scroll up for a whole page
-- Ctrl+F: scroll down for a whole page
-- gg: scroll to top of a page
-- G: scroll to bottom of a page
+- Ctrl+U: scroll up half a page
+- Ctrl+D: scroll down half a page
+- Ctrl+B: scroll up a page
+- Ctrl+F: scroll down a page
+- gg: scroll to the top of a page
+- G: scroll to the bottom of a page
- 0: scroll to the leftmost part of a page
- $: scroll to the rightmost part of a page
-- m: set a mark from current position
-- ': jump to position by the mark
+- m: set a mark for the current position
+- ': jump to a marked position
-Lowercase alphabet mark (`[a-z]`) stores position on the current tab. Upper alphabet and numeric mark (`[A-Z0-9]`) stores position and tab.
+Lowercase marks (`[a-z]`) store the position of the current tab. Uppercase and
+numeric marks (`[A-Z0-9]`) store the position and the tab.
#### Zoom
@@ -69,109 +72,111 @@ Lowercase alphabet mark (`[a-z]`) stores position on the current tab. Upper alph
#### Navigation
-- f: start following links in the page in the current tab
-- F: start following links in the page in new tabs
+- f: follow links in the page in the current tab
+- F: follow links in the page in a new tab
- H: go back in history
- L: go forward in history
-- [[, ]]: find prev or next links and open it
-- gu: go to parent directory
-- gU: go to root directory
-- gi: focus first input
-
-Plugin can be configured to follow links in new tabs in background instead of
-switching to a new tab immediately. To do this you need to update config file:
-change `"background"` property of `"follow.start"` action to true:
+- [[, ]]: find a link to the previous/next page and open it
+- gu: go to the parent directory
+- gU: go to the root directory
+- gi: focus the first input field
+
+Vim Vixen can be configured to follow links opened in tabs in the background
+instead of switching to a new tab immediately. To do this, you'll need to update
+the config file: change the `"background"` property of the `"follow.start"`
+action to `true`, e.g.:
+
```json
{
"keymaps": {
- "F": { "type": "follow.start", "newTab": true, "background": true },
+ "F": { "type": "follow.start", "newTab": true, "background": true }
}
}
```
#### Misc
-- y: copy URL in current tab
-- p: open clipboard's URL in current tab
-- P: open clipboard's URL in new tab
-- Shift+Esc: enable or disable the add-on in current tab.
-- /: start to find a keyword in the page
-- n: find next keyword in the page
-- N: find prev keyword in the page
-- gf: view page source
+- y: copy the URL of the current tab to the clipboard
+- p: open the clipboard's URL in the current tab
+- P: open the clipboard's URL in new tab
+- Shift+Esc: enable or disable the add-on in the current tab
+- /: start searching for text in the page
+- n: find the next search result in the page
+- N: find the previous search result in the page
+- gf: view the source of the current tab
### Console commands
-Vim Vixen provides a console for `ex`-style commands similar to Vimperator.
+Vim Vixen provides a console for `ex`-style commands, similar to Vimperator.
-Open the console with :. Or start it with initial values using
-o/O, t/T,
-or w/W.
+Open the console with :. Or populate it with initial values using
+o/O, t/T, or
+w/W.
-#### `:open` command
+#### `:open`
The `:open` command operates two different ways, depending on the parameter.
-When the parameter is a URL, that URL is opened in the current tab.
+When the parameter is a URL, it's opened in the current tab.
```
:open http://github.com/ueokande
```
-Otherwise, the current tab will open a search page with the supplied string (defaults to Google).
+Otherwise, the current tab opens a search page with the supplied string (defaults to Google).
```
:open How to contribute to Vim-Vixen
```
-To use a search engine other than the default, specify which search engine to use as the first parameter.
+To use a search engine other than the default, specify the search engine to use as the first parameter.
```
:open yahoo How to contribute to Vim-Vixen
```
-To adjust the search engine default and add/remove search engines, see the [search engines](#search-engines) section.
+To adjust the default search-engine and add/remove search engines, see the [search engines](#search-engines) section.
-#### `:tabopen` command
+#### `:tabopen`
-Open a URL or search keywords by search engine in new tab.
+Open a URL or search-engine query in a new tab.
-#### `:quit` or `:q` command
+#### `:quit` or `:q`
Close the current tab.
-#### `:quitall` or `:qa` command
+#### `:quitall` or `:qa`
Close all tabs.
-#### `:bdelete` command
+#### `:bdelete`
Close a certain tab.
-You can add `!` at the end of the command to close tab even if it is pinned:
+You can add `!` to the end of the command to close a tab even if it is pinned:
```
:bdelete!
```
-#### `:bdeletes` command
+#### `:bdeletes`
-Close tabs matches with keywords.
+Close tabs matching the specified keywords.
-You can add `!` at the end of the command to close even pinned tabs:
+You can add `!` to the end of the command to close pinned tabs:
```
:bdeletes!
```
-#### `:winopen` command
+#### `:winopen`
-Open a URL or search keywords by search engine in new window.
+Open a URL or search-engine query in a new window.
-#### `:buffer` command
+#### `:buffer`
-Select tabs by URL or title matched by keywords.
+Select tabs by URL or title keywords.
-#### `:addbookmark` command
+#### `:addbookmark`
Create a bookmark from the current URL.
@@ -179,20 +184,19 @@ Create a bookmark from the current URL.
:addbookmark My bookmark title
```
-The key map a is a convenient way to create a bookmark from the
-current page. That shows `:addbookmark` with a title from the current page into
-console.
+The keymap a is a convenient way to create a bookmark for the
+current page. It populates the console with `:addbookmark` and the title of
+the current page.
-#### `:set` command
+#### `:set`
-`:set` command can temporary override properties using console. See
-[properties](#properties) section for more detailed description of available
-properties.
+The `:set` command can be used to temporarily override properties in the
+console. See the [properties](#properties) section for more details on
+the available properties.
### Properties
-Plugin supports configurable properties which can be configured in JSON
-settings:
+Vim Vixen can be configured by defining settings in a JSON document, e.g.:
```json
{
@@ -202,33 +206,35 @@ settings:
}
```
-Properties can be temporary overwritten by `:set` command in console.
+Properties can be temporarily overridden by using the `:set` command in the
+console.
-List of available properties you can find below:
+The following properties are available:
-#### `smoothscroll` property
+#### `smoothscroll`
-Enable/disable smooth scroll.
+Enable/disable smooth scrolling.
```
-:set smoothscroll " enable smooth scroll
-:set nosmoothscroll " disable smooth scroll
+:set smoothscroll " enable smooth scrolling
+:set nosmoothscroll " disable smooth scrolling
```
-#### `hintchars` property
+#### `hintchars`
-Set hint characters
+Set hint characters.
```
:set hintchars=0123456789
```
-#### `complete` property
+#### `complete`
-Set completion items on `open`, `tabopen` `winopen` commands.
+Set completion items on `open`, `tabopen`, and `winopen` commands.
The allowed value is character sequence of `s`, `b`, or `h`.
-Hit Tab or Shift+Tab to Select an item from the completion list.
-Each character presents as following:
+Hit Tab or Shift+Tab to select an item from the completion list.
+Each character represents the following:
+
- `s`: search engines
- `b`: bookmark items
- `h`: history items.
@@ -239,10 +245,10 @@ Each character presents as following:
### Search engines
-Vim Vixen supports search by search engines like Google and Yahoo.
+Vim Vixen supports searching with search engines such as Google and Yahoo.
-You can configure search engines, including the default search engine, in the add-ons preferences.
-The URLs specified in `"engines"` must contain a {}-placeholder, which will be
+You can configure search engines, including the default search engine, in the add-on's preferences.
+The URLs specified in `"engines"` must contain a `{}`-placeholder, which will be
replaced with the search keyword parameters of the command.
```json
@@ -264,7 +270,7 @@ replaced with the search keyword parameters of the command.
### Blacklist
The blacklist allows you to disable the plugin for certain pages by URL patterns.
-For instance, when you describe `"*.slack.com"`, the plugin is disabled on any Slack rooms.
+For instance, you could use `"*.slack.com"` to disable the plugin on all Slack channels.
In addition, you can also specify path patterns, such as `"example.com/mail/*"`.
```json
@@ -279,6 +285,14 @@ In addition, you can also specify path patterns, such as `"example.com/mail/*"`.
You can toggle Vim Vixen between disabled and enabled with
shift+Esc.
+## Compatibility
+
+- Firefox 60 ESR
+
+## Copyright
+
+Copyright © 2017-2019 by Shin'ya Ueoka
+
## Licence
MIT
diff --git a/e2e/settings.js b/e2e/settings.js
index e09747f..e78add0 100644
--- a/e2e/settings.js
+++ b/e2e/settings.js
@@ -24,7 +24,8 @@ module.exports = {
"G": { "type": "scroll.bottom" },
"$": { "type": "scroll.end" },
"d": { "type": "tabs.close" },
- "D": { "type": "tabs.close.right" },
+ "D": { "type": "tabs.close", "select": "left" },
+ "x$": { "type": "tabs.close.right" },
"!d": { "type": "tabs.close.force" },
"u": { "type": "tabs.reopen" },
"K": { "type": "tabs.prev", "count": 1 },
diff --git a/e2e/tab.test.js b/e2e/tab.test.js
index 16d61ae..c82890e 100644
--- a/e2e/tab.test.js
+++ b/e2e/tab.test.js
@@ -55,18 +55,32 @@ describe("tab test", () => {
await browser.windows.remove(win.id);
});
- it('deletes tab by d', async () => {
+ it('deletes tab and selects right by d', async () => {
+ await browser.tabs.update(tabs[3].id, { active: true });
let body = await session.findElementByCSS('body');
await body.sendKeys('d');
let current = await browser.tabs.query({ windowId: win.id });
assert(current.length === tabs.length - 1);
+ assert(current[3].active);
+ assert(current[3].url === tabs[4].url);
+ });
+
+ it('deletes tab and selects left by D', async () => {
+ await browser.tabs.update(tabs[3].id, { active: true });
+ let body = await session.findElementByCSS('body');
+ await body.sendKeys(Key.Shift, 'D');
+
+ let current = await browser.tabs.query({ windowId: win.id });
+ assert(current.length === tabs.length - 1);
+ assert(current[2].active);
+ assert(current[2].url === tabs[2].url);
});
- it('deletes tabs to the right by D', async () => {
+ it('deletes all tabs to the right by x$', async () => {
await browser.tabs.update(tabs[1].id, { active: true });
let body = await session.findElementByCSS('body');
- await body.sendKeys(Key.Shift, 'd');
+ await body.sendKeys('x', '$');
let current = await browser.tabs.query({ windowId: win.id });
assert(current.length === 2);
diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts
index 51cff28..7a10ad6 100644
--- a/src/background/controllers/OperationController.ts
+++ b/src/background/controllers/OperationController.ts
@@ -32,7 +32,7 @@ export default class OperationController {
doOperation(operation: operations.Operation): Promise {
switch (operation.type) {
case operations.TAB_CLOSE:
- return this.tabUseCase.close(false);
+ return this.tabUseCase.close(false, operation.select === 'left');
case operations.TAB_CLOSE_RIGHT:
return this.tabUseCase.closeRight();
case operations.TAB_CLOSE_FORCE:
diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts
index 31112a9..386307e 100644
--- a/src/background/usecases/TabUseCase.ts
+++ b/src/background/usecases/TabUseCase.ts
@@ -12,11 +12,15 @@ export default class TabUseCase {
) {
}
- async close(force: boolean): Promise {
+ async close(force: boolean, selectLeft = false): Promise {
let tab = await this.tabPresenter.getCurrent();
if (!force && tab.pinned) {
return Promise.resolve();
}
+ if (selectLeft && tab.index > 0) {
+ let tabs = await this.tabPresenter.getAll();
+ await this.tabPresenter.select(tabs[tab.index - 1].id as number);
+ }
return this.tabPresenter.remove([tab.id as number]);
}
diff --git a/src/content/presenters/ScrollPresenter.ts b/src/content/presenters/ScrollPresenter.ts
index c06efca..e83f172 100644
--- a/src/content/presenters/ScrollPresenter.ts
+++ b/src/content/presenters/ScrollPresenter.ts
@@ -90,7 +90,7 @@ class Scroller {
clearTimeout(lastTimeoutId);
lastTimeoutId = null;
}
- lastTimeoutId = setTimeout(resetScrolling, 100);
+ lastTimeoutId = window.setTimeout(resetScrolling, 100);
}
}
diff --git a/src/settings/keymaps.ts b/src/settings/keymaps.ts
index 33ad26d..24ba1a5 100644
--- a/src/settings/keymaps.ts
+++ b/src/settings/keymaps.ts
@@ -18,11 +18,11 @@ const fields = [
['mark.set.prefix', 'Set mark at current position'],
['mark.jump.prefix', 'Jump to the mark'],
], [
- ['tabs.close', 'Close a tab'],
- ['tabs.close.right', 'Close tabs to the right'],
+ ['tabs.close?{"select":"right"}', 'Close a tab'],
+ ['tabs.close.right', 'Close all tabs to the right'],
['tabs.reopen', 'Reopen closed tab'],
- ['tabs.next', 'Select next Tab'],
- ['tabs.prev', 'Select prev Tab'],
+ ['tabs.next', 'Select next tab'],
+ ['tabs.prev', 'Select prev tab'],
['tabs.first', 'Select first tab'],
['tabs.last', 'Select last tab'],
['tabs.reload?{"cache":false}', 'Reload current tab'],
@@ -50,8 +50,8 @@ const fields = [
['command.show', 'Open console'],
['command.show.open?{"alter":false}', 'Open URL'],
['command.show.open?{"alter":true}', 'Alter URL'],
- ['command.show.tabopen?{"alter":false}', 'Open URL in new Tab'],
- ['command.show.tabopen?{"alter":true}', 'Alter URL in new Tab'],
+ ['command.show.tabopen?{"alter":false}', 'Open URL in new tab'],
+ ['command.show.tabopen?{"alter":true}', 'Alter URL in new tab'],
['command.show.winopen?{"alter":false}', 'Open URL in new window'],
['command.show.winopen?{"alter":true}', 'Alter URL in new window'],
['command.show.buffer', 'Open buffer command'],
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts
index 1c085cf..14a7d35 100644
--- a/src/shared/SettingData.ts
+++ b/src/shared/SettingData.ts
@@ -353,7 +353,8 @@ export const DefaultSettingData: SettingData = SettingData.valueOf({
"G": { "type": "scroll.bottom" },
"$": { "type": "scroll.end" },
"d": { "type": "tabs.close" },
- "D": { "type": "tabs.close.right" },
+ "D": { "type": "tabs.close", "select": "left" },
+ "x$": { "type": "tabs.close.right" },
"!d": { "type": "tabs.close.force" },
"u": { "type": "tabs.reopen" },
"K": { "type": "tabs.prev" },
diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts
index 0bef342..2a392df 100644
--- a/src/shared/Settings.ts
+++ b/src/shared/Settings.ts
@@ -146,7 +146,8 @@ export const DefaultSetting: Settings = {
'G': { 'type': 'scroll.bottom' },
'$': { 'type': 'scroll.end' },
'd': { 'type': 'tabs.close' },
- 'D': { 'type': 'tabs.close.right' },
+ 'D': { 'type': 'tabs.close', 'select': 'left' },
+ 'x$': { 'type': 'tabs.close.right' },
'!d': { 'type': 'tabs.close.force' },
'u': { 'type': 'tabs.reopen' },
'K': { 'type': 'tabs.prev' },
diff --git a/src/shared/operations.ts b/src/shared/operations.ts
index 2b03d9d..2df2e67 100644
--- a/src/shared/operations.ts
+++ b/src/shared/operations.ts
@@ -201,6 +201,7 @@ export interface PageHomeOperation {
export interface TabCloseOperation {
type: typeof TAB_CLOSE;
+ select?: 'left' | 'right';
}
export interface TabCloseForceOperation {
@@ -367,28 +368,41 @@ export type Operation =
const assertOptionalBoolean = (obj: any, name: string) => {
if (Object.prototype.hasOwnProperty.call(obj, name) &&
typeof obj[name] !== 'boolean') {
- throw new TypeError(`Not a boolean parameter '${name}'`);
+ throw new TypeError(`Not a boolean parameter: '${name}'`);
+ }
+};
+
+const assertOptionalString = (obj: any, name: string, values?: string[]) => {
+ if (Object.prototype.hasOwnProperty.call(obj, name)) {
+ let value = obj[name];
+ if (typeof value !== 'string') {
+ throw new TypeError(`Not a string parameter: '${name}'`);
+ }
+ if (values && values.length && values.indexOf(value) === -1) {
+ // eslint-disable-next-line max-len
+ throw new TypeError(`Invalid parameter for '${name}': '${value}'`);
+ }
}
};
const assertRequiredNumber = (obj: any, name: string) => {
if (!Object.prototype.hasOwnProperty.call(obj, name) ||
typeof obj[name] !== 'number') {
- throw new TypeError(`Missing number parameter '${name}`);
+ throw new TypeError(`Missing number parameter: '${name}`);
}
};
const assertRequiredString = (obj: any, name: string) => {
if (!Object.prototype.hasOwnProperty.call(obj, name) ||
typeof obj[name] !== 'string') {
- throw new TypeError(`Missing string parameter '${name}`);
+ throw new TypeError(`Missing string parameter: '${name}`);
}
};
// eslint-disable-next-line complexity, max-lines-per-function
export const valueOf = (o: any): Operation => {
if (!Object.prototype.hasOwnProperty.call(o, 'type')) {
- throw new TypeError(`missing 'type' field`);
+ throw new TypeError(`Missing 'type' field`);
}
switch (o.type) {
case COMMAND_SHOW_OPEN:
@@ -416,6 +430,12 @@ export const valueOf = (o: any): Operation => {
type: PAGE_HOME,
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
};
+ case TAB_CLOSE:
+ assertOptionalString(o, 'select', ['left', 'right']);
+ return {
+ type: TAB_CLOSE,
+ select: (typeof o.select === undefined ? 'right' : o.select),
+ };
case TAB_RELOAD:
assertOptionalBoolean(o, 'cache');
return {
@@ -458,7 +478,6 @@ export const valueOf = (o: any): Operation => {
case NAVIGATE_ROOT:
case FOCUS_INPUT:
case PAGE_SOURCE:
- case TAB_CLOSE:
case TAB_CLOSE_FORCE:
case TAB_CLOSE_RIGHT:
case TAB_REOPEN:
@@ -483,5 +502,5 @@ export const valueOf = (o: any): Operation => {
case REPEAT_LAST:
return { type: o.type };
}
- throw new TypeError('unknown operation type: ' + o.type);
+ throw new TypeError('Unknown operation type: ' + o.type);
};