Skip to content

Commit

Permalink
Add filter parameter to search API
Browse files Browse the repository at this point in the history
  • Loading branch information
FIameCaster committed Oct 30, 2023
1 parent a5367c5 commit 6786676
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 36 deletions.
52 changes: 27 additions & 25 deletions package/src/extensions/search/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ export interface ReplaceAPI extends SearchAPI {
*/
selectMatch(index: number, scrollPadding?: number): void
/**
* If a match is selected, it's replaced with the specified string.
* If a match is selected, it's replaced with the specified value.
* If not, the closest match will be selected and the index is returned.
*/
replace(str: string): number | undefined
/** Replaces all matches with the specified string. */
replaceAll(str: string, selection?: [number, number]): void
replace(value: string): number | undefined
/**
* @param value Value
* @param selection Does nothing. Kept for backwards compatibility.
*/
replaceAll(value: string, selection?: [number, number]): void
/** Removes the highlight container from the DOM and all potential event listeners. */
destroy(): void
}
Expand Down Expand Up @@ -102,40 +105,39 @@ const createReplaceAPI = (editor: PrismEditor): ReplaceAPI => {
}
insertText(editor, str)
},
replaceAll(str: string, selection?: [number, number]) {
const { matches, regex } = search
replaceAll(str: string) {
const { matches } = search
if (!matches[0]) return
let value = editor.value,
[start, end] = getSelection(),
newLen = str.length,
newStart = start,
newEnd = end,
[searchStart, searchEnd] = selection || [0, value.length]
newValue = "",
l = matches.length

for (let i = 0, l = matches.length; i < l; i++) {
for (let i = 0; i < l; i++) {
const [matchStart, matchEnd] = matches[i],
lengthDiff = newLen - matchEnd + matchStart
if (end <= matchStart) break
lengthDiff = newLen - matchEnd + matchStart,
move = (pos: number) =>
matchStart > pos
? 0
: pos >= matchEnd
? lengthDiff
: lengthDiff < 0 && pos > matchStart + newLen
? newLen + matchStart - pos
: 0

newEnd +=
end >= matchEnd
? lengthDiff
: lengthDiff < 0 && end > matchStart + newLen
? newLen + matchStart - end
: 0
newStart +=
start >= matchEnd
? lengthDiff
: lengthDiff < 0 && start > matchStart + newLen
? newLen + matchStart - start
: 0
newEnd += move(end)
newStart += move(start)
newValue += i ? value.slice(matches[i - 1][1], matchStart) + str : str
}

insertText(
editor,
value.slice(searchStart, searchEnd).replace(regex, str.replace(/\$/g, "$$$$")),
searchStart,
searchEnd,
newValue,
matches[0][0],
matches[l - 1][1],
newStart,
newEnd,
)
Expand Down
30 changes: 19 additions & 11 deletions package/src/extensions/search/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const template = createTemplate(
"color:#0000;display:none;contain:strict;padding:0 var(--padding-inline,.75em) 0 var(--padding-left);",
)

export type SearchFilter = (start: number, end: number) => boolean

/** Object with methods useful for performing a search and highlighting the matches. */
export interface SearchAPI {
/**
Expand All @@ -16,7 +18,8 @@ export interface SearchAPI {
* @param wholeWord Whether or not matches must be surrounded by word boundries.
* @param useRegExp If false, special characters will be escaped when creating the RegExp.
* @param selection Boundries to search between. If excluded, all the code is searched.
* @param excludedPosition A match containing this position in the string will be excluded.
* @param filter A function called for each match. If it returns false, the match won't be included.
* Can also take an excluded position for backwards compatibility.
* @returns An error message if the RegExp was invalid.
*/
search(
Expand All @@ -25,13 +28,13 @@ export interface SearchAPI {
wholeWord?: boolean,
useRegExp?: boolean,
selection?: [number, number],
excludedPosition?: number,
filter?: number | SearchFilter,
): string | void
/** Container that all the search results are appended to. */
readonly container: HTMLDivElement
/** Current regex used for searching. */
readonly regex: RegExp
/** Array of the positions of all the matches. */
/** Array of positions of all the matches. */
readonly matches: [number, number][]
/** Hides the search container and removes all the matches. */
stopSearch(): void
Expand All @@ -56,15 +59,23 @@ const createSearchAPI = (editor: PrismEditor): SearchAPI => {
editor.overlays.append(container)

return {
search(str, caseSensitive, wholeWord, useRegExp, selection, excludedPosition = -1) {
search(str, caseSensitive, wholeWord, useRegExp, selection, filter) {
if (!str) return stopSearch()
if (!useRegExp) str = regexEscape(str)
const value = editor.value,
searchStr = selection ? value.slice(...selection) : value,
offset = selection?.[0] || 0,
flags = `gum${caseSensitive ? "" : "i"}`
flags = `gum${caseSensitive ? "" : "i"}`,
filterFn =
typeof filter == "number"
? (start: number, end: number) => start > filter || end <= filter
: filter

try {
let match: RegExpExecArray | null,
l: number,
index: number,
i = 0
matchPositions.length = 0
regex = RegExp(str, flags)
// Reassigning the regex for cleaner error messages
Expand All @@ -73,13 +84,10 @@ const createSearchAPI = (editor: PrismEditor): SearchAPI => {
supportsLookbehind ? `(?<=^|\\b|\\W)${str}(?=\\b|\\W|$)` : `\\b${str}\\b`,
flags,
)
for (
let match: RegExpExecArray | null, l: number, index: number, i = 0;
(match = regex.exec(searchStr));

) {
while ((match = regex.exec(searchStr))) {
;(l = match[0].length) || regex.lastIndex++
if ((index = match.index) > excludedPosition || index + l <= excludedPosition)
index = match.index
if (!filterFn || filterFn(index, index + l))
matchPositions[i++] = [index + offset, index + l + offset]
}
} catch (e) {
Expand Down

0 comments on commit 6786676

Please sign in to comment.