From 65cf6f0842d8d5933dc13b3767b1baf398d68cd5 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 14 Jun 2021 23:14:51 +0900
Subject: Implement FindNextOperator

---
 src/background/operators/impls/FindNextOperator.ts | 94 ++++++++++++++++++++++
 .../operators/impls/FindOperatorFactoryChain.ts    | 49 +++++++++++
 src/background/operators/impls/FindPrevOperator.ts | 20 +++++
 .../operators/impls/OperatorFactoryImpl.ts         |  3 +
 4 files changed, 166 insertions(+)
 create mode 100644 src/background/operators/impls/FindNextOperator.ts
 create mode 100644 src/background/operators/impls/FindOperatorFactoryChain.ts
 create mode 100644 src/background/operators/impls/FindPrevOperator.ts

(limited to 'src/background/operators/impls')

diff --git a/src/background/operators/impls/FindNextOperator.ts b/src/background/operators/impls/FindNextOperator.ts
new file mode 100644
index 0000000..241f71d
--- /dev/null
+++ b/src/background/operators/impls/FindNextOperator.ts
@@ -0,0 +1,94 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import FramePresenter from "../../presenters/FramePresenter";
+
+export default class FindNextOperator implements Operator {
+  constructor(
+    private readonly tabPresenter: TabPresenter,
+    private readonly findRepository: FindRepository,
+    private readonly findClient: FindClient,
+    private readonly consoleClient: ConsoleClient,
+    private readonly framePresenter: FramePresenter
+  ) {}
+
+  async run(): Promise<void> {
+    const tab = await this.tabPresenter.getCurrent();
+    const tabId = tab?.id;
+    if (tabId == null) {
+      return;
+    }
+
+    const state = await this.findRepository.getLocalState(tabId);
+    if (state) {
+      // Start to find the keyword from the current frame which last found on,
+      // and concat it to end of frame ids to perform a wrap-search
+      //
+      //                ,- keyword should be in this frame
+      //                |
+      // [100, 101, 0, 100]
+      //   |
+      //   `- continue from frame id 100
+      //
+      const targetFrameIds = state.frameIds
+        .slice(state.framePos)
+        .concat(
+          state.frameIds.slice(0, state.framePos),
+          state.frameIds[state.framePos]
+        );
+
+      for (let i = 0; i < targetFrameIds.length; ++i) {
+        const found = await this.findClient.findNext(
+          tabId,
+          targetFrameIds[i],
+          state.keyword
+        );
+        if (found) {
+          this.findRepository.setLocalState(tabId, {
+            ...state,
+            framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first
+          });
+          return;
+        }
+        this.findClient.clearSelection(tabId, targetFrameIds[i]);
+      }
+
+      // The keyword is gone.
+      this.consoleClient.showError(
+        tabId,
+        "Pattern not found: " + state.keyword
+      );
+      return;
+    }
+
+    const keyword = await this.findRepository.getGlobalKeyword();
+    if (keyword) {
+      const frameIds = await this.framePresenter.getAllFrameIds(tabId);
+      for (const frameId of frameIds) {
+        await this.findClient.clearSelection(tabId, frameId);
+      }
+
+      for (let framePos = 0; framePos < frameIds.length; ++framePos) {
+        const found = await this.findClient.findNext(
+          tabId,
+          frameIds[framePos],
+          keyword
+        );
+        if (found) {
+          await this.findRepository.setLocalState(tabId, {
+            frameIds,
+            framePos,
+            keyword,
+          });
+          await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword);
+          return;
+        }
+      }
+      this.consoleClient.showError(tabId, "Pattern not found: " + keyword);
+      return;
+    }
+    await this.consoleClient.showError(tabId, "No previous search keywords");
+  }
+}
diff --git a/src/background/operators/impls/FindOperatorFactoryChain.ts b/src/background/operators/impls/FindOperatorFactoryChain.ts
new file mode 100644
index 0000000..b71f032
--- /dev/null
+++ b/src/background/operators/impls/FindOperatorFactoryChain.ts
@@ -0,0 +1,49 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import TabPresenter from "../../presenters/TabPresenter";
+import * as operations from "../../../shared/operations";
+import FindNextOperator from "./FindNextOperator";
+import FindPrevOperator from "./FindPrevOperator";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import FramePresenter from "../../presenters/FramePresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+@injectable()
+export default class FindOperatorFactoryChain implements OperatorFactoryChain {
+  constructor(
+    @inject("TabPresenter")
+    private readonly tabPresenter: TabPresenter,
+    @inject("FindRepository")
+    private readonly findRepository: FindRepository,
+    @inject("FindClient")
+    private readonly findClient: FindClient,
+    @inject("ConsoleClient")
+    private readonly consoleClient: ConsoleClient,
+    @inject("FramePresenter")
+    private readonly framePresenter: FramePresenter
+  ) {}
+
+  create(op: operations.Operation): Operator | null {
+    switch (op.type) {
+      case operations.FIND_NEXT:
+        return new FindNextOperator(
+          this.tabPresenter,
+          this.findRepository,
+          this.findClient,
+          this.consoleClient,
+          this.framePresenter
+        );
+      case operations.FIND_PREV:
+        return new FindPrevOperator(
+          this.tabPresenter,
+          this.findRepository,
+          this.findClient,
+          this.consoleClient,
+          this.framePresenter
+        );
+    }
+    return null;
+  }
+}
diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts
new file mode 100644
index 0000000..28238f6
--- /dev/null
+++ b/src/background/operators/impls/FindPrevOperator.ts
@@ -0,0 +1,20 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import FramePresenter from "../../presenters/FramePresenter";
+
+export default class FindPrevOperator implements Operator {
+  constructor(
+    _tabPresenter: TabPresenter,
+    _findRepository: FindRepository,
+    _findClient: FindClient,
+    _consoleClient: ConsoleClient,
+    _framePresenter: FramePresenter
+  ) {}
+
+  async run(): Promise<void> {
+    throw new Error("not implemented");
+  }
+}
diff --git a/src/background/operators/impls/OperatorFactoryImpl.ts b/src/background/operators/impls/OperatorFactoryImpl.ts
index 34e7bb5..ce87491 100644
--- a/src/background/operators/impls/OperatorFactoryImpl.ts
+++ b/src/background/operators/impls/OperatorFactoryImpl.ts
@@ -8,6 +8,7 @@ import NavigateOperatorFactoryChain from "./NavigateOperatorFactoryChain";
 import RepeatOperatorFactoryChain from "./RepeatOperatorFactoryChain";
 import TabOperatorFactoryChain from "./TabOperatorFactoryChain";
 import ZoomOperatorFactoryChain from "./ZoomOperatorFactoryChain";
