aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/controllers/OperationController.ts175
-rw-r--r--src/background/infrastructures/ContentMessageListener.ts9
-rw-r--r--src/content/client/OperationClient.ts6
-rw-r--r--src/content/controllers/KeymapController.ts83
-rw-r--r--src/content/usecases/KeymapUseCase.ts12
-rw-r--r--src/shared/messages.ts1
-rw-r--r--src/shared/operations.ts26
7 files changed, 175 insertions, 137 deletions
diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts
index 7a10ad6..69b45c3 100644
--- a/src/background/controllers/OperationController.ts
+++ b/src/background/controllers/OperationController.ts
@@ -21,95 +21,108 @@ export default class OperationController {
) {
}
- async exec(op: operations.Operation): Promise<any> {
- await this.doOperation(op);
+ async exec(count: number, op: operations.Operation): Promise<any> {
+ await this.doOperation(count, op);
if (this.repeatUseCase.isRepeatable(op)) {
this.repeatUseCase.storeLastOperation(op);
}
}
// eslint-disable-next-line complexity, max-lines-per-function
- doOperation(operation: operations.Operation): Promise<any> {
- switch (operation.type) {
- case operations.TAB_CLOSE:
- return this.tabUseCase.close(false, operation.select === 'left');
- case operations.TAB_CLOSE_RIGHT:
- return this.tabUseCase.closeRight();
- case operations.TAB_CLOSE_FORCE:
- return this.tabUseCase.close(true);
- case operations.TAB_REOPEN:
- return this.tabUseCase.reopen();
- case operations.TAB_PREV:
- return this.tabSelectUseCase.selectPrev(1);
- case operations.TAB_NEXT:
- return this.tabSelectUseCase.selectNext(1);
- case operations.TAB_FIRST:
- return this.tabSelectUseCase.selectFirst();
- case operations.TAB_LAST:
- return this.tabSelectUseCase.selectLast();
- case operations.TAB_PREV_SEL:
- return this.tabSelectUseCase.selectPrevSelected();
- case operations.TAB_RELOAD:
- return this.tabUseCase.reload(operation.cache);
- case operations.TAB_PIN:
- return this.tabUseCase.setPinned(true);
- case operations.TAB_UNPIN:
- return this.tabUseCase.setPinned(false);
- case operations.TAB_TOGGLE_PINNED:
- return this.tabUseCase.togglePinned();
- case operations.TAB_DUPLICATE:
- return this.tabUseCase.duplicate();
- case operations.PAGE_SOURCE:
- return this.tabUseCase.openPageSource();
- case operations.PAGE_HOME:
- return this.tabUseCase.openHome(operation.newTab);
- case operations.ZOOM_IN:
- return this.zoomUseCase.zoomIn();
- case operations.ZOOM_OUT:
- return this.zoomUseCase.zoomOut();
- case operations.ZOOM_NEUTRAL:
- return this.zoomUseCase.zoomNutoral();
- case operations.COMMAND_SHOW:
- return this.consoleUseCase.showCommand();
- case operations.COMMAND_SHOW_OPEN:
- return this.consoleUseCase.showOpenCommand(operation.alter);
- case operations.COMMAND_SHOW_TABOPEN:
- return this.consoleUseCase.showTabopenCommand(operation.alter);
- case operations.COMMAND_SHOW_WINOPEN:
- return this.consoleUseCase.showWinopenCommand(operation.alter);
- case operations.COMMAND_SHOW_BUFFER:
- return this.consoleUseCase.showBufferCommand();
- case operations.COMMAND_SHOW_ADDBOOKMARK:
- return this.consoleUseCase.showAddbookmarkCommand(operation.alter);
- case operations.FIND_START:
- return this.findUseCase.findStart();
- case operations.CANCEL:
- return this.consoleUseCase.hideConsole();
- case operations.NAVIGATE_HISTORY_PREV:
- return this.navigateUseCase.openHistoryPrev();
- case operations.NAVIGATE_HISTORY_NEXT:
- return this.navigateUseCase.openHistoryNext();
- case operations.NAVIGATE_LINK_PREV:
- return this.navigateUseCase.openLinkPrev();
- case operations.NAVIGATE_LINK_NEXT:
- return this.navigateUseCase.openLinkNext();
- case operations.NAVIGATE_PARENT:
- return this.navigateUseCase.openParent();
- case operations.NAVIGATE_ROOT:
- return this.navigateUseCase.openRoot();
- case operations.REPEAT_LAST:
- {
- let last = this.repeatUseCase.getLastOperation();
- if (typeof last !== 'undefined') {
- return this.doOperation(last);
+ async doOperation(
+ count: number,
+ operation: operations.Operation,
+ ): Promise<any> {
+ // eslint-disable-next-line complexity, max-lines-per-function
+ const opFunc = (() => {
+ switch (operation.type) {
+ case operations.TAB_CLOSE:
+ return () => this.tabUseCase.close(false, operation.select === 'left');
+ case operations.TAB_CLOSE_RIGHT:
+ return () => this.tabUseCase.closeRight();
+ case operations.TAB_CLOSE_FORCE:
+ return () => this.tabUseCase.close(true);
+ case operations.TAB_REOPEN:
+ return () => this.tabUseCase.reopen();
+ case operations.TAB_PREV:
+ return () => this.tabSelectUseCase.selectPrev(1);
+ case operations.TAB_NEXT:
+ return () => this.tabSelectUseCase.selectNext(1);
+ case operations.TAB_FIRST:
+ return () => this.tabSelectUseCase.selectFirst();
+ case operations.TAB_LAST:
+ return () => this.tabSelectUseCase.selectLast();
+ case operations.TAB_PREV_SEL:
+ return () => this.tabSelectUseCase.selectPrevSelected();
+ case operations.TAB_RELOAD:
+ return () => this.tabUseCase.reload(operation.cache);
+ case operations.TAB_PIN:
+ return () => this.tabUseCase.setPinned(true);
+ case operations.TAB_UNPIN:
+ return () => this.tabUseCase.setPinned(false);
+ case operations.TAB_TOGGLE_PINNED:
+ return () => this.tabUseCase.togglePinned();
+ case operations.TAB_DUPLICATE:
+ return () => this.tabUseCase.duplicate();
+ case operations.PAGE_SOURCE:
+ return () => this.tabUseCase.openPageSource();
+ case operations.PAGE_HOME:
+ return () => this.tabUseCase.openHome(operation.newTab);
+ case operations.ZOOM_IN:
+ return () => this.zoomUseCase.zoomIn();
+ case operations.ZOOM_OUT:
+ return () => this.zoomUseCase.zoomOut();
+ case operations.ZOOM_NEUTRAL:
+ return () => this.zoomUseCase.zoomNutoral();
+ case operations.COMMAND_SHOW:
+ return () => this.consoleUseCase.showCommand();
+ case operations.COMMAND_SHOW_OPEN:
+ return () => this.consoleUseCase.showOpenCommand(operation.alter);
+ case operations.COMMAND_SHOW_TABOPEN:
+ return () => this.consoleUseCase.showTabopenCommand(operation.alter);
+ case operations.COMMAND_SHOW_WINOPEN:
+ return () => this.consoleUseCase.showWinopenCommand(operation.alter);
+ case operations.COMMAND_SHOW_BUFFER:
+ return () => this.consoleUseCase.showBufferCommand();
+ case operations.COMMAND_SHOW_ADDBOOKMARK:
+ return () => this.consoleUseCase.showAddbookmarkCommand(
+ operation.alter);
+ case operations.FIND_START:
+ return () => this.findUseCase.findStart();
+ case operations.CANCEL:
+ return () => this.consoleUseCase.hideConsole();
+ case operations.NAVIGATE_HISTORY_PREV:
+ return () => this.navigateUseCase.openHistoryPrev();
+ case operations.NAVIGATE_HISTORY_NEXT:
+ return () => this.navigateUseCase.openHistoryNext();
+ case operations.NAVIGATE_LINK_PREV:
+ return () => this.navigateUseCase.openLinkPrev();
+ case operations.NAVIGATE_LINK_NEXT:
+ return () => this.navigateUseCase.openLinkNext();
+ case operations.NAVIGATE_PARENT:
+ return () => this.navigateUseCase.openParent();
+ case operations.NAVIGATE_ROOT:
+ return () => this.navigateUseCase.openRoot();
+ case operations.REPEAT_LAST:
+ return () => {
+ let last = this.repeatUseCase.getLastOperation();
+ if (typeof last !== 'undefined') {
+ return this.doOperation(1, last);
+ }
+ return Promise.resolve();
+ };
+ case operations.INTERNAL_OPEN_URL:
+ return () => this.tabUseCase.openURL(
+ operation.url, operation.newTab, operation.newWindow);
+ default:
+ throw new Error('unknown operation: ' + operation.type);
}
- return Promise.resolve();
- }
- case operations.INTERNAL_OPEN_URL:
- return this.tabUseCase.openURL(
- operation.url, operation.newTab, operation.newWindow);
+ })();
+
+ for (let i = 0; i < count; ++i) {
+ // eslint-disable-next-line no-await-in-loop
+ await opFunc();
}
- throw new Error('unknown operation: ' + operation.type);
}
}
diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts
index f80d686..51a9f82 100644
--- a/src/background/infrastructures/ContentMessageListener.ts
+++ b/src/background/infrastructures/ContentMessageListener.ts
@@ -1,5 +1,6 @@
import { injectable } from 'tsyringe';
import * as messages from '../../shared/messages';
+import * as operations from '../../shared/operations';
import CompletionGroup from '../domains/CompletionGroup';
import CommandController from '../controllers/CommandController';
import SettingController from '../controllers/SettingController';
@@ -19,7 +20,7 @@ export default class ContentMessageListener {
private findController: FindController,
private addonEnabledController: AddonEnabledController,
private linkController: LinkController,
- private backgroundOperationController: OperationController,
+ private operationController: OperationController,
private markController: MarkController,
) {
this.consolePorts = {};
@@ -79,7 +80,7 @@ export default class ContentMessageListener {
senderTab.id as number,
message.background);
case messages.BACKGROUND_OPERATION:
- return this.onBackgroundOperation(message.operation);
+ return this.onBackgroundOperation(message.count, message.operation);
case messages.MARK_SET_GLOBAL:
return this.onMarkSetGlobal(message.key, message.x, message.y);
case messages.MARK_JUMP_GLOBAL:
@@ -126,8 +127,8 @@ export default class ContentMessageListener {
return this.linkController.openToTab(url, openerId);
}
- onBackgroundOperation(operation: any): Promise<any> {
- return this.backgroundOperationController.exec(operation);
+ onBackgroundOperation(count: number, op: operations.Operation): Promise<any> {
+ return this.operationController.exec(count, op);
}
onMarkSetGlobal(key: string, x: number, y: number): Promise<any> {
diff --git a/src/content/client/OperationClient.ts b/src/content/client/OperationClient.ts
index 5dbe555..06077dc 100644
--- a/src/content/client/OperationClient.ts
+++ b/src/content/client/OperationClient.ts
@@ -2,7 +2,7 @@ import * as operations from '../../shared/operations';
import * as messages from '../../shared/messages';
export default interface OperationClient {
- execBackgroundOp(op: operations.Operation): Promise<void>;
+ execBackgroundOp(count: number, op: operations.Operation): Promise<void>;
internalOpenUrl(
url: string, newTab?: boolean, background?: boolean,
@@ -10,9 +10,10 @@ export default interface OperationClient {
}
export class OperationClientImpl implements OperationClient {
- execBackgroundOp(op: operations.Operation): Promise<void> {
+ execBackgroundOp(count: number, op: operations.Operation): Promise<void> {
return browser.runtime.sendMessage({
type: messages.BACKGROUND_OPERATION,
+ count,
operation: op,
});
}
@@ -22,6 +23,7 @@ export class OperationClientImpl implements OperationClient {
): Promise<void> {
return browser.runtime.sendMessage({
type: messages.BACKGROUND_OPERATION,
+ count: 1,
operation: {
type: operations.INTERNAL_OPEN_URL,
url,
diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts
index cf59ae5..b8069f9 100644
--- a/src/content/controllers/KeymapController.ts
+++ b/src/content/controllers/KeymapController.ts
@@ -23,7 +23,7 @@ export default class KeymapController {
private markKeyUseCase: MarkKeyyUseCase,
@inject('OperationClient')
- private backgroundClient: OperationClient,
+ private operationClient: OperationClient,
@inject('FollowMasterClient')
private followMasterClient: FollowMasterClient,
@@ -32,74 +32,69 @@ export default class KeymapController {
// eslint-disable-next-line complexity, max-lines-per-function
press(key: Key): boolean {
- let ops = this.keymapUseCase.nextOps(key);
- if (ops.length === 0) {
+ let nextOp = this.keymapUseCase.nextOps(key);
+ if (nextOp === null) {
return false;
}
- // Do not await asynchronous methods to return a boolean immidiately. The
- // caller requires the synchronous response from the callback to identify
- // to continue of abandon the event propagations.
- for (let op of ops) {
+ if (!operations.isNRepeatable(nextOp.op.type)) {
+ nextOp.count = 1;
+ }
+
+ const doFunc = ((op: operations.Operation) => {
switch (op.type) {
case operations.ADDON_ENABLE:
- this.addonEnabledUseCase.enable();
- break;
+ return () => this.addonEnabledUseCase.enable();
case operations.ADDON_DISABLE:
- this.addonEnabledUseCase.disable();
- break;
+ return () => this.addonEnabledUseCase.disable();
case operations.ADDON_TOGGLE_ENABLED:
- this.addonEnabledUseCase.toggle();
- break;
+ return () => this.addonEnabledUseCase.toggle();
case operations.FIND_NEXT:
- this.findSlaveUseCase.findNext();
- break;
+ return () => this.findSlaveUseCase.findNext();
case operations.FIND_PREV:
- this.findSlaveUseCase.findPrev();
- break;
+ return () => this.findSlaveUseCase.findPrev();
case operations.SCROLL_VERTICALLY:
- this.scrollUseCase.scrollVertically(op.count);
- break;
+ return () => this.scrollUseCase.scrollVertically(op.count);
case operations.SCROLL_HORIZONALLY:
- this.scrollUseCase.scrollHorizonally(op.count);
- break;
+ return () => this.scrollUseCase.scrollHorizonally(op.count);
case operations.SCROLL_PAGES:
- this.scrollUseCase.scrollPages(op.count);
- break;
+ return () => this.scrollUseCase.scrollPages(op.count);
case operations.SCROLL_TOP:
- this.scrollUseCase.scrollToTop();
- break;
+ return () => this.scrollUseCase.scrollToTop();
case operations.SCROLL_BOTTOM:
- this.scrollUseCase.scrollToBottom();
- break;
+ return () => this.scrollUseCase.scrollToBottom();
case operations.SCROLL_HOME:
- this.scrollUseCase.scrollToHome();
- break;
+ return () => this.scrollUseCase.scrollToHome();
case operations.SCROLL_END:
- this.scrollUseCase.scrollToEnd();
- break;
+ return () => this.scrollUseCase.scrollToEnd();
case operations.FOLLOW_START:
- this.followMasterClient.startFollow(op.newTab, op.background);
- break;
+ return () => this.followMasterClient.startFollow(
+ op.newTab, op.background);
case operations.MARK_SET_PREFIX:
- this.markKeyUseCase.enableSetMode();
- break;
+ return () => this.markKeyUseCase.enableSetMode();
case operations.MARK_JUMP_PREFIX:
- this.markKeyUseCase.enableJumpMode();
- break;
+ return () => this.markKeyUseCase.enableJumpMode();
case operations.FOCUS_INPUT:
- this.focusUseCase.focusFirstInput();
- break;
+ return () => this.focusUseCase.focusFirstInput();
case operations.URLS_YANK:
- this.clipbaordUseCase.yankCurrentURL();
- break;
+ return () => this.clipbaordUseCase.yankCurrentURL();
case operations.URLS_PASTE:
- this.clipbaordUseCase.openOrSearch(
+ return () => this.clipbaordUseCase.openOrSearch(
op.newTab ? op.newTab : false,
);
- break;
default:
- this.backgroundClient.execBackgroundOp(op);
+ return null;
+ }
+ })(nextOp.op);
+
+ if (doFunc === null) {
+ // Do not await asynchronous methods to return a boolean immidiately. The
+ // caller requires the synchronous response from the callback to identify
+ // to continue of abandon the event propagations.
+ this.operationClient.execBackgroundOp(nextOp.count, nextOp.op);
+ } else {
+ for (let i = 0; i < nextOp.count; ++i) {
+ doFunc();
}
}
return true;
diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts
index f7de334..fab13f5 100644
--- a/src/content/usecases/KeymapUseCase.ts
+++ b/src/content/usecases/KeymapUseCase.ts
@@ -36,13 +36,13 @@ export default class KeymapUseCase {
}
// eslint-disable-next-line max-statements
- nextOps(key: Key): operations.Operation[] {
+ nextOps(key: Key): { count: number, op: operations.Operation } | null {
let sequence = this.repository.enqueueKey(key);
let baseSequence = sequence.trimNumericPrefix();
if (baseSequence.length() === 1 && this.blacklistKey(key)) {
// ignore if the input starts with black list keys
this.repository.clear();
- return [];
+ return null;
}
let keymaps = this.keymapEntityMap();
@@ -53,21 +53,21 @@ export default class KeymapUseCase {
sequence.length() === matched[0][0].length()) {
// keys are matched with an operation
this.repository.clear();
- return [matched[0][1]];
+ return { count: 1, op: matched[0][1] };
} else if (
baseMatched.length === 1 &&
baseSequence.length() === baseMatched[0][0].length()) {
// keys are matched with an operation with a numeric prefix
this.repository.clear();
- return Array(sequence.repeatCount()).fill(baseMatched[0][1]);
+ return { count: sequence.repeatCount(), op: baseMatched[0][1] };
} else if (matched.length >= 1 || baseMatched.length >= 1) {
// keys are matched with an operation's prefix
- return [];
+ return null;
}
// matched with no operations
this.repository.clear();
- return [];
+ return null;
}
private keymapEntityMap(): [KeySequence, operations.Operation][] {
diff --git a/src/shared/messages.ts b/src/shared/messages.ts
index 36a23d8..8032109 100644
--- a/src/shared/messages.ts
+++ b/src/shared/messages.ts
@@ -49,6 +49,7 @@ export const NAVIGATE_LINK_PREV = 'navigate.link.prev';
export interface BackgroundOperationMessage {
type: typeof BACKGROUND_OPERATION;
+ count: number;
operation: operations.Operation;
}
diff --git a/src/shared/operations.ts b/src/shared/operations.ts
index 1ce5256..67c5ca8 100644
--- a/src/shared/operations.ts
+++ b/src/shared/operations.ts
@@ -508,3 +508,29 @@ export const valueOf = (o: any): Operation => {
}
throw new TypeError('Unknown operation type: ' + o.type);
};
+
+export const isNRepeatable = (type: string): boolean => {
+ switch (type) {
+ case SCROLL_VERTICALLY:
+ case SCROLL_HORIZONALLY:
+ case SCROLL_PAGES:
+ case NAVIGATE_HISTORY_PREV:
+ case NAVIGATE_HISTORY_NEXT:
+ case NAVIGATE_PARENT:
+ case TAB_CLOSE:
+ case TAB_CLOSE_FORCE:
+ case TAB_CLOSE_RIGHT:
+ case TAB_REOPEN:
+ case TAB_PREV:
+ case TAB_NEXT:
+ case TAB_DUPLICATE:
+ case ZOOM_IN:
+ case ZOOM_OUT:
+ case URLS_PASTE:
+ case FIND_NEXT:
+ case FIND_PREV:
+ case REPEAT_LAST:
+ return true;
+ }
+ return false;
+};