@@ -52,6 +52,7 @@ export class TextSearchProvider implements vscode.TextSearchProvider {
5252 let projectList : string [ ] ;
5353 let searchPromise : Promise < SearchResult [ ] > ;
5454 const params = new URLSearchParams ( options . folder . query ) ;
55+ const csp = params . has ( "csp" ) && [ "" , "1" ] . includes ( params . get ( "csp" ) ) ;
5556 if ( params . has ( "project" ) && params . get ( "project" ) . length ) {
5657 project = params . get ( "project" ) ;
5758 projectList = await api
@@ -104,13 +105,94 @@ export class TextSearchProvider implements vscode.TextSearchProvider {
104105 } else {
105106 const sysStr = params . has ( "system" ) && params . get ( "system" ) . length ? params . get ( "system" ) : "0" ;
106107 const genStr = params . has ( "generated" ) && params . get ( "generated" ) . length ? params . get ( "generated" ) : "0" ;
108+
109+ let uri = options . folder ;
110+
111+ if ( ! params . get ( "filter" ) ) {
112+ // Unless isfs spec already includes a non-empty filter (which it rarely does), apply includes and excludes at the server side.
113+ // Convert **/ separators and /** suffix into multiple *-patterns that simulate these elements of glob syntax.
114+
115+ // Function to convert glob-style filters into ones that the server understands
116+ const convertFilters = ( filters : string [ ] ) : string [ ] => {
117+ // Use map to prevent duplicates in final result
118+ const filterMap = new Map < string , void > ( ) ;
119+
120+ // The recursive function we use
121+ const recurse = ( value : string ) : void => {
122+ const parts = value . split ( "**/" ) ;
123+ if ( parts . length < 2 ) {
124+ // No more recursion
125+ if ( value . endsWith ( "/**" ) ) {
126+ filterMap . set ( value . slice ( 0 , - 1 ) ) ;
127+ filterMap . set ( value . slice ( 0 , - 3 ) ) ;
128+ } else {
129+ filterMap . set ( value ) ;
130+ }
131+ } else {
132+ const first = parts [ 0 ] ;
133+ const rest = parts . slice ( 1 ) ;
134+ recurse ( first + "*/" + rest . join ( "**/" ) ) ;
135+ recurse ( first + rest . join ( "**/" ) ) ;
136+ }
137+ } ;
138+
139+ // Invoke our recursive function
140+ filters
141+ . filter ( ( value ) => csp || ! value . match ( / \. ( [ a - z ] + | \* ) \/ \* \* $ / ) ) // drop superfluous entries ending .xyz/** or .*/** when not handling CSP files
142+ . forEach ( ( value ) => {
143+ recurse ( value ) ;
144+ } ) ;
145+
146+ // Convert map to array and return it
147+ const results : string [ ] = [ ] ;
148+ filterMap . forEach ( ( _v , key ) => {
149+ results . push ( key ) ;
150+ } ) ;
151+ return results ;
152+ } ;
153+
154+ // Function to get one of the two kinds of exclude settings as an array
155+ const getConfigExcludes = ( key : string ) => {
156+ return Object . entries ( vscode . workspace . getConfiguration ( key , options . folder ) . get ( "exclude" ) )
157+ . filter ( ( value ) => value [ 1 ] === true )
158+ . map ( ( value ) => value [ 0 ] ) ;
159+ } ;
160+
161+ // Build an array containing the files.exclude settings followed by the search.exclude ones,
162+ // then try to remove exactly those from the end of the ones passed to us when "Use Exclude Settings and Ignore Files" is on.
163+ const configurationExcludes = getConfigExcludes ( "files" ) . concat ( getConfigExcludes ( "search" ) ) ;
164+ const ourExcludes = options . excludes ;
165+ while ( configurationExcludes . length > 0 ) {
166+ if ( configurationExcludes . pop ( ) !== ourExcludes . pop ( ) ) {
167+ break ;
168+ }
169+ }
170+
171+ // If we successfully removed them all, the ones that remain were explicitly entered in the "files to exclude" field of Search, so use them.
172+ // If removal was unsuccessful use the whole set.
173+ const filterExclude = convertFilters ( ! configurationExcludes . length ? ourExcludes : options . excludes ) . join ( ",'" ) ;
174+
175+ const filterInclude =
176+ options . includes . length > 0
177+ ? convertFilters ( options . includes ) . join ( "," )
178+ : filterExclude
179+ ? fileSpecFromURI ( uri ) // Excludes were specified but no includes, so start with the default includes (this step makes type=cls|rtn effective)
180+ : "" ;
181+ const filter = filterInclude + ( ! filterExclude ? "" : ",'" + filterExclude ) ;
182+ if ( filter ) {
183+ // Unless isfs is serving CSP files, slash separators in filters must be converted to dot ones before sending to server
184+ params . append ( "filter" , csp ? filter : filter . replace ( / \/ / g, "." ) ) ;
185+ uri = options . folder . with ( { query : params . toString ( ) } ) ;
186+ }
187+ }
188+
107189 searchPromise = api
108190 . actionSearch ( {
109191 query : query . pattern ,
110192 regex : query . isRegExp ,
111193 word : query . isWordMatch ,
112194 case : query . isCaseSensitive ,
113- files : fileSpecFromURI ( options . folder ) ,
195+ files : fileSpecFromURI ( uri ) ,
114196 sys : sysStr === "1" || ( sysStr === "0" && api . ns === "%SYS" ) ,
115197 gen : genStr === "1" ,
116198 // If options.maxResults is null the search is supposed to return an unlimited number of results
@@ -139,6 +221,31 @@ export class TextSearchProvider implements vscode.TextSearchProvider {
139221 return ;
140222 }
141223 }
224+
225+ // Don't report matches in filetypes we don't want or don't handle
226+ const fileType = file . doc . split ( "." ) . pop ( ) . toLowerCase ( ) ;
227+ if ( ! csp ) {
228+ switch ( params . get ( "type" ) ) {
229+ case "cls" :
230+ if ( fileType !== "cls" ) {
231+ return ;
232+ }
233+ break ;
234+
235+ case "rtn" :
236+ if ( ! [ "inc" , "int" , "mac" ] . includes ( fileType ) ) {
237+ return ;
238+ }
239+ break ;
240+
241+ default :
242+ if ( ! [ "cls" , "inc" , "int" , "mac" ] . includes ( fileType ) ) {
243+ return ;
244+ }
245+ break ;
246+ }
247+ }
248+
142249 const uri = DocumentContentProvider . getUri ( file . doc , "" , "" , true , options . folder ) ;
143250 const content = decoder . decode ( await vscode . workspace . fs . readFile ( uri ) ) . split ( "\n" ) ;
144251 // Find all lines that we have matches on
0 commit comments