From 6594841b1d1f082107bdb1489f02d3c99cff8ffa Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Wed, 4 Oct 2017 21:30:57 +0900
Subject: use createStore short-hand method

---
 src/background/index.js | 18 +++++++++---------
 src/content/index.js    | 10 +++++-----
 src/pages/console.js    | 16 ++++++++--------
 src/pages/settings.js   |  8 ++++----
 4 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/src/background/index.js b/src/background/index.js
index e968c82..9a1adc6 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -4,27 +4,27 @@ import BackgroundComponent from '../components/background';
 import BackgroundInputComponent from '../components/background-input';
 import reducers from '../reducers';
 import messages from '../content/messages';
-import * as store from '../store';
+import { createStore } from '../store';
 
-const backgroundStore = store.createStore(reducers, (e, sender) => {
+const store = createStore(reducers, (e, sender) => {
   console.error('Vim-Vixen:', e);
   if (sender) {
-    backgroundStore.dispatch(consoleActions.showError(e.message), sender);
+    store.dispatch(consoleActions.showError(e.message), sender);
   }
 });
-const backgroundComponent = new BackgroundComponent(backgroundStore);
-const backgroundInputComponent = new BackgroundInputComponent(backgroundStore);
-backgroundStore.subscribe((sender) => {
+const backgroundComponent = new BackgroundComponent(store);
+const backgroundInputComponent = new BackgroundInputComponent(store);
+store.subscribe((sender) => {
   backgroundComponent.update(sender);
   backgroundInputComponent.update(sender);
 });
-backgroundStore.subscribe((sender) => {
+store.subscribe((sender) => {
   if (sender) {
     return browser.tabs.sendMessage(sender.tab.id, {
       type: messages.STATE_UPDATE,
-      state: backgroundStore.getState()
+      state: store.getState()
     });
   }
 });
 
-backgroundStore.dispatch(settingsActions.load());
+store.dispatch(settingsActions.load());
diff --git a/src/content/index.js b/src/content/index.js
index 655bea4..ea08982 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -3,16 +3,16 @@ import * as consoleFrames from './console-frames';
 import * as scrolls from '../content/scrolls';
 import * as navigates from '../content/navigates';
 import * as followActions from '../actions/follow';
-import * as store from '../store';
+import { createStore } from '../store';
 import ContentInputComponent from '../components/content-input';
 import FollowComponent from '../components/follow';
 import followReducer from '../reducers/follow';
 import operations from '../operations';
 import messages from './messages';
 
-const followStore = store.createStore(followReducer);
-const followComponent = new FollowComponent(window.document.body, followStore);
-followStore.subscribe(() => {
+const store = createStore(followReducer);
+const followComponent = new FollowComponent(window.document.body, store);
+store.subscribe(() => {
   try {
     followComponent.update();
   } catch (e) {
@@ -39,7 +39,7 @@ const execOperation = (operation) => {
   case operations.SCROLL_END:
     return scrolls.scrollRight(window);
   case operations.FOLLOW_START:
-    return followStore.dispatch(followActions.enable(false));
+    return store.dispatch(followActions.enable(false));
   case operations.NAVIGATE_HISTORY_PREV:
     return navigates.historyPrev(window);
   case operations.NAVIGATE_HISTORY_NEXT:
diff --git a/src/pages/console.js b/src/pages/console.js
index 2cbea25..4ce9e47 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -3,26 +3,26 @@ import messages from '../content/messages';
 import CompletionComponent from '../components/completion';
 import ConsoleComponent from '../components/console';
 import completionReducer from '../reducers/completion';
-import * as store from '../store';
+import { createStore } from '../store';
 import * as completionActions from '../actions/completion';
 
-const completionStore = store.createStore(completionReducer);
+const store = createStore(completionReducer);
 let completionComponent = null;
 let consoleComponent = null;
 let prevState = {};
 
 window.addEventListener('load', () => {
   let wrapper = document.querySelector('#vimvixen-console-completion');
-  completionComponent = new CompletionComponent(wrapper, completionStore);
+  completionComponent = new CompletionComponent(wrapper, store);
 
-  // TODO use root root store instead of completionStore
-  consoleComponent = new ConsoleComponent(document.body, completionStore);
+  // TODO use root root store instead of store
+  consoleComponent = new ConsoleComponent(document.body, store);
 });
 
-completionStore.subscribe(() => {
+store.subscribe(() => {
   completionComponent.update();
 
-  let state = completionStore.getState();
+  let state = store.getState();
 
   if (state.groupSelection >= 0) {
     let item = state.groups[state.groupSelection].items[state.itemSelection];
@@ -40,6 +40,6 @@ browser.runtime.onMessage.addListener((action) => {
   if (action.type === messages.STATE_UPDATE) {
     let state = action.state.console;
     consoleComponent.update(state);
-    completionStore.dispatch(completionActions.setItems(state.completions));
+    store.dispatch(completionActions.setItems(state.completions));
   }
 });
diff --git a/src/pages/settings.js b/src/pages/settings.js
index 9bad967..076d23d 100644
--- a/src/pages/settings.js
+++ b/src/pages/settings.js
@@ -1,15 +1,15 @@
 import './settings.scss';
 import SettingComponent from '../components/setting';
 import settingReducer from '../reducers/setting';
-import * as store from '../store';
+import { createStore } from '../store';
 
-const settingStore = store.createStore(settingReducer);
+const store = createStore(settingReducer);
 let settingComponent = null;
 
-settingStore.subscribe(() => {
+store.subscribe(() => {
   settingComponent.update();
 });
 
 document.addEventListener('DOMContentLoaded', () => {
-  settingComponent = new SettingComponent(document.body, settingStore);
+  settingComponent = new SettingComponent(document.body, store);
 });
-- 
cgit v1.2.3


From 79a4a805f6be14572b4486ddb79b0ebb98e37690 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Wed, 4 Oct 2017 22:01:16 +0900
Subject: single reducer

---
 src/components/completion.js | 2 +-
 src/components/follow.js     | 2 +-
 src/content/index.js         | 4 ++--
 src/pages/console.js         | 7 +++----
 src/reducers/completion.js   | 2 +-
 src/reducers/index.js        | 6 ++++++
 6 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/src/components/completion.js b/src/components/completion.js
index 489061c..d1fdd06 100644
--- a/src/components/completion.js
+++ b/src/components/completion.js
@@ -6,7 +6,7 @@ export default class Completion {
   }
 
   update() {
-    let state = this.store.getState();
+    let state = this.store.getState().completion;
     if (JSON.stringify(this.prevState) === JSON.stringify(state)) {
       return;
     }
diff --git a/src/components/follow.js b/src/components/follow.js
index 4fe4c58..d2d3902 100644
--- a/src/components/follow.js
+++ b/src/components/follow.js
@@ -44,7 +44,7 @@ export default class FollowComponent {
 
   update() {
     let prevState = this.state;
-    this.state = this.store.getState();
+    this.state = this.store.getState().follow;
     if (!prevState.enabled && this.state.enabled) {
       this.create();
     } else if (prevState.enabled && !this.state.enabled) {
diff --git a/src/content/index.js b/src/content/index.js
index ea08982..38ad837 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -6,11 +6,11 @@ import * as followActions from '../actions/follow';
 import { createStore } from '../store';
 import ContentInputComponent from '../components/content-input';
 import FollowComponent from '../components/follow';
-import followReducer from '../reducers/follow';
+import reducers from '../reducers';
 import operations from '../operations';
 import messages from './messages';
 
-const store = createStore(followReducer);
+const store = createStore(reducers);
 const followComponent = new FollowComponent(window.document.body, store);
 store.subscribe(() => {
   try {
diff --git a/src/pages/console.js b/src/pages/console.js
index 4ce9e47..0e152d3 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -2,11 +2,11 @@ import './console.scss';
 import messages from '../content/messages';
 import CompletionComponent from '../components/completion';
 import ConsoleComponent from '../components/console';
-import completionReducer from '../reducers/completion';
+import reducers from '../reducers';
 import { createStore } from '../store';
 import * as completionActions from '../actions/completion';
 
-const store = createStore(completionReducer);
+const store = createStore(reducers);
 let completionComponent = null;
 let consoleComponent = null;
 let prevState = {};
@@ -15,14 +15,13 @@ window.addEventListener('load', () => {
   let wrapper = document.querySelector('#vimvixen-console-completion');
   completionComponent = new CompletionComponent(wrapper, store);
 
-  // TODO use root root store instead of store
   consoleComponent = new ConsoleComponent(document.body, store);
 });
 
 store.subscribe(() => {
   completionComponent.update();
 
-  let state = store.getState();
+  let state = store.getState().completion;
 
   if (state.groupSelection >= 0) {
     let item = state.groups[state.groupSelection].items[state.itemSelection];
diff --git a/src/reducers/completion.js b/src/reducers/completion.js
index a8a6444..878aeac 100644
--- a/src/reducers/completion.js
+++ b/src/reducers/completion.js
@@ -63,6 +63,6 @@ export default function reducer(state = defaultState, action = {}) {
     });
   }
   default:
-    return defaultState;
+    return state;
   }
 }
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 9beb81c..db17edf 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,11 +1,15 @@
 import inputReducer from '../reducers/input';
 import consoleReducer from '../reducers/console';
 import settingReducer from '../reducers/setting';
+import followReducer from '../reducers/follow';
+import completionReducer from '../reducers/completion';
 
 const defaultState = {
   input: inputReducer(undefined, {}),
   console: consoleReducer(undefined, {}),
   setting: settingReducer(undefined, {}),
+  follow: followReducer(undefined, {}),
+  completion: completionReducer(undefined, {}),
 };
 
 export default function reducer(state = defaultState, action = {}) {
@@ -13,5 +17,7 @@ export default function reducer(state = defaultState, action = {}) {
     input: inputReducer(state.input, action),
     console: consoleReducer(state.console, action),
     setting: settingReducer(state.setting, action),
+    follow: followReducer(state.follow, action),
+    completion: completionReducer(state.completion, action),
   });
 }
-- 
cgit v1.2.3


From 5fb3de3263357b3b42a0d1a67a0b9153e4c34260 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Wed, 4 Oct 2017 22:05:29 +0900
Subject: move operations

---
 src/actions/operation.js         |  2 +-
 src/content/index.js             |  2 +-
 src/operations/index.js          | 38 --------------------------------------
 src/shared/operations.js         | 38 ++++++++++++++++++++++++++++++++++++++
 src/shared/validators/setting.js |  2 +-
 5 files changed, 41 insertions(+), 41 deletions(-)
 delete mode 100644 src/operations/index.js
 create mode 100644 src/shared/operations.js

diff --git a/src/actions/operation.js b/src/actions/operation.js
index 5b7f127..3ed7f05 100644
--- a/src/actions/operation.js
+++ b/src/actions/operation.js
@@ -1,4 +1,4 @@
-import operations from '../operations';
+import operations from '../shared/operations';
 import messages from '../content/messages';
 import * as consoleActions from './console';
 import * as tabs from '../background/tabs';
diff --git a/src/content/index.js b/src/content/index.js
index 38ad837..a9a50be 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -7,7 +7,7 @@ import { createStore } from '../store';
 import ContentInputComponent from '../components/content-input';
 import FollowComponent from '../components/follow';
 import reducers from '../reducers';
-import operations from '../operations';
+import operations from '../shared/operations';
 import messages from './messages';
 
 const store = createStore(reducers);
diff --git a/src/operations/index.js b/src/operations/index.js
deleted file mode 100644
index b68f59d..0000000
--- a/src/operations/index.js
+++ /dev/null
@@ -1,38 +0,0 @@
-export default {
-  // Command
-  COMMAND_SHOW: 'command.show',
-  COMMAND_SHOW_OPEN: 'command.show.open',
-  COMMAND_SHOW_TABOPEN: 'command.show.tabopen',
-  COMMAND_SHOW_BUFFER: 'command.show.buffer',
-
-  // Scrolls
-  SCROLL_LINES: 'scroll.lines',
-  SCROLL_PAGES: 'scroll.pages',
-  SCROLL_TOP: 'scroll.top',
-  SCROLL_BOTTOM: 'scroll.bottom',
-  SCROLL_HOME: 'scroll.home',
-  SCROLL_END: 'scroll.end',
-
-  // Follows
-  FOLLOW_START: 'follow.start',
-
-  // Navigations
-  NAVIGATE_HISTORY_PREV: 'navigate.history.prev',
-  NAVIGATE_HISTORY_NEXT: 'navigate.history.next',
-  NAVIGATE_LINK_PREV: 'navigate.link.prev',
-  NAVIGATE_LINK_NEXT: 'navigate.link.next',
-  NAVIGATE_PARENT: 'navigate.parent',
-  NAVIGATE_ROOT: 'navigate.root',
-
-  // Tabs
-  TAB_CLOSE: 'tabs.close',
-  TAB_REOPEN: 'tabs.reopen',
-  TAB_PREV: 'tabs.prev',
-  TAB_NEXT: 'tabs.next',
-  TAB_RELOAD: 'tabs.reload',
-
-  // Zooms
-  ZOOM_IN: 'zoom.in',
-  ZOOM_OUT: 'zoom.out',
-  ZOOM_NEUTRAL: 'zoom.neutral',
-};
diff --git a/src/shared/operations.js b/src/shared/operations.js
new file mode 100644
index 0000000..b68f59d
--- /dev/null
+++ b/src/shared/operations.js
@@ -0,0 +1,38 @@
+export default {
+  // Command
+  COMMAND_SHOW: 'command.show',
+  COMMAND_SHOW_OPEN: 'command.show.open',
+  COMMAND_SHOW_TABOPEN: 'command.show.tabopen',
+  COMMAND_SHOW_BUFFER: 'command.show.buffer',
+
+  // Scrolls
+  SCROLL_LINES: 'scroll.lines',
+  SCROLL_PAGES: 'scroll.pages',
+  SCROLL_TOP: 'scroll.top',
+  SCROLL_BOTTOM: 'scroll.bottom',
+  SCROLL_HOME: 'scroll.home',
+  SCROLL_END: 'scroll.end',
+
+  // Follows
+  FOLLOW_START: 'follow.start',
+
+  // Navigations
+  NAVIGATE_HISTORY_PREV: 'navigate.history.prev',
+  NAVIGATE_HISTORY_NEXT: 'navigate.history.next',
+  NAVIGATE_LINK_PREV: 'navigate.link.prev',
+  NAVIGATE_LINK_NEXT: 'navigate.link.next',
+  NAVIGATE_PARENT: 'navigate.parent',
+  NAVIGATE_ROOT: 'navigate.root',
+
+  // Tabs
+  TAB_CLOSE: 'tabs.close',
+  TAB_REOPEN: 'tabs.reopen',
+  TAB_PREV: 'tabs.prev',
+  TAB_NEXT: 'tabs.next',
+  TAB_RELOAD: 'tabs.reload',
+
+  // Zooms
+  ZOOM_IN: 'zoom.in',
+  ZOOM_OUT: 'zoom.out',
+  ZOOM_NEUTRAL: 'zoom.neutral',
+};
diff --git a/src/shared/validators/setting.js b/src/shared/validators/setting.js
index caba5cc..4dc35ff 100644
--- a/src/shared/validators/setting.js
+++ b/src/shared/validators/setting.js
@@ -1,4 +1,4 @@
-import operations from '../../operations';
+import operations from '../operations';
 
 const VALID_TOP_KEYS = ['keymaps', 'search'];
 const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
-- 
cgit v1.2.3


From c5e9a3d35db6fb0308a3771f5d1b3083dbf2943c Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Thu, 5 Oct 2017 20:08:55 +0900
Subject: set import root for webpack

---
 webpack.config.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/webpack.config.js b/webpack.config.js
index f1ba07a..e4d5a17 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -39,7 +39,8 @@ module.exports = {
   },
 
   resolve: {
-    extensions: [ '.js' ]
+    extensions: [ '.js' ],
+    modules: [path.join(__dirname), 'node_modules']
   },
 
   plugins: [
-- 
cgit v1.2.3


From 5ef9a2a60c99f24fe3df7035ae4dca574fc38c68 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Thu, 5 Oct 2017 20:12:08 +0900
Subject: fix imports in test

---
 test/actions/completion.test.js        | 4 ++--
 test/actions/console.test.js           | 4 ++--
 test/actions/follow.test.js            | 4 ++--
 test/actions/input.test.js             | 4 ++--
 test/components/follow.test.js         | 2 +-
 test/content/hint-key-producer.test.js | 2 +-
 test/content/hint.test.js              | 2 +-
 test/content/navigates.test.js         | 2 +-
 test/reducers/completion.test.js       | 4 ++--
 test/reducers/console.test.js          | 4 ++--
 test/reducers/follow.test.js           | 4 ++--
 test/reducers/input.test.js            | 4 ++--
 test/reducers/setting.test.js          | 4 ++--
 test/shared/validators/setting.test.js | 2 +-
 test/store/index.test.js               | 2 +-
 15 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/test/actions/completion.test.js b/test/actions/completion.test.js
index da88f53..92de383 100644
--- a/test/actions/completion.test.js
+++ b/test/actions/completion.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import * as completionActions from '../../src/actions/completion';
+import actions from 'actions';
+import * as completionActions from 'actions/completion';
 
 describe("completion actions", () => {
   describe('setItems', () => {
diff --git a/test/actions/console.test.js b/test/actions/console.test.js
index 512ee40..2dbcf55 100644
--- a/test/actions/console.test.js
+++ b/test/actions/console.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import * as consoleActions from '../../src/actions/console';
+import actions from 'actions';
+import * as consoleActions from 'actions/console';
 
 describe("console actions", () => {
   describe("showCommand", () => {
diff --git a/test/actions/follow.test.js b/test/actions/follow.test.js
index 9439de7..32ab9e2 100644
--- a/test/actions/follow.test.js
+++ b/test/actions/follow.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import * as followActions from '../../src/actions/follow';
+import actions from 'actions';
+import * as followActions from 'actions/follow';
 
 describe('follow actions', () => {
   describe('enable', () => {
diff --git a/test/actions/input.test.js b/test/actions/input.test.js
index 904d3e7..0a2ab18 100644
--- a/test/actions/input.test.js
+++ b/test/actions/input.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import * as inputActions from '../../src/actions/input';
+import actions from 'actions';
+import * as inputActions from 'actions/input';
 
 describe("input actions", () => {
   describe("keyPress", () => {
diff --git a/test/components/follow.test.js b/test/components/follow.test.js
index f2f870e..c83e211 100644
--- a/test/components/follow.test.js
+++ b/test/components/follow.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import FollowComponent from '../../src/components/follow';
+import FollowComponent from 'components/follow';
 
 describe('FollowComponent', () => {
   describe('#codeChars', () => {
diff --git a/test/content/hint-key-producer.test.js b/test/content/hint-key-producer.test.js
index 74fb462..b2171ba 100644
--- a/test/content/hint-key-producer.test.js
+++ b/test/content/hint-key-producer.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import HintKeyProducer from '../../src/content/hint-key-producer';
+import HintKeyProducer from 'content/hint-key-producer';
 
 describe('HintKeyProducer class', () => {
   describe('#constructor', () => {
diff --git a/test/content/hint.test.js b/test/content/hint.test.js
index 9b2ab6e..1547971 100644
--- a/test/content/hint.test.js
+++ b/test/content/hint.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import Hint from '../../src/content/hint';
+import Hint from 'content/hint';
 
 describe('Hint class', () => {
   beforeEach(() => {
diff --git a/test/content/navigates.test.js b/test/content/navigates.test.js
index cf20435..b5144e9 100644
--- a/test/content/navigates.test.js
+++ b/test/content/navigates.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import * as navigates from '../../src/content/navigates';
+import * as navigates from 'content/navigates';
 
 describe('navigates module', () => {
   describe('#linkPrev', () => {
diff --git a/test/reducers/completion.test.js b/test/reducers/completion.test.js
index 79163bf..b2b36d9 100644
--- a/test/reducers/completion.test.js
+++ b/test/reducers/completion.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import completionReducer from '../../src/reducers/completion';
+import actions from 'actions';
+import completionReducer from 'reducers/completion';
 
 describe("completion reducer", () => {
   it ('return the initial state', () => {
diff --git a/test/reducers/console.test.js b/test/reducers/console.test.js
index 9820a08..d9f500f 100644
--- a/test/reducers/console.test.js
+++ b/test/reducers/console.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import consoleReducer from '../../src/reducers/console';
+import actions from 'actions';
+import consoleReducer from 'reducers/console';
 
 describe("console reducer", () => {
   it('return the initial state', () => {
diff --git a/test/reducers/follow.test.js b/test/reducers/follow.test.js
index 19a1300..79e75d4 100644
--- a/test/reducers/follow.test.js
+++ b/test/reducers/follow.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import followReducer from '../../src/reducers/follow';
+import actions from 'actions';
+import followReducer from 'reducers/follow';
 
 describe('follow reducer', () => {
   it ('returns the initial state', () => {
diff --git a/test/reducers/input.test.js b/test/reducers/input.test.js
index 3c3bf39..7b5a89c 100644
--- a/test/reducers/input.test.js
+++ b/test/reducers/input.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import inputReducer from '../../src/reducers/input';
+import actions from 'actions';
+import inputReducer from 'reducers/input';
 
 describe("input reducer", () => {
   it('return the initial state', () => {
diff --git a/test/reducers/setting.test.js b/test/reducers/setting.test.js
index 7261be6..1af031a 100644
--- a/test/reducers/setting.test.js
+++ b/test/reducers/setting.test.js
@@ -1,6 +1,6 @@
 import { expect } from "chai";
-import actions from '../../src/actions';
-import settingReducer from '../../src/reducers/setting';
+import actions from 'actions';
+import settingReducer from 'reducers/setting';
 
 describe("setting reducer", () => {
   it('return the initial state', () => {
diff --git a/test/shared/validators/setting.test.js b/test/shared/validators/setting.test.js
index 9baf858..15d6a10 100644
--- a/test/shared/validators/setting.test.js
+++ b/test/shared/validators/setting.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import { validate } from '../../../src/shared/validators/setting';
+import { validate } from 'shared/validators/setting';
 
 describe("setting validator", () => {
   describe("unknown top keys", () => {
diff --git a/test/store/index.test.js b/test/store/index.test.js
index e19d50e..5dce715 100644
--- a/test/store/index.test.js
+++ b/test/store/index.test.js
@@ -1,5 +1,5 @@
 import { expect } from "chai";
-import { createStore } from '../../src/store';
+import { createStore } from 'store';
 
 describe("Store class", () => {
   const reducer = (state, action) => {
-- 
cgit v1.2.3


From 32168a94e07478325a53779513533b76a6ef2c18 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Thu, 5 Oct 2017 20:14:58 +0900
Subject: fix imports in src

---
 src/actions/command.js             |  4 ++--
 src/actions/completion.js          |  2 +-
 src/actions/console.js             |  2 +-
 src/actions/follow.js              |  2 +-
 src/actions/input.js               |  2 +-
 src/actions/operation.js           |  8 ++++----
 src/actions/setting.js             |  6 +++---
 src/background/index.js            | 14 +++++++-------
 src/components/background-input.js |  4 ++--
 src/components/background.js       | 12 ++++++------
 src/components/console.js          |  4 ++--
 src/components/content-input.js    |  2 +-
 src/components/follow.js           |  8 ++++----
 src/components/setting.js          |  4 ++--
 src/content/index.js               | 16 ++++++++--------
 src/pages/console.js               | 12 ++++++------
 src/pages/settings.js              |  6 +++---
 src/reducers/completion.js         |  2 +-
 src/reducers/console.js            |  2 +-
 src/reducers/follow.js             |  2 +-
 src/reducers/index.js              | 10 +++++-----
 src/reducers/input.js              |  2 +-
 src/reducers/setting.js            |  2 +-
 src/shared/validators/setting.js   |  2 +-
 webpack.config.js                  |  2 +-
 25 files changed, 66 insertions(+), 66 deletions(-)

diff --git a/src/actions/command.js b/src/actions/command.js
index f578afd..a40cc97 100644
--- a/src/actions/command.js
+++ b/src/actions/command.js
@@ -1,5 +1,5 @@
-import * as tabs from '../background/tabs';
-import * as histories from '../background/histories';
+import * as tabs from 'background/tabs';
+import * as histories from 'background/histories';
 import * as consoleActions from './console';
 
 const normalizeUrl = (string, searchConfig) => {
diff --git a/src/actions/completion.js b/src/actions/completion.js
index 1ffb025..733f516 100644
--- a/src/actions/completion.js
+++ b/src/actions/completion.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const setItems = (groups) => {
   return {
diff --git a/src/actions/console.js b/src/actions/console.js
index e0ec631..ba1fbeb 100644
--- a/src/actions/console.js
+++ b/src/actions/console.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const showCommand = (text) => {
   return {
diff --git a/src/actions/follow.js b/src/actions/follow.js
index 7ab689e..708cd95 100644
--- a/src/actions/follow.js
+++ b/src/actions/follow.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const enable = (newTab) => {
   return {
diff --git a/src/actions/input.js b/src/actions/input.js
index 67788dd..61acb76 100644
--- a/src/actions/input.js
+++ b/src/actions/input.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const asKeymapChars = (key, ctrl) => {
   if (ctrl) {
diff --git a/src/actions/operation.js b/src/actions/operation.js
index 3ed7f05..97bcf45 100644
--- a/src/actions/operation.js
+++ b/src/actions/operation.js
@@ -1,8 +1,8 @@
-import operations from '../shared/operations';
-import messages from '../content/messages';
+import operations from 'shared/operations';
+import messages from 'content/messages';
 import * as consoleActions from './console';
-import * as tabs from '../background/tabs';
-import * as zooms from '../background/zooms';
+import * as tabs from 'background/tabs';
+import * as zooms from 'background/zooms';
 
 const exec = (operation, tab) => {
   switch (operation.type) {
diff --git a/src/actions/setting.js b/src/actions/setting.js
index 7898f06..2a47608 100644
--- a/src/actions/setting.js
+++ b/src/actions/setting.js
@@ -1,6 +1,6 @@
-import actions from '../actions';
-import messages from '../content/messages';
-import DefaultSettings from '../shared/default-settings';
+import actions from 'actions';
+import messages from 'content/messages';
+import DefaultSettings from 'shared/default-settings';
 
 const load = () => {
   return browser.storage.local.get('settings').then((value) => {
diff --git a/src/background/index.js b/src/background/index.js
index 9a1adc6..8dc55cb 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -1,10 +1,10 @@
-import * as consoleActions from '../actions/console';
-import * as settingsActions from '../actions/setting';
-import BackgroundComponent from '../components/background';
-import BackgroundInputComponent from '../components/background-input';
-import reducers from '../reducers';
-import messages from '../content/messages';
-import { createStore } from '../store';
+import * as consoleActions from 'actions/console';
+import * as settingsActions from 'actions/setting';
+import BackgroundComponent from 'components/background';
+import BackgroundInputComponent from 'components/background-input';
+import reducers from 'reducers';
+import messages from 'content/messages';
+import { createStore } from 'store';
 
 const store = createStore(reducers, (e, sender) => {
   console.error('Vim-Vixen:', e);
diff --git a/src/components/background-input.js b/src/components/background-input.js
index 4735d5a..bd6ecf9 100644
--- a/src/components/background-input.js
+++ b/src/components/background-input.js
@@ -1,5 +1,5 @@
-import * as inputActions from '../actions/input';
-import * as operationActions from '../actions/operation';
+import * as inputActions from 'actions/input';
+import * as operationActions from 'actions/operation';
 
 export default class BackgroundInputComponent {
   constructor(store) {
diff --git a/src/components/background.js b/src/components/background.js
index 0585a04..08d5115 100644
--- a/src/components/background.js
+++ b/src/components/background.js
@@ -1,9 +1,9 @@
-import messages from '../content/messages';
-import * as commandActions from '../actions/command';
-import * as consoleActions from '../actions/console';
-import * as inputActions from '../actions/input';
-import * as settingsActions from '../actions/setting';
-import * as tabActions from '../actions/tab';
+import messages from 'content/messages';
+import * as commandActions from 'actions/command';
+import * as consoleActions from 'actions/console';
+import * as inputActions from 'actions/input';
+import * as settingsActions from 'actions/setting';
+import * as tabActions from 'actions/tab';
 
 export default class BackgroundComponent {
   constructor(store) {
diff --git a/src/components/console.js b/src/components/console.js
index 9580dcf..25b135c 100644
--- a/src/components/console.js
+++ b/src/components/console.js
@@ -1,5 +1,5 @@
-import messages from '../content/messages';
-import * as completionActions from '../actions/completion';
+import messages from 'content/messages';
+import * as completionActions from 'actions/completion';
 
 export default class ConsoleComponent {
   constructor(wrapper, store) {
diff --git a/src/components/content-input.js b/src/components/content-input.js
index 10c785b..38d57fd 100644
--- a/src/components/content-input.js
+++ b/src/components/content-input.js
@@ -1,4 +1,4 @@
-import messages from '../content/messages';
+import messages from 'content/messages';
 
 export default class ContentInputComponent {
   constructor(target) {
diff --git a/src/components/follow.js b/src/components/follow.js
index d2d3902..9221759 100644
--- a/src/components/follow.js
+++ b/src/components/follow.js
@@ -1,7 +1,7 @@
-import * as followActions from '../actions/follow';
-import messages from '../content/messages';
-import Hint from '../content/hint';
-import HintKeyProducer from '../content/hint-key-producer';
+import * as followActions from 'actions/follow';
+import messages from 'content/messages';
+import Hint from 'content/hint';
+import HintKeyProducer from 'content/hint-key-producer';
 
 const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz';
 
diff --git a/src/components/setting.js b/src/components/setting.js
index 1f3b3fe..c2f99b6 100644
--- a/src/components/setting.js
+++ b/src/components/setting.js
@@ -1,5 +1,5 @@
-import * as settingActions from '../actions/setting';
-import { validate } from '../shared/validators/setting';
+import * as settingActions from 'actions/setting';
+import { validate } from 'shared/validators/setting';
 
 export default class SettingComponent {
   constructor(wrapper, store) {
diff --git a/src/content/index.js b/src/content/index.js
index a9a50be..31b37cf 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -1,13 +1,13 @@
 import './console-frame.scss';
 import * as consoleFrames from './console-frames';
-import * as scrolls from '../content/scrolls';
-import * as navigates from '../content/navigates';
-import * as followActions from '../actions/follow';
-import { createStore } from '../store';
-import ContentInputComponent from '../components/content-input';
-import FollowComponent from '../components/follow';
-import reducers from '../reducers';
-import operations from '../shared/operations';
+import * as scrolls from 'content/scrolls';
+import * as navigates from 'content/navigates';
+import * as followActions from 'actions/follow';
+import { createStore } from 'store';
+import ContentInputComponent from 'components/content-input';
+import FollowComponent from 'components/follow';
+import reducers from 'reducers';
+import operations from 'shared/operations';
 import messages from './messages';
 
 const store = createStore(reducers);
diff --git a/src/pages/console.js b/src/pages/console.js
index 0e152d3..a4536ec 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -1,10 +1,10 @@
 import './console.scss';
-import messages from '../content/messages';
-import CompletionComponent from '../components/completion';
-import ConsoleComponent from '../components/console';
-import reducers from '../reducers';
-import { createStore } from '../store';
-import * as completionActions from '../actions/completion';
+import messages from 'content/messages';
+import CompletionComponent from 'components/completion';
+import ConsoleComponent from 'components/console';
+import reducers from 'reducers';
+import { createStore } from 'store';
+import * as completionActions from 'actions/completion';
 
 const store = createStore(reducers);
 let completionComponent = null;
diff --git a/src/pages/settings.js b/src/pages/settings.js
index 076d23d..6e25e6f 100644
--- a/src/pages/settings.js
+++ b/src/pages/settings.js
@@ -1,7 +1,7 @@
 import './settings.scss';
-import SettingComponent from '../components/setting';
-import settingReducer from '../reducers/setting';
-import { createStore } from '../store';
+import SettingComponent from 'components/setting';
+import settingReducer from 'reducers/setting';
+import { createStore } from 'store';
 
 const store = createStore(settingReducer);
 let settingComponent = null;
diff --git a/src/reducers/completion.js b/src/reducers/completion.js
index 878aeac..f85a500 100644
--- a/src/reducers/completion.js
+++ b/src/reducers/completion.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const defaultState = {
   groupSelection: -1,
diff --git a/src/reducers/console.js b/src/reducers/console.js
index 5c49c3b..3e63672 100644
--- a/src/reducers/console.js
+++ b/src/reducers/console.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const defaultState = {
   errorShown: false,
diff --git a/src/reducers/follow.js b/src/reducers/follow.js
index 136b367..a2397b4 100644
--- a/src/reducers/follow.js
+++ b/src/reducers/follow.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const defaultState = {
   enabled: false,
diff --git a/src/reducers/index.js b/src/reducers/index.js
index db17edf..5a4f14b 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,8 +1,8 @@
-import inputReducer from '../reducers/input';
-import consoleReducer from '../reducers/console';
-import settingReducer from '../reducers/setting';
-import followReducer from '../reducers/follow';
-import completionReducer from '../reducers/completion';
+import inputReducer from 'reducers/input';
+import consoleReducer from 'reducers/console';
+import settingReducer from 'reducers/setting';
+import followReducer from 'reducers/follow';
+import completionReducer from 'reducers/completion';
 
 const defaultState = {
   input: inputReducer(undefined, {}),
diff --git a/src/reducers/input.js b/src/reducers/input.js
index 8be701e..2e4bcd8 100644
--- a/src/reducers/input.js
+++ b/src/reducers/input.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const defaultState = {
   keys: '',
diff --git a/src/reducers/setting.js b/src/reducers/setting.js
index 735d4fb..7326ed7 100644
--- a/src/reducers/setting.js
+++ b/src/reducers/setting.js
@@ -1,4 +1,4 @@
-import actions from '../actions';
+import actions from 'actions';
 
 const defaultState = {
   settings: {}
diff --git a/src/shared/validators/setting.js b/src/shared/validators/setting.js
index 4dc35ff..5039ec2 100644
--- a/src/shared/validators/setting.js
+++ b/src/shared/validators/setting.js
@@ -1,4 +1,4 @@
-import operations from '../operations';
+import operations from 'shared/operations';
 
 const VALID_TOP_KEYS = ['keymaps', 'search'];
 const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
diff --git a/webpack.config.js b/webpack.config.js
index e4d5a17..3d4ef03 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -40,7 +40,7 @@ module.exports = {
 
   resolve: {
     extensions: [ '.js' ],
-    modules: [path.join(__dirname), 'node_modules']
+    modules: [path.join(__dirname, 'src'), 'node_modules']
   },
 
   plugins: [
-- 
cgit v1.2.3


From 10ad62e60698c5d53ffcf58ae6abd182f7d3fc9c Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Fri, 6 Oct 2017 23:03:28 +0900
Subject: console command actions without store

---
 src/actions/command.js       | 147 -------------------------------------------
 src/components/background.js |  12 ++--
 src/components/console.js    |   6 +-
 src/content/messages.js      |   2 +-
 src/pages/console.js         |   1 -
 src/shared/commands.js       | 143 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 153 insertions(+), 158 deletions(-)
 delete mode 100644 src/actions/command.js
 create mode 100644 src/shared/commands.js

diff --git a/src/actions/command.js b/src/actions/command.js
deleted file mode 100644
index a40cc97..0000000
--- a/src/actions/command.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import * as tabs from 'background/tabs';
-import * as histories from 'background/histories';
-import * as consoleActions from './console';
-
-const normalizeUrl = (string, searchConfig) => {
-  try {
-    return new URL(string).href;
-  } catch (e) {
-    if (string.includes('.') && !string.includes(' ')) {
-      return 'http://' + string;
-    }
-    let query = encodeURI(string);
-    let template = searchConfig.engines[
-      searchConfig.default
-    ];
-    for (let key in searchConfig.engines) {
-      if (string.startsWith(key + ' ')) {
-        query = encodeURI(string.replace(key + ' ', ''));
-        template = searchConfig.engines[key];
-      }
-    }
-    return template.replace('{}', query);
-  }
-};
-
-const openCommand = (url) => {
-  return browser.tabs.query({
-    active: true, currentWindow: true
-  }).then((gotTabs) => {
-    if (gotTabs.length > 0) {
-      return browser.tabs.update(gotTabs[0].id, { url: url });
-    }
-  });
-};
-
-const tabopenCommand = (url) => {
-  return browser.tabs.create({ url: url });
-};
-
-const bufferCommand = (keywords) => {
-  return browser.tabs.query({
-    active: true, currentWindow: true
-  }).then((gotTabs) => {
-    if (gotTabs.length > 0) {
-      if (isNaN(keywords)) {
-        return tabs.selectByKeyword(gotTabs[0], keywords);
-      }
-      let index = parseInt(keywords, 10) - 1;
-      return tabs.selectAt(index);
-    }
-  });
-};
-
-const getOpenCompletions = (command, keywords, searchConfig) => {
-  return histories.getCompletions(keywords).then((pages) => {
-    let historyItems = pages.map((page) => {
-      return {
-        caption: page.title,
-        content: command + ' ' + page.url,
-        url: page.url
-      };
-    });
-    let engineNames = Object.keys(searchConfig.engines);
-    let engineItems = engineNames.filter(name => name.startsWith(keywords))
-      .map(name => ({
-        caption: name,
-        content: command + ' ' + name
-      }));
-
-    let completions = [];
-    if (engineItems.length > 0) {
-      completions.push({
-        name: 'Search Engines',
-        items: engineItems
-      });
-    }
-    if (historyItems.length > 0) {
-      completions.push({
-        name: 'History',
-        items: historyItems
-      });
-    }
-    return completions;
-  });
-};
-
-const doCommand = (name, remaining, settings) => {
-  switch (name) {
-  case 'o':
-  case 'open':
-    // TODO use search engined and pass keywords to them
-    return openCommand(normalizeUrl(remaining, settings.search));
-  case 't':
-  case 'tabopen':
-    return tabopenCommand(normalizeUrl(remaining, settings.search));
-  case 'b':
-  case 'buffer':
-    return bufferCommand(remaining);
-  }
-  throw new Error(name + ' command is not defined');
-};
-
-const getCompletions = (command, keywords, settings) => {
-  switch (command) {
-  case 'o':
-  case 'open':
-  case 't':
-  case 'tabopen':
-    return getOpenCompletions(command, keywords, settings.search);
-  case 'b':
-  case 'buffer':
-    return tabs.getCompletions(keywords).then((gotTabs) => {
-      let items = gotTabs.map((tab) => {
-        return {
-          caption: tab.title,
-          content: command + ' ' + tab.title,
-          url: tab.url,
-          icon: tab.favIconUrl
-        };
-      });
-      return [
-        {
-          name: 'Buffers',
-          items: items
-        }
-      ];
-    });
-  }
-  return Promise.resolve([]);
-};
-
-const exec = (line, settings) => {
-  let name = line.split(' ')[0];
-  let remaining = line.replace(name + ' ', '');
-  return doCommand(name, remaining, settings).then(() => {
-    return consoleActions.hide();
-  });
-};
-
-const complete = (line, settings) => {
-  let command = line.split(' ', 1)[0];
-  let keywords = line.replace(command + ' ', '');
-  return getCompletions(command, keywords, settings)
-    .then(consoleActions.setCompletions);
-};
-
-export { exec, complete };
diff --git a/src/components/background.js b/src/components/background.js
index 08d5115..195cfd9 100644
--- a/src/components/background.js
+++ b/src/components/background.js
@@ -1,9 +1,9 @@
 import messages from 'content/messages';
-import * as commandActions from 'actions/command';
 import * as consoleActions from 'actions/console';
 import * as inputActions from 'actions/input';
 import * as settingsActions from 'actions/setting';
 import * as tabActions from 'actions/tab';
+import * as commands from 'shared/commands';
 
 export default class BackgroundComponent {
   constructor(store) {
@@ -12,7 +12,7 @@ export default class BackgroundComponent {
 
     browser.runtime.onMessage.addListener((message, sender) => {
       try {
-        this.onMessage(message, sender);
+        return this.onMessage(message, sender);
       } catch (e) {
         this.store.dispatch(consoleActions.showError(e.message), sender);
       }
@@ -47,11 +47,9 @@ export default class BackgroundComponent {
       return this.store.dispatch(
         consoleActions.hide(), sender);
     case messages.CONSOLE_ENTERED:
-      return this.store.dispatch(
-        commandActions.exec(message.text, this.settings), sender);
-    case messages.CONSOLE_CHANGEED:
-      return this.store.dispatch(
-        commandActions.complete(message.text, this.settings), sender);
+      return commands.exec(message.text, this.settings);
+    case messages.CONSOLE_QUERY_COMPLETIONS:
+      return commands.complete(message.text, this.settings);
     case messages.SETTINGS_RELOAD:
       this.store.dispatch(settingsActions.load());
     }
diff --git a/src/components/console.js b/src/components/console.js
index 25b135c..177cfe5 100644
--- a/src/components/console.js
+++ b/src/components/console.js
@@ -36,7 +36,7 @@ export default class ConsoleComponent {
       return browser.runtime.sendMessage({
         type: messages.CONSOLE_ENTERED,
         text: e.target.value
-      });
+      }).then(this.onBlur);
     case KeyboardEvent.DOM_VK_TAB:
       if (e.shiftKey) {
         this.store.dispatch(completionActions.selectPrev());
@@ -63,8 +63,10 @@ export default class ConsoleComponent {
 
     this.prevValue = e.target.value;
     return browser.runtime.sendMessage({
-      type: messages.CONSOLE_CHANGEED,
+      type: messages.CONSOLE_QUERY_COMPLETIONS,
       text: e.target.value
+    }).then((completions) => {
+      this.store.dispatch(completionActions.setItems(completions));
     });
   }
 
diff --git a/src/content/messages.js b/src/content/messages.js
index df9fba2..72a566b 100644
--- a/src/content/messages.js
+++ b/src/content/messages.js
@@ -4,7 +4,7 @@ export default {
 
   CONSOLE_BLURRED: 'console.blured',
   CONSOLE_ENTERED: 'console.entered',
-  CONSOLE_CHANGEED: 'console.changed',
+  CONSOLE_QUERY_COMPLETIONS: 'console.query.completions',
 
   KEYDOWN: 'keydown',
 
diff --git a/src/pages/console.js b/src/pages/console.js
index a4536ec..4d78826 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -39,6 +39,5 @@ browser.runtime.onMessage.addListener((action) => {
   if (action.type === messages.STATE_UPDATE) {
     let state = action.state.console;
     consoleComponent.update(state);
-    store.dispatch(completionActions.setItems(state.completions));
   }
 });
diff --git a/src/shared/commands.js b/src/shared/commands.js
new file mode 100644
index 0000000..b1d8780
--- /dev/null
+++ b/src/shared/commands.js
@@ -0,0 +1,143 @@
+import * as tabs from 'background/tabs';
+import * as histories from 'background/histories';
+
+const normalizeUrl = (string, searchConfig) => {
+  try {
+    return new URL(string).href;
+  } catch (e) {
+    if (string.includes('.') && !string.includes(' ')) {
+      return 'http://' + string;
+    }
+    let query = encodeURI(string);
+    let template = searchConfig.engines[
+      searchConfig.default
+    ];
+    for (let key in searchConfig.engines) {
+      if (string.startsWith(key + ' ')) {
+        query = encodeURI(string.replace(key + ' ', ''));
+        template = searchConfig.engines[key];
+      }
+    }
+    return template.replace('{}', query);
+  }
+};
+
+const openCommand = (url) => {
+  return browser.tabs.query({
+    active: true, currentWindow: true
+  }).then((gotTabs) => {
+    if (gotTabs.length > 0) {
+      return browser.tabs.update(gotTabs[0].id, { url: url });
+    }
+  });
+};
+
+const tabopenCommand = (url) => {
+  return browser.tabs.create({ url: url });
+};
+
+const bufferCommand = (keywords) => {
+  return browser.tabs.query({
+    active: true, currentWindow: true
+  }).then((gotTabs) => {
+    if (gotTabs.length > 0) {
+      if (isNaN(keywords)) {
+        return tabs.selectByKeyword(gotTabs[0], keywords);
+      }
+      let index = parseInt(keywords, 10) - 1;
+      return tabs.selectAt(index);
+    }
+  });
+};
+
+const getOpenCompletions = (command, keywords, searchConfig) => {
+  return histories.getCompletions(keywords).then((pages) => {
+    let historyItems = pages.map((page) => {
+      return {
+        caption: page.title,
+        content: command + ' ' + page.url,
+        url: page.url
+      };
+    });
+    let engineNames = Object.keys(searchConfig.engines);
+    let engineItems = engineNames.filter(name => name.startsWith(keywords))
+      .map(name => ({
+        caption: name,
+        content: command + ' ' + name
+      }));
+
+    let completions = [];
+    if (engineItems.length > 0) {
+      completions.push({
+        name: 'Search Engines',
+        items: engineItems
+      });
+    }
+    if (historyItems.length > 0) {
+      completions.push({
+        name: 'History',
+        items: historyItems
+      });
+    }
+    return completions;
+  });
+};
+
+const doCommand = (name, remaining, settings) => {
+  switch (name) {
+  case 'o':
+  case 'open':
+    // TODO use search engined and pass keywords to them
+    return openCommand(normalizeUrl(remaining, settings.search));
+  case 't':
+  case 'tabopen':
+    return tabopenCommand(normalizeUrl(remaining, settings.search));
+  case 'b':
+  case 'buffer':
+    return bufferCommand(remaining);
+  }
+  throw new Error(name + ' command is not defined');
+};
+
+const getCompletions = (command, keywords, settings) => {
+  switch (command) {
+  case 'o':
+  case 'open':
+  case 't':
+  case 'tabopen':
+    return getOpenCompletions(command, keywords, settings.search);
+  case 'b':
+  case 'buffer':
+    return tabs.getCompletions(keywords).then((gotTabs) => {
+      let items = gotTabs.map((tab) => {
+        return {
+          caption: tab.title,
+          content: command + ' ' + tab.title,
+          url: tab.url,
+          icon: tab.favIconUrl
+        };
+      });
+      return [
+        {
+          name: 'Buffers',
+          items: items
+        }
+      ];
+    });
+  }
+  return Promise.resolve([]);
+};
+
+const exec = (line, settings) => {
+  let name = line.split(' ')[0];
+  let remaining = line.replace(name + ' ', '');
+  return doCommand(name, remaining, settings);
+};
+
+const complete = (line, settings) => {
+  let command = line.split(' ', 1)[0];
+  let keywords = line.replace(command + ' ', '');
+  return getCompletions(command, keywords, settings);
+};
+
+export { exec, complete };
-- 
cgit v1.2.3


From 4cb17031d11d76275de51e31218fb87359e7d826 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Fri, 6 Oct 2017 23:55:52 +0900
Subject: [wip] remove STATE_UPDATE

---
 src/actions/operation.js  | 20 +++++++++++++-------
 src/background/index.js   |  9 ---------
 src/components/console.js |  4 ++--
 src/content/index.js      | 13 ++++---------
 src/content/messages.js   |  4 +++-
 src/pages/console.js      | 13 +++++++++----
 6 files changed, 31 insertions(+), 32 deletions(-)

diff --git a/src/actions/operation.js b/src/actions/operation.js
index 97bcf45..295fd4f 100644
--- a/src/actions/operation.js
+++ b/src/actions/operation.js
@@ -1,9 +1,15 @@
 import operations from 'shared/operations';
 import messages from 'content/messages';
-import * as consoleActions from './console';
 import * as tabs from 'background/tabs';
 import * as zooms from 'background/zooms';
 
+const sendConsoleShowCommand = (tab, command) => {
+  return browser.tabs.sendMessage(tab.id, {
+    type: messages.CONSOLE_SHOW_COMMAND,
+    command,
+  });
+};
+
 const exec = (operation, tab) => {
   switch (operation.type) {
   case operations.TAB_CLOSE:
@@ -23,21 +29,21 @@ const exec = (operation, tab) => {
   case operations.ZOOM_NEUTRAL:
     return zooms.neutral();
   case operations.COMMAND_SHOW:
-    return consoleActions.showCommand('');
+    return sendConsoleShowCommand(tab, '');
   case operations.COMMAND_SHOW_OPEN:
     if (operation.alter) {
       // alter url
-      return consoleActions.showCommand('open ' + tab.url);
+      return sendConsoleShowCommand(tab, 'open ' + tab.url);
     }
-    return consoleActions.showCommand('open ');
+    return sendConsoleShowCommand(tab, 'open ');
   case operations.COMMAND_SHOW_TABOPEN:
     if (operation.alter) {
       // alter url
-      return consoleActions.showCommand('tabopen ' + tab.url);
+      return sendConsoleShowCommand(tab, 'tabopen ' + tab.url);
     }
-    return consoleActions.showCommand('tabopen ');
+    return sendConsoleShowCommand(tab, 'tabopen ');
   case operations.COMMAND_SHOW_BUFFER:
-    return consoleActions.showCommand('buffer ');
+    return sendConsoleShowCommand(tab, 'buffer ');
   default:
     return browser.tabs.sendMessage(tab.id, {
       type: messages.CONTENT_OPERATION,
diff --git a/src/background/index.js b/src/background/index.js
index 8dc55cb..05d3553 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -3,7 +3,6 @@ import * as settingsActions from 'actions/setting';
 import BackgroundComponent from 'components/background';
 import BackgroundInputComponent from 'components/background-input';
 import reducers from 'reducers';
-import messages from 'content/messages';
 import { createStore } from 'store';
 
 const store = createStore(reducers, (e, sender) => {
@@ -18,13 +17,5 @@ store.subscribe((sender) => {
   backgroundComponent.update(sender);
   backgroundInputComponent.update(sender);
 });
-store.subscribe((sender) => {
-  if (sender) {
-    return browser.tabs.sendMessage(sender.tab.id, {
-      type: messages.STATE_UPDATE,
-      state: store.getState()
-    });
-  }
-});
 
 store.dispatch(settingsActions.load());
diff --git a/src/components/console.js b/src/components/console.js
index 177cfe5..3a7f88b 100644
--- a/src/components/console.js
+++ b/src/components/console.js
@@ -70,8 +70,8 @@ export default class ConsoleComponent {
     });
   }
 
-  // TODO use store/reducer to update state.  
-  update(state) {
+  update() {
+    let state = this.store.getState().console;
     if (!this.prevState.commandShown && state.commandShown) {
       this.showCommand(state.commandText);
     } else if (!state.commandShown) {
diff --git a/src/content/index.js b/src/content/index.js
index 31b37cf..b29118d 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -55,17 +55,12 @@ const execOperation = (operation) => {
   }
 };
 
-const update = (state) => {
-  if (!state.console.commandShown) {
-    window.focus();
-    consoleFrames.blur(window.document);
-  }
-};
-
 browser.runtime.onMessage.addListener((action) => {
   switch (action.type) {
-  case messages.STATE_UPDATE:
-    return update(action.state);
+  case messages.CONSOLE_HIDE:
+    window.focus();
+    consoleFrames.blur(window.document);
+    return Promise.resolve();
   case messages.CONTENT_OPERATION:
     execOperation(action.operation);
     return Promise.resolve();
diff --git a/src/content/messages.js b/src/content/messages.js
index 72a566b..0e66fa0 100644
--- a/src/content/messages.js
+++ b/src/content/messages.js
@@ -1,10 +1,12 @@
 export default {
-  STATE_UPDATE: 'state.update',
   CONTENT_OPERATION: 'content.operation',
 
   CONSOLE_BLURRED: 'console.blured',
   CONSOLE_ENTERED: 'console.entered',
   CONSOLE_QUERY_COMPLETIONS: 'console.query.completions',
+  CONSOLE_SHOW_COMMAND: 'console.show.command',
+  CONSOLE_SHOW_ERROR: 'console.show.error',
+  CONSOLE_HIDE: 'console.hide',
 
   KEYDOWN: 'keydown',
 
diff --git a/src/pages/console.js b/src/pages/console.js
index 4d78826..4d3dd3f 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -4,7 +4,7 @@ import CompletionComponent from 'components/completion';
 import ConsoleComponent from 'components/console';
 import reducers from 'reducers';
 import { createStore } from 'store';
-import * as completionActions from 'actions/completion';
+import * as consoleActions from 'actions/console';
 
 const store = createStore(reducers);
 let completionComponent = null;
@@ -20,6 +20,7 @@ window.addEventListener('load', () => {
 
 store.subscribe(() => {
   completionComponent.update();
+  consoleComponent.update();
 
   let state = store.getState().completion;
 
@@ -36,8 +37,12 @@ store.subscribe(() => {
 });
 
 browser.runtime.onMessage.addListener((action) => {
-  if (action.type === messages.STATE_UPDATE) {
-    let state = action.state.console;
-    consoleComponent.update(state);
+  switch (action.type) {
+  case messages.CONSOLE_SHOW_COMMAND:
+    return store.dispatch(consoleActions.showCommand(action.command));
+  case messages.CONSOLE_SHOW_ERROR:
+    return store.dispatch(consoleActions.showError(action.command));
+  case messages.CONSOLE_HIDE:
+    return store.dispatch(consoleActions.hide(action.command));
   }
 });
-- 
cgit v1.2.3


From 45368384d1bbcbc005bf1eb3cdc7f7c8b137c28c Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sat, 7 Oct 2017 07:51:39 +0900
Subject: remove completion actions/reducer

---
 src/actions/completion.js        | 22 ----------
 src/actions/console.js           | 26 +++++++++---
 src/actions/index.js             | 10 +++--
 src/components/completion.js     |  6 +--
 src/components/console.js        | 20 +++++++--
 src/pages/console.js             | 14 -------
 src/reducers/completion.js       | 68 ------------------------------
 src/reducers/console.js          | 60 +++++++++++++++++++++++++--
 src/reducers/index.js            |  3 --
 test/actions/completion.test.js  | 27 ------------
 test/actions/console.test.js     | 30 ++++++++++----
 test/reducers/completion.test.js | 90 ----------------------------------------
 test/reducers/console.test.js    | 87 +++++++++++++++++++++++++++++++++++---
 13 files changed, 204 insertions(+), 259 deletions(-)
 delete mode 100644 src/actions/completion.js
 delete mode 100644 src/reducers/completion.js
 delete mode 100644 test/actions/completion.test.js
 delete mode 100644 test/reducers/completion.test.js

diff --git a/src/actions/completion.js b/src/actions/completion.js
deleted file mode 100644
index 733f516..0000000
--- a/src/actions/completion.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import actions from 'actions';
-
-const setItems = (groups) => {
-  return {
-    type: actions.COMPLETION_SET_ITEMS,
-    groups,
-  };
-};
-
-const selectNext = () => {
-  return {
-    type: actions.COMPLETION_SELECT_NEXT
-  };
-};
-
-const selectPrev = () => {
-  return {
-    type: actions.COMPLETION_SELECT_PREV
-  };
-};
-
-export { setItems, selectNext, selectPrev };
diff --git a/src/actions/console.js b/src/actions/console.js
index ba1fbeb..4183489 100644
--- a/src/actions/console.js
+++ b/src/actions/console.js
@@ -7,6 +7,19 @@ const showCommand = (text) => {
   };
 };
 
+const showError = (text) => {
+  return {
+    type: actions.CONSOLE_SHOW_ERROR,
+    text: text
+  };
+};
+
+const hide = () => {
+  return {
+    type: actions.CONSOLE_HIDE
+  };
+};
+
 const setCompletions = (completions) => {
   return {
     type: actions.CONSOLE_SET_COMPLETIONS,
@@ -14,17 +27,18 @@ const setCompletions = (completions) => {
   };
 };
 
-const showError = (text) => {
+const completionNext = () => {
   return {
-    type: actions.CONSOLE_SHOW_ERROR,
-    text: text
+    type: actions.CONSOLE_COMPLETION_NEXT,
   };
 };
 
-const hide = () => {
+const completionPrev = () => {
   return {
-    type: actions.CONSOLE_HIDE
+    type: actions.CONSOLE_COMPLETION_PREV,
   };
 };
 
-export { showCommand, setCompletions, showError, hide };
+export {
+  showCommand, showError, hide, setCompletions, completionNext, completionPrev
+};
diff --git a/src/actions/index.js b/src/actions/index.js
index 4e8d4a7..6a64795 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -1,9 +1,11 @@
 export default {
   // console commands
-  CONSOLE_SHOW_COMMAND: 'vimvixen.console.show.command',
-  CONSOLE_SET_COMPLETIONS: 'vimvixen.console.set.completions',
-  CONSOLE_SHOW_ERROR: 'vimvixen.console.show.error',
-  CONSOLE_HIDE: 'vimvixen.console.hide',
+  CONSOLE_SHOW_COMMAND: 'console.show.command',
+  CONSOLE_SET_COMPLETIONS: 'console.set.completions',
+  CONSOLE_SHOW_ERROR: 'console.show.error',
+  CONSOLE_HIDE: 'console.hide',
+  CONSOLE_COMPLETION_NEXT: 'console.completion.next',
+  CONSOLE_COMPLETION_PREV: 'console.completion.prev',
 
   // User input
   INPUT_KEY_PRESS: 'input.key,press',
diff --git a/src/components/completion.js b/src/components/completion.js
index d1fdd06..f527a84 100644
--- a/src/components/completion.js
+++ b/src/components/completion.js
@@ -6,15 +6,15 @@ export default class Completion {
   }
 
   update() {
-    let state = this.store.getState().completion;
+    let state = this.store.getState().console;
     if (JSON.stringify(this.prevState) === JSON.stringify(state)) {
       return;
     }
 
     this.wrapper.innerHTML = '';
 
-    for (let i = 0; i < state.groups.length; ++i) {
-      let group = state.groups[i];
+    for (let i = 0; i < state.completions.length; ++i) {
+      let group = state.completions[i];
       let title = this.createCompletionTitle(group.name);
       this.wrapper.append(title);
 
diff --git a/src/components/console.js b/src/components/console.js
index 3a7f88b..12341c1 100644
--- a/src/components/console.js
+++ b/src/components/console.js
@@ -1,5 +1,5 @@
 import messages from 'content/messages';
-import * as completionActions from 'actions/completion';
+import * as consoleActions from 'actions/console';
 
 export default class ConsoleComponent {
   constructor(wrapper, store) {
@@ -39,9 +39,9 @@ export default class ConsoleComponent {
       }).then(this.onBlur);
     case KeyboardEvent.DOM_VK_TAB:
       if (e.shiftKey) {
-        this.store.dispatch(completionActions.selectPrev());
+        this.store.dispatch(consoleActions.completionPrev());
       } else {
-        this.store.dispatch(completionActions.selectNext());
+        this.store.dispatch(consoleActions.completionNext());
       }
       e.stopPropagation();
       e.preventDefault();
@@ -66,7 +66,7 @@ export default class ConsoleComponent {
       type: messages.CONSOLE_QUERY_COMPLETIONS,
       text: e.target.value
     }).then((completions) => {
-      this.store.dispatch(completionActions.setItems(completions));
+      this.store.dispatch(consoleActions.setCompletions(completions));
     });
   }
 
@@ -85,6 +85,18 @@ export default class ConsoleComponent {
       this.hideError();
     }
 
+    if (state.groupSelection >= 0 && state.itemSelection >= 0) {
+      let group = state.completions[state.groupSelection];
+      let item = group.items[state.itemSelection];
+      this.setCommandValue(item.content);
+    } else if (state.completions.length > 0 &&
+      JSON.stringify(this.prevState.completions) ===
+        JSON.stringify(state.completions)) {
+      // Reset input only completion groups not changed (unselected an item in
+      // completion) in order to avoid to override previous input
+      this.setCommandCompletionOrigin();
+    }
+
     this.prevState = state;
   }
 
diff --git a/src/pages/console.js b/src/pages/console.js
index 4d3dd3f..20a60f6 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -9,7 +9,6 @@ import * as consoleActions from 'actions/console';
 const store = createStore(reducers);
 let completionComponent = null;
 let consoleComponent = null;
-let prevState = {};
 
 window.addEventListener('load', () => {
   let wrapper = document.querySelector('#vimvixen-console-completion');
@@ -21,19 +20,6 @@ window.addEventListener('load', () => {
 store.subscribe(() => {
   completionComponent.update();
   consoleComponent.update();
-
-  let state = store.getState().completion;
-
-  if (state.groupSelection >= 0) {
-    let item = state.groups[state.groupSelection].items[state.itemSelection];
-    consoleComponent.setCommandValue(item.content);
-  } else if (state.groups.length > 0 &&
-    JSON.stringify(prevState.groups) === JSON.stringify(state.groups)) {
-    // Reset input only completion groups not changed (unselected an item in
-    // completion) in order to avoid to override previous input
-    consoleComponent.setCommandCompletionOrigin();
-  }
-  prevState = state;
 });
 
 browser.runtime.onMessage.addListener((action) => {
diff --git a/src/reducers/completion.js b/src/reducers/completion.js
deleted file mode 100644
index f85a500..0000000
--- a/src/reducers/completion.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import actions from 'actions';
-
-const defaultState = {
-  groupSelection: -1,
-  itemSelection: -1,
-  groups: [],
-};
-
-const nextSelection = (state) => {
-  if (state.groupSelection < 0) {
-    return [0, 0];
-  }
-
-  let group = state.groups[state.groupSelection];
-  if (state.groupSelection + 1 >= state.groups.length &&
-    state.itemSelection + 1 >= group.items.length) {
-    return [-1, -1];
-  }
-  if (state.itemSelection + 1 >= group.items.length) {
-    return [state.groupSelection + 1, 0];
-  }
-  return [state.groupSelection, state.itemSelection + 1];
-};
-
-const prevSelection = (state) => {
-  if (state.groupSelection < 0) {
-    return [
-      state.groups.length - 1,
-      state.groups[state.groups.length - 1].items.length - 1
-    ];
-  }
-  if (state.groupSelection === 0 && state.itemSelection === 0) {
-    return [-1, -1];
-  } else if (state.itemSelection === 0) {
-    return [
-      state.groupSelection - 1,
-      state.groups[state.groupSelection - 1].items.length - 1
-    ];
-  }
-  return [state.groupSelection, state.itemSelection - 1];
-};
-
-export default function reducer(state = defaultState, action = {}) {
-  switch (action.type) {
-  case actions.COMPLETION_SET_ITEMS:
-    return Object.assign({}, state, {
-      groups: action.groups,
-      groupSelection: -1,
-      itemSelection: -1,
-    });
-  case actions.COMPLETION_SELECT_NEXT: {
-    let next = nextSelection(state);
-    return Object.assign({}, state, {
-      groupSelection: next[0],
-      itemSelection: next[1],
-    });
-  }
-  case actions.COMPLETION_SELECT_PREV: {
-    let next = prevSelection(state);
-    return Object.assign({}, state, {
-      groupSelection: next[0],
-      itemSelection: next[1],
-    });
-  }
-  default:
-    return state;
-  }
-}
diff --git a/src/reducers/console.js b/src/reducers/console.js
index 3e63672..b9ed5b8 100644
--- a/src/reducers/console.js
+++ b/src/reducers/console.js
@@ -6,6 +6,42 @@ const defaultState = {
   commandShown: false,
   commandText: '',
   completions: [],
+  groupSelection: -1,
+  itemSelection: -1,
+};
+
+const nextSelection = (state) => {
+  if (state.groupSelection < 0) {
+    return [0, 0];
+  }
+
+  let group = state.completions[state.groupSelection];
+  if (state.groupSelection + 1 >= state.completions.length &&
+    state.itemSelection + 1 >= group.items.length) {
+    return [-1, -1];
+  }
+  if (state.itemSelection + 1 >= group.items.length) {
+    return [state.groupSelection + 1, 0];
+  }
+  return [state.groupSelection, state.itemSelection + 1];
+};
+
+const prevSelection = (state) => {
+  if (state.groupSelection < 0) {
+    return [
+      state.completions.length - 1,
+      state.completions[state.completions.length - 1].items.length - 1
+    ];
+  }
+  if (state.groupSelection === 0 && state.itemSelection === 0) {
+    return [-1, -1];
+  } else if (state.itemSelection === 0) {
+    return [
+      state.groupSelection - 1,
+      state.completions[state.groupSelection - 1].items.length - 1
+    ];
+  }
+  return [state.groupSelection, state.itemSelection - 1];
 };
 
 export default function reducer(state = defaultState, action = {}) {
@@ -17,10 +53,6 @@ export default function reducer(state = defaultState, action = {}) {
       errorShown: false,
       completions: []
     });
-  case actions.CONSOLE_SET_COMPLETIONS:
-    return Object.assign({}, state, {
-      completions: action.completions
-    });
   case actions.CONSOLE_SHOW_ERROR:
     return Object.assign({}, state, {
       errorText: action.text,
@@ -36,6 +68,26 @@ export default function reducer(state = defaultState, action = {}) {
       errorShown: false,
       commandShown: false
     });
+  case actions.CONSOLE_SET_COMPLETIONS:
+    return Object.assign({}, state, {
+      completions: action.completions,
+      groupSelection: -1,
+      itemSelection: -1,
+    });
+  case actions.CONSOLE_COMPLETION_NEXT: {
+    let next = nextSelection(state);
+    return Object.assign({}, state, {
+      groupSelection: next[0],
+      itemSelection: next[1],
+    });
+  }
+  case actions.CONSOLE_COMPLETION_PREV: {
+    let next = prevSelection(state);
+    return Object.assign({}, state, {
+      groupSelection: next[0],
+      itemSelection: next[1],
+    });
+  }
   default:
     return state;
   }
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 5a4f14b..8ed6452 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -2,14 +2,12 @@ import inputReducer from 'reducers/input';
 import consoleReducer from 'reducers/console';
 import settingReducer from 'reducers/setting';
 import followReducer from 'reducers/follow';
-import completionReducer from 'reducers/completion';
 
 const defaultState = {
   input: inputReducer(undefined, {}),
   console: consoleReducer(undefined, {}),
   setting: settingReducer(undefined, {}),
   follow: followReducer(undefined, {}),
-  completion: completionReducer(undefined, {}),
 };
 
 export default function reducer(state = defaultState, action = {}) {
@@ -18,6 +16,5 @@ export default function reducer(state = defaultState, action = {}) {
     console: consoleReducer(state.console, action),
     setting: settingReducer(state.setting, action),
     follow: followReducer(state.follow, action),
-    completion: completionReducer(state.completion, action),
   });
 }
diff --git a/test/actions/completion.test.js b/test/actions/completion.test.js
deleted file mode 100644
index 92de383..0000000
--- a/test/actions/completion.test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { expect } from "chai";
-import actions from 'actions';
-import * as completionActions from 'actions/completion';
-
-describe("completion actions", () => {
-  describe('setItems', () => {
-    it('create COMPLETION_SET_ITEMS action', () => {
-      let action = completionActions.setItems([1, 2, 3]);
-      expect(action.type).to.equal(actions.COMPLETION_SET_ITEMS);
-      expect(action.groups).to.deep.equal([1, 2, 3]);
-    });
-  });
-
-  describe('selectNext', () => {
-    it('create COMPLETION_SELECT_NEXT action', () => {
-      let action = completionActions.selectNext();
-      expect(action.type).to.equal(actions.COMPLETION_SELECT_NEXT);
-    });
-  });
-
-  describe('selectPrev', () => {
-    it('create COMPLETION_SELECT_PREV action', () => {
-      let action = completionActions.selectPrev();
-      expect(action.type).to.equal(actions.COMPLETION_SELECT_PREV);
-    });
-  });
-});
diff --git a/test/actions/console.test.js b/test/actions/console.test.js
index 2dbcf55..ff905bc 100644
--- a/test/actions/console.test.js
+++ b/test/actions/console.test.js
@@ -11,14 +11,6 @@ describe("console actions", () => {
     });
   });
 
-  describe("setCompletions", () => {
-    it('create CONSOLE_SET_COMPLETIONS action', () => {
-      let action = consoleActions.setCompletions([1,2,3]);
-      expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS);
-      expect(action.completions).to.deep.equal([1, 2, 3]);
-    });
-  });
-
   describe("showError", () => {
     it('create CONSOLE_SHOW_ERROR action', () => {
       let action = consoleActions.showError('an error');
@@ -33,5 +25,27 @@ describe("console actions", () => {
       expect(action.type).to.equal(actions.CONSOLE_HIDE);
     });
   });
+
+  describe("setCompletions", () => {
+    it('create CONSOLE_SET_COMPLETIONS action', () => {
+      let action = consoleActions.setCompletions([1,2,3]);
+      expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS);
+      expect(action.completions).to.deep.equal([1, 2, 3]);
+    });
+  });
+
+  describe("completionPrev", () => {
+    it('create CONSOLE_COMPLETION_PREV action', () => {
+      let action = consoleActions.completionPrev();
+      expect(action.type).to.equal(actions.CONSOLE_COMPLETION_PREV);
+    });
+  });
+
+  describe("completionNext", () => {
+    it('create CONSOLE_COMPLETION_NEXT action', () => {
+      let action = consoleActions.completionNext();
+      expect(action.type).to.equal(actions.CONSOLE_COMPLETION_NEXT);
+    });
+  });
 });
 
diff --git a/test/reducers/completion.test.js b/test/reducers/completion.test.js
deleted file mode 100644
index b2b36d9..0000000
--- a/test/reducers/completion.test.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import { expect } from "chai";
-import actions from 'actions';
-import completionReducer from 'reducers/completion';
-
-describe("completion reducer", () => {
-  it ('return the initial state', () => {
-    let state = completionReducer(undefined, {});
-    expect(state).to.have.property('groupSelection', -1);
-    expect(state).to.have.property('itemSelection', -1);
-    expect(state).to.have.deep.property('groups', []);
-  });
-
-  it ('return next state for COMPLETION_SET_ITEMS', () => {
-    let state = {
-      groupSelection: 0,
-      itemSelection: 0,
-      groups: [],
-    }
-    let action = {
-      type: actions.COMPLETION_SET_ITEMS,
-      groups: [{
-        name: 'Apple',
-        items: [1, 2, 3]
-      }, {
-        name: 'Banana',
-        items: [4, 5, 6]
-      }]
-    }
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groups', action.groups);
-    expect(state).to.have.property('groupSelection', -1);
-    expect(state).to.have.property('itemSelection', -1);
-  });
-
-  it ('return next state for COMPLETION_SELECT_NEXT', () => {
-    let action = { type: actions.COMPLETION_SELECT_NEXT };
-    let state = {
-      groupSelection: -1,
-      itemSelection: -1,
-      groups: [{
-        name: 'Apple',
-        items: [1, 2]
-      }, {
-        name: 'Banana',
-        items: [3]
-      }]
-    };
-
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', 0);
-    expect(state).to.have.property('itemSelection', 0);
-
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', 0);
-    expect(state).to.have.property('itemSelection', 1);
-
-    state = completionReducer(state, action);
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', -1);
-    expect(state).to.have.property('itemSelection', -1);
-  });
-
-  it ('return next state for COMPLETION_SELECT_PREV', () => {
-    let action = { type: actions.COMPLETION_SELECT_PREV };
-    let state = {
-      groupSelection: -1,
-      itemSelection: -1,
-      groups: [{
-        name: 'Apple',
-        items: [1, 2]
-      }, {
-        name: 'Banana',
-        items: [3]
-      }]
-    };
-
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', 1);
-    expect(state).to.have.property('itemSelection', 0);
-
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', 0);
-    expect(state).to.have.property('itemSelection', 1);
-
-    state = completionReducer(state, action);
-    state = completionReducer(state, action);
-    expect(state).to.have.property('groupSelection', -1);
-    expect(state).to.have.property('itemSelection', -1);
-  });
-});
diff --git a/test/reducers/console.test.js b/test/reducers/console.test.js
index d9f500f..5ebf4bc 100644
--- a/test/reducers/console.test.js
+++ b/test/reducers/console.test.js
@@ -10,6 +10,8 @@ describe("console reducer", () => {
     expect(state).to.have.property('commandShown', false);
     expect(state).to.have.property('commandText', '');
     expect(state).to.have.deep.property('completions', []);
+    expect(state).to.have.property('groupSelection', -1);
+    expect(state).to.have.property('itemSelection', -1);
   });
 
   it('return next state for CONSOLE_SHOW_COMMAND', () => {
@@ -20,12 +22,6 @@ describe("console reducer", () => {
     expect(state).to.have.property('errorShown', false);
   });
 
-  it('return next state for CONSOLE_SET_COMPLETIONS', () => {
-    let action = { type: actions.CONSOLE_SET_COMPLETIONS, completions: [1, 2, 3] };
-    let state = consoleReducer({}, action);
-    expect(state).to.have.deep.property('completions', [1, 2, 3]);
-  });
-
   it('return next state for CONSOLE_SHOW_ERROR', () => {
     let action = { type: actions.CONSOLE_SHOW_ERROR, text: 'an error' };
     let state = consoleReducer({}, action);
@@ -40,4 +36,83 @@ describe("console reducer", () => {
     expect(state).to.have.property('errorShown', false);
     expect(state).to.have.property('commandShown', false);
   });
+
+  it ('return next state for CONSOLE_SET_COMPLETIONS', () => {
+    let state = {
+      groupSelection: 0,
+      itemSelection: 0,
+      completions: [],
+    }
+    let action = {
+      type: actions.CONSOLE_SET_COMPLETIONS,
+      completions: [{
+        name: 'Apple',
+        items: [1, 2, 3]
+      }, {
+        name: 'Banana',
+        items: [4, 5, 6]
+      }]
+    }
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('completions', action.completions);
+    expect(state).to.have.property('groupSelection', -1);
+    expect(state).to.have.property('itemSelection', -1);
+  });
+
+  it ('return next state for CONSOLE_COMPLETION_NEXT', () => {
+    let action = { type: actions.CONSOLE_COMPLETION_NEXT };
+    let state = {
+      groupSelection: -1,
+      itemSelection: -1,
+      completions: [{
+        name: 'Apple',
+        items: [1, 2]
+      }, {
+        name: 'Banana',
+        items: [3]
+      }]
+    };
+
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', 0);
+    expect(state).to.have.property('itemSelection', 0);
+
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', 0);
+    expect(state).to.have.property('itemSelection', 1);
+
+    state = consoleReducer(state, action);
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', -1);
+    expect(state).to.have.property('itemSelection', -1);
+  });
+
+  it ('return next state for CONSOLE_COMPLETION_PREV', () => {
+    let action = { type: actions.CONSOLE_COMPLETION_PREV };
+    let state = {
+      groupSelection: -1,
+      itemSelection: -1,
+      completions: [{
+        name: 'Apple',
+        items: [1, 2]
+      }, {
+        name: 'Banana',
+        items: [3]
+      }]
+    };
+
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', 1);
+    expect(state).to.have.property('itemSelection', 0);
+
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', 0);
+    expect(state).to.have.property('itemSelection', 1);
+
+    state = consoleReducer(state, action);
+    state = consoleReducer(state, action);
+    expect(state).to.have.property('groupSelection', -1);
+    expect(state).to.have.property('itemSelection', -1);
+  });
+
 });
-- 
cgit v1.2.3


From 9fb7bf96be786acfbad97f7c76bc423a401dd657 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sat, 7 Oct 2017 11:00:34 +0900
Subject: fix console errors

---
 src/background/index.js      |  7 +++++--
 src/components/background.js | 18 +++++++++++++-----
 src/pages/console.js         |  2 +-
 3 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/background/index.js b/src/background/index.js
index 05d3553..b966c13 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -1,5 +1,5 @@
-import * as consoleActions from 'actions/console';
 import * as settingsActions from 'actions/setting';
+import messages from 'content/messages';
 import BackgroundComponent from 'components/background';
 import BackgroundInputComponent from 'components/background-input';
 import reducers from 'reducers';
@@ -8,7 +8,10 @@ import { createStore } from 'store';
 const store = createStore(reducers, (e, sender) => {
   console.error('Vim-Vixen:', e);
   if (sender) {
-    store.dispatch(consoleActions.showError(e.message), sender);
+    return browser.tabs.sendMessage(sender.tab.id, {
+      type: messages.CONSOLE_SHOW_ERROR,
+      text: e.message,
+    });
   }
 });
 const backgroundComponent = new BackgroundComponent(store);
diff --git a/src/components/background.js b/src/components/background.js
index 195cfd9..487e3af 100644
--- a/src/components/background.js
+++ b/src/components/background.js
@@ -1,5 +1,4 @@
 import messages from 'content/messages';
-import * as consoleActions from 'actions/console';
 import * as inputActions from 'actions/input';
 import * as settingsActions from 'actions/setting';
 import * as tabActions from 'actions/tab';
@@ -14,7 +13,10 @@ export default class BackgroundComponent {
       try {
         return this.onMessage(message, sender);
       } catch (e) {
-        this.store.dispatch(consoleActions.showError(e.message), sender);
+        return browser.tabs.sendMessage(sender.tab.id, {
+          type: messages.CONSOLE_SHOW_ERROR,
+          text: e.message,
+        });
       }
     });
   }
@@ -44,10 +46,16 @@ export default class BackgroundComponent {
       return this.store.dispatch(
         tabActions.openToTab(message.url, sender.tab), sender);
     case messages.CONSOLE_BLURRED:
-      return this.store.dispatch(
-        consoleActions.hide(), sender);
+      return browser.tabs.sendMessage(sender.tab.id, {
+        type: messages.CONSOLE_HIDE,
+      });
     case messages.CONSOLE_ENTERED:
-      return commands.exec(message.text, this.settings);
+      return commands.exec(message.text, this.settings).catch((e) => {
+        return browser.tabs.sendMessage(sender.tab.id, {
+          type: messages.CONSOLE_SHOW_ERROR,
+          text: e.message,
+        });
+      });
     case messages.CONSOLE_QUERY_COMPLETIONS:
       return commands.complete(message.text, this.settings);
     case messages.SETTINGS_RELOAD:
diff --git a/src/pages/console.js b/src/pages/console.js
index 20a60f6..b7be73d 100644
--- a/src/pages/console.js
+++ b/src/pages/console.js
@@ -27,7 +27,7 @@ browser.runtime.onMessage.addListener((action) => {
   case messages.CONSOLE_SHOW_COMMAND:
     return store.dispatch(consoleActions.showCommand(action.command));
   case messages.CONSOLE_SHOW_ERROR:
-    return store.dispatch(consoleActions.showError(action.command));
+    return store.dispatch(consoleActions.showError(action.text));
   case messages.CONSOLE_HIDE:
     return store.dispatch(consoleActions.hide(action.command));
   }
-- 
cgit v1.2.3