@@ -105,6 +105,9 @@ export class SearchQuery {
105105 /// @internal
106106 readonly unquoted : string
107107
108+ /// Optional test function used to filter matches.
109+ readonly test : ( ( match : string , state : EditorState , from : number , to : number ) => boolean ) | undefined
110+
108111 /// Create a query object.
109112 constructor ( config : {
110113 /// The search string.
@@ -121,6 +124,10 @@ export class SearchQuery {
121124 replace ?: string ,
122125 /// Enable whole-word matching.
123126 wholeWord ?: boolean
127+
128+ /// Optional function to filter matches. It is passed the matched
129+ /// string, the editor state, and the match range.
130+ test ?: ( match : string , state : EditorState , from : number , to : number ) => boolean
124131 } ) {
125132 this . search = config . search
126133 this . caseSensitive = ! ! config . caseSensitive
@@ -130,6 +137,7 @@ export class SearchQuery {
130137 this . valid = ! ! this . search && ( ! this . regexp || validRegExp ( this . search ) )
131138 this . unquoted = this . unquote ( this . search )
132139 this . wholeWord = ! ! config . wholeWord
140+ this . test = config . test
133141 }
134142
135143 /// @internal
@@ -142,7 +150,7 @@ export class SearchQuery {
142150 eq ( other : SearchQuery ) {
143151 return this . search == other . search && this . replace == other . replace &&
144152 this . caseSensitive == other . caseSensitive && this . regexp == other . regexp &&
145- this . wholeWord == other . wholeWord
153+ this . wholeWord == other . wholeWord && this . test == other . test
146154 }
147155
148156 /// @internal
@@ -178,9 +186,22 @@ abstract class QueryType<Result extends SearchResult = SearchResult> {
178186const enum FindPrev { ChunkSize = 10000 }
179187
180188function stringCursor ( spec : SearchQuery , state : EditorState , from : number , to : number ) {
189+ const wordTest = spec . wholeWord ? stringWordTest ( state . doc , state . charCategorizer ( state . selection . main . head ) ) : undefined
190+ const test = ( from : number , to : number , buffer : string , bufferPos : number ) => {
191+ return (
192+ ( wordTest ? wordTest ( from , to , buffer , bufferPos ) : true ) &&
193+ ( spec . test ? spec . test ( matchFromBuffer ( state , from , to , buffer , bufferPos ) , state , from , to ) : true )
194+ )
195+ }
181196 return new SearchCursor (
182197 state . doc , spec . unquoted , from , to , spec . caseSensitive ? undefined : x => x . toLowerCase ( ) ,
183- spec . wholeWord ? stringWordTest ( state . doc , state . charCategorizer ( state . selection . main . head ) ) : undefined )
198+ wordTest || spec . test ? test : undefined )
199+ }
200+
201+ function matchFromBuffer ( state : EditorState , from : number , to : number , buffer : string , bufferPos : number ) {
202+ if ( from >= bufferPos && to <= bufferPos + buffer . length )
203+ return buffer . slice ( from - bufferPos , to - bufferPos )
204+ return state . doc . sliceString ( from , to )
184205}
185206
186207function stringWordTest ( doc : Text , categorizer : ( ch : string ) => CharCategory ) {
@@ -253,9 +274,17 @@ const enum RegExp { HighlightMargin = 250 }
253274type RegExpResult = typeof RegExpCursor . prototype . value
254275
255276function regexpCursor ( spec : SearchQuery , state : EditorState , from : number , to : number ) {
277+ const wordTest = spec . wholeWord ? regexpWordTest ( state . charCategorizer ( state . selection . main . head ) ) : undefined
278+ const test = ( from : number , to : number , match : RegExpExecArray ) => {
279+ return (
280+ ( wordTest ? wordTest ( from , to , match ) : true ) &&
281+ ( spec . test ? spec . test ( match [ 0 ] , state , from , to ) : true )
282+ )
283+ }
284+
256285 return new RegExpCursor ( state . doc , spec . search , {
257286 ignoreCase : ! spec . caseSensitive ,
258- test : spec . wholeWord ? regexpWordTest ( state . charCategorizer ( state . selection . main . head ) ) : undefined
287+ test : wordTest || spec . test ? test : undefined
259288 } , from , to )
260289}
261290
0 commit comments