diff options
Diffstat (limited to 'devtools/client/debugger/src/workers/search')
6 files changed, 250 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..972dea6818 --- /dev/null +++ b/devtools/client/debugger/src/workers/search/get-matches.js @@ -0,0 +1,45 @@ +/* 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/>. */ + +import assert from "../../utils/assert"; +import buildQuery from "../../utils/build-query"; + +export default function getMatches(query, text, options) { + if (!query || !text || !options) { + return []; + } + const regexQuery = buildQuery(query, options, { + 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, + match: singleMatch[0], + }); + + // 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..928bbf50e6 --- /dev/null +++ b/devtools/client/debugger/src/workers/search/index.js @@ -0,0 +1,17 @@ +/* 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/>. */ + +import { WorkerDispatcher } from "devtools/client/shared/worker-utils"; + +const WORKER_URL = "resource://devtools/client/debugger/dist/search-worker.js"; + +export class SearchDispatcher extends WorkerDispatcher { + constructor(jestUrl) { + super(jestUrl || WORKER_URL); + } + + getMatches = this.task("getMatches"); + + findSourceMatches = this.task("findSourceMatches"); +} 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..f3751b57c4 --- /dev/null +++ b/devtools/client/debugger/src/workers/search/project-search.js @@ -0,0 +1,70 @@ +/* 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/>. */ + +// Maybe reuse file search's functions? + +import getMatches from "./get-matches"; + +export function findSourceMatches(content, queryText, options) { + if (queryText == "") { + return []; + } + + const text = content.value; + const lines = text.split("\n"); + + return getMatches(queryText, text, options).map(({ line, ch, match }) => { + const { value, matchIndex } = truncateLine(lines[line], ch); + return { + line: line + 1, + column: ch, + + matchIndex, + match, + 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("") +); +// For texts over 100 characters this truncates the text (for display) +// around the context of the matched text. +function truncateLine(text, column) { + 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/get-matches.spec.js b/devtools/client/debugger/src/workers/search/tests/get-matches.spec.js new file mode 100644 index 0000000000..7b2da92d43 --- /dev/null +++ b/devtools/client/debugger/src/workers/search/tests/get-matches.spec.js @@ -0,0 +1,99 @@ +/* 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/>. */ + +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/worker.js b/devtools/client/debugger/src/workers/search/worker.js new file mode 100644 index 0000000000..b452697516 --- /dev/null +++ b/devtools/client/debugger/src/workers/search/worker.js @@ -0,0 +1,9 @@ +/* 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/>. */ + +import getMatches from "./get-matches"; +import { findSourceMatches } from "./project-search"; +import { workerHandler } from "../../../../shared/worker-utils"; + +self.onmessage = workerHandler({ getMatches, findSourceMatches }); |