Skip to content

Commit 819b777

Browse files
Merge pull request #1021 from gjsjohnmurray/fix-1019
Server-side search: use include/exclude specs (fixes #1019)
2 parents fcad677 + 98c8b4d commit 819b777

File tree

1 file changed

+108
-1
lines changed

1 file changed

+108
-1
lines changed

src/providers/FileSystemProvider/TextSearchProvider.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)