summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/workers/search
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/workers/search')
-rw-r--r--devtools/client/debugger/src/workers/search/get-matches.js49
-rw-r--r--devtools/client/debugger/src/workers/search/index.js40
-rw-r--r--devtools/client/debugger/src/workers/search/moz.build10
-rw-r--r--devtools/client/debugger/src/workers/search/project-search.js83
-rw-r--r--devtools/client/debugger/src/workers/search/tests/__snapshots__/project-search.spec.js.snap22
-rw-r--r--devtools/client/debugger/src/workers/search/tests/get-matches.spec.js101
-rw-r--r--devtools/client/debugger/src/workers/search/tests/project-search.spec.js40
-rw-r--r--devtools/client/debugger/src/workers/search/worker.js12
8 files changed, 357 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/workers/search/get-matches.js b/devtools/client/debugger/src/workers/search/get-matches.js
new file mode 100644
index 0000000000..947e75a7c1
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/get-matches.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import assert from "../../utils/assert";
+import buildQuery from "../../utils/build-query";
+
+import type { SearchModifiers } from "../../types";
+
+export default function getMatches(
+ query: string,
+ text: string,
+ modifiers: SearchModifiers
+): Object[] {
+ if (!query || !text || !modifiers) {
+ return [];
+ }
+ const regexQuery = buildQuery(query, modifiers, {
+ isGlobal: true,
+ });
+ const matchedLocations = [];
+ const lines = text.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ let singleMatch;
+ const line = lines[i];
+ while ((singleMatch = regexQuery.exec(line)) !== null) {
+ // Flow doesn't understand the test above.
+ if (!singleMatch) {
+ throw new Error("no singleMatch");
+ }
+
+ matchedLocations.push({ line: i, ch: singleMatch.index });
+
+ // When the match is an empty string the regexQuery.lastIndex will not
+ // change resulting in an infinite loop so we need to check for this and
+ // increment it manually in that case. See issue #7023
+ if (singleMatch[0] === "") {
+ assert(
+ !regexQuery.unicode,
+ "lastIndex++ can cause issues in unicode mode"
+ );
+ regexQuery.lastIndex++;
+ }
+ }
+ }
+ return matchedLocations;
+}
diff --git a/devtools/client/debugger/src/workers/search/index.js b/devtools/client/debugger/src/workers/search/index.js
new file mode 100644
index 0000000000..39ec9fa25e
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/index.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import { workerUtils } from "devtools-utils";
+const { WorkerDispatcher } = workerUtils;
+
+let startArgs;
+let dispatcher;
+
+function getDispatcher() {
+ if (!dispatcher) {
+ dispatcher = new WorkerDispatcher();
+ dispatcher.start(...startArgs);
+ }
+
+ return dispatcher;
+}
+
+export const start = (...args: any[]) => {
+ startArgs = args;
+};
+
+export const stop = () => {
+ if (dispatcher) {
+ dispatcher.stop();
+ dispatcher = null;
+ startArgs = null;
+ }
+};
+
+export const getMatches = (...args: any[]) => {
+ return getDispatcher().invoke("getMatches", ...args);
+};
+
+export const findSourceMatches = (...args: any[]) => {
+ return getDispatcher().invoke("findSourceMatches", ...args);
+};
diff --git a/devtools/client/debugger/src/workers/search/moz.build b/devtools/client/debugger/src/workers/search/moz.build
new file mode 100644
index 0000000000..b7223ac81a
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += []
+
+CompiledModules(
+ "index.js",
+)
diff --git a/devtools/client/debugger/src/workers/search/project-search.js b/devtools/client/debugger/src/workers/search/project-search.js
new file mode 100644
index 0000000000..c55f649cb4
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/project-search.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+// Maybe reuse file search's functions?
+
+import getMatches from "./get-matches";
+
+import type { SourceId, TextSourceContent } from "../../types";
+
+export function findSourceMatches(
+ sourceId: SourceId,
+ content: TextSourceContent,
+ queryText: string
+): Object[] {
+ if (queryText == "") {
+ return [];
+ }
+
+ const modifiers = {
+ caseSensitive: false,
+ regexMatch: false,
+ wholeWord: false,
+ };
+
+ const text = content.value;
+ const lines = text.split("\n");
+
+ return getMatches(queryText, text, modifiers).map(({ line, ch }) => {
+ const { value, matchIndex } = truncateLine(lines[line], ch);
+ return {
+ sourceId,
+ line: line + 1,
+ column: ch,
+ matchIndex,
+ match: queryText,
+ value,
+ };
+ });
+}
+
+// This is used to find start of a word, so that cropped string look nice
+const startRegex = /([ !@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/g;
+// Similarly, find
+const endRegex = new RegExp(
+ [
+ "([ !@#$%^&*()_+-=[]{};':\"\\|,.<>/?])",
+ '[^ !@#$%^&*()_+-=[]{};\':"\\|,.<>/?]*$"/',
+ ].join("")
+);
+
+function truncateLine(text: string, column: number) {
+ if (text.length < 100) {
+ return {
+ matchIndex: column,
+ value: text,
+ };
+ }
+
+ // Initially take 40 chars left to the match
+ const offset = Math.max(column - 40, 0);
+ // 400 characters should be enough to figure out the context of the match
+ const truncStr = text.slice(offset, column + 400);
+ let start = truncStr.search(startRegex);
+ let end = truncStr.search(endRegex);
+
+ if (start > column) {
+ // No word separator found before the match, so we take all characters
+ // before the match
+ start = -1;
+ }
+ if (end < column) {
+ end = truncStr.length;
+ }
+ const value = truncStr.slice(start + 1, end);
+
+ return {
+ matchIndex: column - start - offset - 1,
+ value,
+ };
+}
diff --git a/devtools/client/debugger/src/workers/search/tests/__snapshots__/project-search.spec.js.snap b/devtools/client/debugger/src/workers/search/tests/__snapshots__/project-search.spec.js.snap
new file mode 100644
index 0000000000..193e549b03
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/tests/__snapshots__/project-search.spec.js.snap
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`project search finds matches 1`] = `
+Array [
+ Object {
+ "column": 11,
+ "line": 2,
+ "match": "foo",
+ "matchIndex": 11,
+ "sourceId": "bar.js",
+ "value": " function foo() {",
+ },
+ Object {
+ "column": 4,
+ "line": 3,
+ "match": "foo",
+ "matchIndex": 4,
+ "sourceId": "bar.js",
+ "value": " foo();",
+ },
+]
+`;
diff --git a/devtools/client/debugger/src/workers/search/tests/get-matches.spec.js b/devtools/client/debugger/src/workers/search/tests/get-matches.spec.js
new file mode 100644
index 0000000000..2ce9151fc4
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/tests/get-matches.spec.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import getMatches from "../get-matches";
+
+describe("search", () => {
+ describe("getMatches", () => {
+ it("gets basic string match", () => {
+ const text = "the test string with test in it multiple times test.";
+ const query = "test";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: false,
+ });
+ expect(matchLocations).toHaveLength(3);
+ });
+
+ it("gets basic string match case-sensitive", () => {
+ const text = "the Test string with test in it multiple times test.";
+ const query = "Test";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: false,
+ });
+ expect(matchLocations).toHaveLength(1);
+ });
+
+ it("gets whole word string match", () => {
+ const text = "the test string test in it multiple times whoatestthe.";
+ const query = "test";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: true,
+ regexMatch: false,
+ });
+ expect(matchLocations).toHaveLength(2);
+ });
+
+ it("gets regex match", () => {
+ const text = "the test string test in it multiple times whoatestthe.";
+ const query = "(\\w+)\\s+(\\w+)";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: true,
+ });
+ expect(matchLocations).toHaveLength(4);
+ });
+
+ it("it doesnt fail on empty data", () => {
+ const text = "";
+ const query = "";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: true,
+ });
+ expect(matchLocations).toHaveLength(0);
+ });
+
+ it("fails gracefully when the line is too long", () => {
+ const text = Array(100002).join("x");
+ const query = "query";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: true,
+ });
+ expect(matchLocations).toHaveLength(0);
+ });
+
+ // regression test for #6896
+ it("doesn't crash on the regex 'a*'", () => {
+ const text = "abc";
+ const query = "a*";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: true,
+ });
+ expect(matchLocations).toHaveLength(4);
+ });
+
+ // regression test for #6896
+ it("doesn't crash on the regex '^'", () => {
+ const text = "012";
+ const query = "^";
+ const matchLocations = getMatches(query, text, {
+ caseSensitive: true,
+ wholeWord: false,
+ regexMatch: true,
+ });
+ expect(matchLocations).toHaveLength(1);
+ });
+ });
+});
diff --git a/devtools/client/debugger/src/workers/search/tests/project-search.spec.js b/devtools/client/debugger/src/workers/search/tests/project-search.spec.js
new file mode 100644
index 0000000000..3374d316d9
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/tests/project-search.spec.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import { findSourceMatches } from "../project-search";
+
+const text = `
+ function foo() {
+ foo();
+ }
+`;
+
+describe("project search", () => {
+ const emptyResults = [];
+
+ it("finds matches", () => {
+ const needle = "foo";
+ const content = {
+ type: "text",
+ value: text,
+ contentType: undefined,
+ };
+
+ const matches = findSourceMatches("bar.js", content, needle);
+ expect(matches).toMatchSnapshot();
+ });
+
+ it("finds no matches in source", () => {
+ const needle = "test";
+ const content = {
+ type: "text",
+ value: text,
+ contentType: undefined,
+ };
+ const matches = findSourceMatches("bar.js", content, needle);
+ expect(matches).toEqual(emptyResults);
+ });
+});
diff --git a/devtools/client/debugger/src/workers/search/worker.js b/devtools/client/debugger/src/workers/search/worker.js
new file mode 100644
index 0000000000..bf3ddcf513
--- /dev/null
+++ b/devtools/client/debugger/src/workers/search/worker.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import getMatches from "./get-matches";
+import { findSourceMatches } from "./project-search";
+import { workerUtils } from "devtools-utils";
+const { workerHandler } = workerUtils;
+
+self.onmessage = workerHandler({ getMatches, findSourceMatches });