aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2018-07-23 21:26:47 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2018-07-26 21:59:05 +0900
commitc4afd7237b7720acbf642fc4c6eb529420295dcd (patch)
tree1e251d23b8549937c0a3b00f020565b36a412cb3
parent0846587baf8ff04d2183985a61f14ccdea7263d3 (diff)
[wip] implement command usecases
-rw-r--r--src/background/controllers/command.js89
-rw-r--r--src/background/controllers/completions.js43
-rw-r--r--src/background/infrastructures/content-message-listener.js13
-rw-r--r--src/background/presenters/bookmark.js0
-rw-r--r--src/background/presenters/console.js16
-rw-r--r--src/background/presenters/tab.js45
-rw-r--r--src/background/presenters/window.js5
-rw-r--r--src/background/repositories/bookmark.js13
-rw-r--r--src/background/usecases/command.js114
-rw-r--r--src/background/usecases/completions.js2
10 files changed, 291 insertions, 49 deletions
diff --git a/src/background/controllers/command.js b/src/background/controllers/command.js
new file mode 100644
index 0000000..41057e0
--- /dev/null
+++ b/src/background/controllers/command.js
@@ -0,0 +1,89 @@
+import CompletionsInteractor from '../usecases/completions';
+import CommandInteractor from '../usecases/command';
+import Completions from '../domains/completions';
+
+export default class CommandController {
+ constructor() {
+ this.completionsInteractor = new CompletionsInteractor();
+ this.commandIndicator = new CommandInteractor();
+ }
+
+ getCompletions(line) {
+ let trimmed = line.trimStart();
+ let words = trimmed.split(/ +/);
+ let name = words[0];
+ if (words.length === 1) {
+ return this.completionsInteractor.queryConsoleCommand(name);
+ }
+ let keywords = trimmed.slice(name.length).trimStart();
+ switch (words[0]) {
+ case 'o':
+ case 'open':
+ case 't':
+ case 'tabopen':
+ case 'w':
+ case 'winopen':
+ return this.completionsInteractor.queryOpen(name, keywords);
+ case 'b':
+ case 'buffer':
+ return this.completionsInteractor.queryBuffer(name, keywords);
+ case 'bd':
+ case 'bdel':
+ case 'bdelete':
+ case 'bdeletes':
+ return this.completionsInteractor.queryBdelete(name, keywords);
+ case 'bd!':
+ case 'bdel!':
+ case 'bdelete!':
+ case 'bdeletes!':
+ return this.completionsInteractor.queryBdeleteForce(name, keywords);
+ case 'set':
+ return this.completionsInteractor.querySet(name, keywords);
+ }
+ return Promise.resolve(Completions.empty());
+ }
+
+ // eslint-disable-next-line complexity
+ exec(line) {
+ let trimmed = line.trimStart();
+ let words = trimmed.split(/ +/);
+ let name = words[0];
+ let keywords = trimmed.slice(name.length).trimStart();
+ switch (words[0]) {
+ case 'o':
+ case 'open':
+ return this.commandIndicator.open(keywords);
+ case 't':
+ case 'tabopen':
+ return this.commandIndicator.tabopen(keywords);
+ case 'w':
+ case 'winopen':
+ return this.commandIndicator.winopen(keywords);
+ case 'b':
+ case 'buffer':
+ return this.commandIndicator.buffer(keywords);
+ case 'bd':
+ case 'bdel':
+ case 'bdelete':
+ return this.commandIndicator.bdelete(false, keywords);
+ case 'bd!':
+ case 'bdel!':
+ case 'bdelete!':
+ return this.commandIndicator.bdelete(true, keywords);
+ case 'bdeletes':
+ return this.commandIndicator.bdeletes(false, keywords);
+ case 'bdeletes!':
+ return this.commandIndicator.bdeletes(true, keywords);
+ case 'addbookmark':
+ return this.commandIndicator.addbookmark(keywords);
+ case 'q':
+ case 'quit':
+ return this.commandIndicator.quit();
+ case 'qa':
+ case 'quitall':
+ return this.commandIndicator.quitAll();
+ case 'set':
+ return this.commandIndicator.set(keywords);
+ }
+ }
+}
diff --git a/src/background/controllers/completions.js b/src/background/controllers/completions.js
deleted file mode 100644
index f8eade9..0000000
--- a/src/background/controllers/completions.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import CompletionsInteractor from '../usecases/completions';
-import Completions from '../domains/completions';
-
-export default class ContentMessageController {
- constructor() {
- this.completionsInteractor = new CompletionsInteractor();
- }
-
- getCompletions(line) {
- let trimmed = line.trimStart();
- let words = trimmed.split(/ +/);
- let name = words[0];
- if (words.length === 1) {
- return this.completionsInteractor.queryConsoleCommand(name);
- }
- let keywords = trimmed.slice(name.length).trimStart();
- switch (words[0]) {
- case 'o':
- case 'open':
- case 't':
- case 'tabopen':
- case 'w':
- case 'winopen':
- return this.completionsInteractor.queryOpen(name, keywords);
- case 'b':
- case 'buffer':
- return this.completionsInteractor.queryBuffer(name, keywords);
- case 'bd':
- case 'bdel':
- case 'bdelete':
- case 'bdeletes':
- return this.completionsInteractor.queryBdelete(name, keywords);
- case 'bd!':
- case 'bdel!':
- case 'bdelete!':
- case 'bdeletes!':
- return this.completionsInteractor.queryBdeleteForce(name, keywords);
- case 'set':
- return this.completionsInteractor.querySet(name, keywords);
- }
- return Promise.resolve(Completions.empty());
- }
-}
diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js
index f16804f..2e84fcc 100644
--- a/src/background/infrastructures/content-message-listener.js
+++ b/src/background/infrastructures/content-message-listener.js
@@ -1,5 +1,5 @@
import messages from '../../shared/messages';
-import CompletionsController from '../controllers/completions';
+import CommandController from '../controllers/command';
import SettingController from '../controllers/setting';
import FindController from '../controllers/find';
import AddonEnabledController from '../controllers/addon-enabled';
@@ -8,7 +8,7 @@ import LinkController from '../controllers/link';
export default class ContentMessageListener {
constructor() {
this.settingController = new SettingController();
- this.completionsController = new CompletionsController();
+ this.commandController = new CommandController();
this.findController = new FindController();
this.addonEnabledController = new AddonEnabledController();
this.linkController = new LinkController();
@@ -31,6 +31,8 @@ export default class ContentMessageListener {
switch (message.type) {
case messages.CONSOLE_QUERY_COMPLETIONS:
return this.onConsoleQueryCompletions(message.text);
+ case messages.CONSOLE_ENTER_COMMAND:
+ return this.onConsoleEnterCommand(message.text);
case messages.SETTINGS_QUERY:
return this.onSettingsQuery();
case messages.SETTINGS_RELOAD:
@@ -48,10 +50,15 @@ export default class ContentMessageListener {
}
async onConsoleQueryCompletions(line) {
- let completions = await this.completionsController.getCompletions(line);
+ let completions = await this.commandController.getCompletions(line);
return Promise.resolve(completions.serialize());
}
+ onConsoleEnterCommand(text) {
+ return this.commandController.exec(text);
+ }
+
+
onSettingsQuery() {
return this.settingController.getSetting();
}
diff --git a/src/background/presenters/bookmark.js b/src/background/presenters/bookmark.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/background/presenters/bookmark.js
diff --git a/src/background/presenters/console.js b/src/background/presenters/console.js
new file mode 100644
index 0000000..f7d3777
--- /dev/null
+++ b/src/background/presenters/console.js
@@ -0,0 +1,16 @@
+import messages from '../../shared/messages';
+
+export default class ConsolePresenter {
+ showInfo(tabId, message) {
+ return browser.tabs.sendMessage(tabId, {
+ type: messages.CONSOLE_SHOW_INFO,
+ text: message,
+ });
+ }
+ showError(tabId, message) {
+ return browser.tabs.sendMessage(tabId, {
+ type: messages.CONSOLE_SHOW_ERROR,
+ text: message,
+ });
+ }
+}
diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js
index 66a207f..be6955a 100644
--- a/src/background/presenters/tab.js
+++ b/src/background/presenters/tab.js
@@ -3,8 +3,49 @@ export default class TabPresenter {
return browser.tabs.update(tabId, { url });
}
- create(url, { openerTabId, active }) {
- return browser.tabs.create({ url, openerTabId, active });
+ create(url, opts) {
+ return browser.tabs.create({ url, ...opts });
+ }
+
+ async getCurrent() {
+ let tabs = await browser.tabs.query({
+ active: true, currentWindow: true
+ });
+ return tabs[0];
+ }
+
+ getAll() {
+ return browser.tabs.query({ currentWindow: true });
+ }
+
+ async getByKeyword(keyword, excludePinned = false) {
+ let tabs = await browser.tabs.query({ currentWindow: true });
+ return tabs.filter((t) => {
+ return t.url.toLowerCase().includes(keyword.toLowerCase()) ||
+ t.title && t.title.toLowerCase().includes(keyword.toLowerCase());
+ }).filter((t) => {
+ return !(excludePinned && t.pinned);
+ });
+ }
+
+ select(tabId) {
+ return browser.tabs.update(tabId, { active: true });
+ }
+
+ async selectAt(index) {
+ let tabs = await browser.tabs.query({ currentWindow: true });
+ if (tabs.length < 2) {
+ return;
+ }
+ if (index < 0 || tabs.length <= index) {
+ throw new RangeError(`tab ${index + 1} does not exist`);
+ }
+ let id = tabs[index].id;
+ return browser.tabs.update(id, { active: true });
+ }
+
+ remove(ids) {
+ return browser.tabs.remove(ids);
}
async createAdjacent(url, { openerTabId, active }) {
diff --git a/src/background/presenters/window.js b/src/background/presenters/window.js
new file mode 100644
index 0000000..a82c4a2
--- /dev/null
+++ b/src/background/presenters/window.js
@@ -0,0 +1,5 @@
+export default class WindowPresenter {
+ create(url) {
+ return browser.windows.create({ url });
+ }
+}
diff --git a/src/background/repositories/bookmark.js b/src/background/repositories/bookmark.js
new file mode 100644
index 0000000..99f7ec4
--- /dev/null
+++ b/src/background/repositories/bookmark.js
@@ -0,0 +1,13 @@
+export default class BookmarkRepository {
+ async create(title, url) {
+ let item = await browser.bookmarks.create({
+ type: 'bookmark',
+ title,
+ url,
+ });
+ if (!item) {
+ throw new Error('Could not create a bookmark');
+ }
+ return item;
+ }
+}
diff --git a/src/background/usecases/command.js b/src/background/usecases/command.js
new file mode 100644
index 0000000..1d4744e
--- /dev/null
+++ b/src/background/usecases/command.js
@@ -0,0 +1,114 @@
+import TabPresenter from '../presenters/tab';
+import WindowPresenter from '../presenters/window';
+import SettingRepository from '../repositories/setting';
+import BookmarkRepository from '../repositories/bookmark';
+import ConsolePresenter from '../presenters/console';
+
+export default class CommandIndicator {
+ constructor() {
+ this.tabPresenter = new TabPresenter();
+ this.windowPresenter = new WindowPresenter();
+ this.settingRepository = new SettingRepository();
+ this.bookmarkRepository = new BookmarkRepository();
+ this.consolePresenter = new ConsolePresenter();
+ }
+
+ async open(keywords) {
+ let url = await this.urlOrSearch(keywords);
+ return this.tabPresenter.open(url);
+ }
+
+ async tabopen(keywords) {
+ let url = await this.urlOrSearch(keywords);
+ return this.tabPresenter.create(url);
+ }
+
+ async winopen(keywords) {
+ let url = await this.urlOrSearch(keywords);
+ return this.windowPresenter.create(url);
+ }
+
+ async buffer(keywords) {
+ if (keywords.length === 0) {
+ return;
+ }
+ if (!isNaN(keywords)) {
+ let index = parseInt(keywords, 10) - 1;
+ return tabs.selectAt(index);
+ }
+
+ let current = await this.tabPresenter.getCurrent();
+ let tabs = await this.tabPresenter.getByKeyword(keywords);
+ if (tabs.length === 0) {
+ throw new RangeError('No matching buffer for ' + keywords);
+ }
+ for (let tab of tabs) {
+ if (tab.index > current.index) {
+ return this.tabPresenter.select(tab.id);
+ }
+ }
+ return this.tabPresenter.select(tabs[0].id);
+ }
+
+ async bdelete(force, keywords) {
+ let excludePinned = !force;
+ let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
+ if (tabs.length === 0) {
+ throw new Error('No matching buffer for ' + keywords);
+ } else if (tabs.length > 1) {
+ throw new Error('More than one match for ' + keywords);
+ }
+ return this.tabPresenter.remove([tabs[0].id]);
+ }
+
+ async bdeletes(force, keywords) {
+ let excludePinned = !force;
+ let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned);
+ let ids = tabs.map(tab => tab.id);
+ return this.tabPresenter.remove(ids);
+ }
+
+ async quit() {
+ let tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.remove([tab.id]);
+ }
+
+ async quitall() {
+ let tabs = await this.tabPresenter.getAll();
+ let ids = tabs.map(tab => tab.id);
+ this.tabPresenter.tabPresenter.remove(ids);
+ }
+
+ async addbookmark(title) {
+ let tab = await this.tabPresenter.getCurrent();
+ let item = await this.bookmarkRepository.create(title, tab.url);
+ let message = 'Saved current page: ' + item.url;
+ return this.consolePresenter.showInfo(tab.id, message);
+ }
+
+ set(keywords) {
+ // TODO implement set command
+ }
+
+ async urlOrSearch(keywords) {
+ try {
+ return new URL(keywords).href;
+ } catch (e) {
+ if (keywords.includes('.') && !keywords.includes(' ')) {
+ return 'http://' + keywords;
+ }
+ let settings = await this.settingRepository.get();
+ let engines = settings.search.engines;
+
+ let template = engines[settings.search.default];
+ let query = keywords;
+
+ let first = keywords.trimStart().split(' ')[0];
+ if (Object.keys(engines).includes(first)) {
+ template = engines[first];
+ query = keywords.trimStart().slice(first.length).trimStart();
+ }
+ return template.replace('{}', encodeURIComponent(query));
+ }
+ }
+}
diff --git a/src/background/usecases/completions.js b/src/background/usecases/completions.js
index 18174bd..ee519e1 100644
--- a/src/background/usecases/completions.js
+++ b/src/background/usecases/completions.js
@@ -50,7 +50,7 @@ export default class CompletionsInteractor {
}
queryBuffer(name, keywords) {
- return this.queryTabs(name, true, keywords);
+ return this.queryTabs(name, false, keywords);
}
queryBdelete(name, keywords) {