+import FindOperatorFactoryChain from "./FindOperatorFactoryChain";
 import * as operations from "../../../shared/operations";
 
 @injectable()
@@ -20,6 +21,7 @@ export class OperatorFactoryImpl implements OperatorFactory {
     navigateOperatorFactoryChain: NavigateOperatorFactoryChain,
     tabOperatorFactoryChain: TabOperatorFactoryChain,
     zoomOperatorFactoryChain: ZoomOperatorFactoryChain,
+    findOperatorFactoryChain: FindOperatorFactoryChain,
     @inject(delay(() => RepeatOperatorFactoryChain))
     repeatOperatorFactoryChain: RepeatOperatorFactoryChain
   ) {
@@ -30,6 +32,7 @@ export class OperatorFactoryImpl implements OperatorFactory {
       repeatOperatorFactoryChain,
       tabOperatorFactoryChain,
       zoomOperatorFactoryChain,
+      findOperatorFactoryChain,
     ];
   }
 
-- 
cgit v1.2.3


From 05a19373e90976b82c2bd81626aee27a79eb4b2d Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 5 Jul 2021 21:31:58 +0900
Subject: Implement backwards find

---
 src/background/clients/FindClient.ts               | 15 ++++
 src/background/operators/impls/FindPrevOperator.ts | 86 ++++++++++++++++++++--
 2 files changed, 95 insertions(+), 6 deletions(-)

(limited to 'src/background/operators/impls')

diff --git a/src/background/clients/FindClient.ts b/src/background/clients/FindClient.ts
index 863c5ad..b46b964 100644
--- a/src/background/clients/FindClient.ts
+++ b/src/background/clients/FindClient.ts
@@ -3,6 +3,8 @@ import * as messages from "../../shared/messages";
 export default interface FindClient {
   findNext(tabId: number, frameId: number, keyword: string): Promise<boolean>;
 
+  findPrev(tabId: number, frameId: number, keyword: string): Promise<boolean>;
+
   clearSelection(tabId: number, frameId: number): Promise<void>;
 }
 
@@ -20,6 +22,19 @@ export class FindClientImpl implements FindClient {
     return found;
   }
 
+  async findPrev(
+    tabId: number,
+    frameId: number,
+    keyword: string
+  ): Promise<boolean> {
+    const found = (await browser.tabs.sendMessage(
+      tabId,
+      { type: messages.FIND_PREV, keyword },
+      { frameId }
+    )) as boolean;
+    return found;
+  }
+
   clearSelection(tabId: number, frameId: number): Promise<void> {
     return browser.tabs.sendMessage(
       tabId,
diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts
index 28238f6..822c386 100644
--- a/src/background/operators/impls/FindPrevOperator.ts
+++ b/src/background/operators/impls/FindPrevOperator.ts
@@ -7,14 +7,88 @@ import FramePresenter from "../../presenters/FramePresenter";
 
 export default class FindPrevOperator implements Operator {
   constructor(
-    _tabPresenter: TabPresenter,
-    _findRepository: FindRepository,
-    _findClient: FindClient,
-    _consoleClient: ConsoleClient,
-    _framePresenter: FramePresenter
+    private readonly tabPresenter: TabPresenter,
+    private readonly findRepository: FindRepository,
+    private readonly findClient: FindClient,
+    private readonly consoleClient: ConsoleClient,
+    private readonly framePresenter: FramePresenter
   ) {}
 
   async run(): Promise<void> {
-    throw new Error("not implemented");
+    const tab = await this.tabPresenter.getCurrent();
+    const tabId = tab?.id;
+    if (tabId == null) {
+      return;
+    }
+
+    const state = await this.findRepository.getLocalState(tabId);
+    if (state) {
+      // Start to find the keyword from the current frame which last found on,
+      // and concat it to end of frame ids to perform a wrap-search
+      //
+      //                ,- keyword should be in this frame
+      //                |
+      // [100, 101, 0, 100]
+      //   |
+      //   `- continue from frame id 100
+      //
+      const targetFrameIds = state.frameIds
+        .slice(state.framePos)
+        .concat(
+          state.frameIds.slice(0, state.framePos),
+          state.frameIds[state.framePos]
+        );
+
+      for (let i = targetFrameIds.length - 1; i >= 0; --i) {
+        const found = await this.findClient.findPrev(
+          tabId,
+          targetFrameIds[i],
+          state.keyword
+        );
+        if (found) {
+          this.findRepository.setLocalState(tabId, {
+            ...state,
+            framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first
+          });
+          return;
+        }
+        this.findClient.clearSelection(tabId, targetFrameIds[i]);
+      }
+
+      // The keyword is gone.
+      this.consoleClient.showError(
+        tabId,
+        "Pattern not found: " + state.keyword
+      );
+      return;
+    }
+
+    const keyword = await this.findRepository.getGlobalKeyword();
+    if (keyword) {
+      const frameIds = await this.framePresenter.getAllFrameIds(tabId);
+      for (const frameId of frameIds) {
+        await this.findClient.clearSelection(tabId, frameId);
+      }
+
+      for (let framePos = frameIds.length - 1; framePos >= 0; --framePos) {
+        const found = await this.findClient.findPrev(
+          tabId,
+          frameIds[framePos],
+          keyword
+        );
+        if (found) {
+          await this.findRepository.setLocalState(tabId, {
+            frameIds,
+            framePos,
+            keyword,
+          });
+          await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword);
+          return;
+        }
+      }
+      this.consoleClient.showError(tabId, "Pattern not found: " + keyword);
+      return;
+    }
+    await this.consoleClient.showError(tabId, "No previous search keywords");
   }
 }
-- 
cgit v1.2.3