Skip to content

Commit cd8adc2

Browse files
committed
search matching changes
1 parent 0f97ddd commit cd8adc2

3 files changed

Lines changed: 124 additions & 75 deletions

File tree

src/components/Docs/SchemaClass.tsx

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { getGame } from "../../games";
1111
import {
1212
matchesWords,
1313
matchesMetadataKeys,
14+
filterItems,
1415
useParsedSearch,
1516
useFieldParam,
1617
} from "./utils/filtering";
@@ -134,11 +135,8 @@ export const SchemaClassView: React.FC<{
134135
}> = ({ declaration }) => {
135136
const { root, classesByKey } = useContext(DeclarationsContext);
136137
const navigate = useNavigate();
137-
const {
138-
nameWords: searchWords,
139-
offsets: searchOffsets,
140-
metadataKeys: searchMetadata,
141-
} = useParsedSearch();
138+
const parsed = useParsedSearch();
139+
const { nameWords: searchWords, offsets: searchOffsets, metadataKeys: searchMetadata } = parsed;
142140
const fieldParam = useFieldParam();
143141

144142
const isSearching = searchWords.length > 0 || searchOffsets.size > 0 || searchMetadata.length > 0;
@@ -167,18 +165,21 @@ export const SchemaClassView: React.FC<{
167165
const nameMatches = searchWords.length > 0 && matchesWords(declaration.name, searchWords);
168166
const collapseNonMatching = isSearching && !nameMatches;
169167

170-
const { matchingFields, hiddenCount } = useMemo(() => {
171-
if (!collapseNonMatching) {
172-
return { matchingFields: declaration.fields, hiddenCount: 0 };
173-
}
174-
const matching = declaration.fields.filter(
175-
(f) =>
176-
(searchWords.length > 0 && matchesWords(f.name, searchWords)) ||
177-
(searchOffsets.size > 0 && searchOffsets.has(f.offset)) ||
178-
(searchMetadata.length > 0 && matchesMetadataKeys(f.metadata, searchMetadata)),
179-
);
180-
return { matchingFields: matching, hiddenCount: declaration.fields.length - matching.length };
181-
}, [declaration.fields, searchWords, searchOffsets, searchMetadata, collapseNonMatching]);
168+
const {
169+
visible: matchingFields,
170+
highlighted: highlightedFields,
171+
hiddenCount,
172+
} = useMemo(
173+
() =>
174+
filterItems(
175+
declaration.fields,
176+
parsed,
177+
declaration.name,
178+
collapseNonMatching,
179+
(f) => searchOffsets.size === 0 || searchOffsets.has(f.offset),
180+
),
181+
[declaration.fields, parsed, declaration.name, collapseNonMatching, searchOffsets],
182+
);
182183

183184
const bitfieldInfo = useMemo(() => computeBitfieldInfo(declaration.fields), [declaration.fields]);
184185
const declPath = declarationPath(root, declaration.module, declaration.name);
@@ -194,8 +195,8 @@ export const SchemaClassView: React.FC<{
194195
</CommonGroupSignature>
195196
</DeclarationHeader>
196197
{(!collapseNonMatching ||
197-
(searchMetadata.length > 0 &&
198-
matchesMetadataKeys(declaration.metadata, searchMetadata))) && (
198+
(searchMetadata.length > 0 && matchesMetadataKeys(declaration.metadata, searchMetadata)) ||
199+
(searchWords.length > 0 && matchesMetadataKeys(declaration.metadata, searchWords))) && (
199200
<MetadataTags metadata={declaration.metadata} root={root} navigate={navigate} />
200201
)}
201202
{inheritedGroups.length > 0 && <InheritedSection groups={inheritedGroups} />}
@@ -209,12 +210,7 @@ export const SchemaClassView: React.FC<{
209210
root={root}
210211
navigate={navigate}
211212
bitfield={bitfieldInfo.get(field)}
212-
highlighted={
213-
collapseNonMatching ||
214-
(searchWords.length > 0 && matchesWords(field.name, searchWords)) ||
215-
(searchOffsets.size > 0 && searchOffsets.has(field.offset)) ||
216-
(searchMetadata.length > 0 && matchesMetadataKeys(field.metadata, searchMetadata))
217-
}
213+
highlighted={highlightedFields.has(field)}
218214
anchored={fieldParam === field.name}
219215
/>
220216
))}

src/components/Docs/SchemaEnum.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ModuleBadge, GitHubFileLink } from "./SchemaClass";
1313
import {
1414
matchesWords,
1515
matchesMetadataKeys,
16+
filterItems,
1617
useParsedSearch,
1718
useFieldParam,
1819
} from "./utils/filtering";
@@ -74,24 +75,22 @@ export const SchemaEnumView: React.FC<{
7475
}> = ({ declaration }) => {
7576
const { root } = useContext(DeclarationsContext);
7677
const navigate = useNavigate();
77-
const { nameWords: searchWords, metadataKeys: searchMetadata } = useParsedSearch();
78+
const parsed = useParsedSearch();
79+
const { nameWords: searchWords, metadataKeys: searchMetadata } = parsed;
7880
const fieldParam = useFieldParam();
7981

8082
const isSearching = searchWords.length > 0 || searchMetadata.length > 0;
8183
const nameMatches = searchWords.length > 0 && matchesWords(declaration.name, searchWords);
8284
const collapseNonMatching = isSearching && !nameMatches;
8385

84-
const { matchingMembers, hiddenCount } = useMemo(() => {
85-
if (!collapseNonMatching) {
86-
return { matchingMembers: declaration.members, hiddenCount: 0 };
87-
}
88-
const matching = declaration.members.filter(
89-
(m) =>
90-
(searchWords.length > 0 && matchesWords(m.name, searchWords)) ||
91-
(searchMetadata.length > 0 && matchesMetadataKeys(m.metadata, searchMetadata)),
92-
);
93-
return { matchingMembers: matching, hiddenCount: declaration.members.length - matching.length };
94-
}, [declaration.members, searchWords, searchMetadata, collapseNonMatching]);
86+
const {
87+
visible: matchingMembers,
88+
highlighted: highlightedMembers,
89+
hiddenCount,
90+
} = useMemo(
91+
() => filterItems(declaration.members, parsed, declaration.name, collapseNonMatching),
92+
[declaration.members, parsed, declaration.name, collapseNonMatching],
93+
);
9594

9695
const declPath = declarationPath(root, declaration.module, declaration.name);
9796

@@ -107,8 +106,8 @@ export const SchemaEnumView: React.FC<{
107106
</CommonGroupSignature>
108107
</DeclarationHeader>
109108
{(!collapseNonMatching ||
110-
(searchMetadata.length > 0 &&
111-
matchesMetadataKeys(declaration.metadata, searchMetadata))) && (
109+
(searchMetadata.length > 0 && matchesMetadataKeys(declaration.metadata, searchMetadata)) ||
110+
(searchWords.length > 0 && matchesMetadataKeys(declaration.metadata, searchWords))) && (
112111
<MetadataTags metadata={declaration.metadata} root={root} navigate={navigate} />
113112
)}
114113
{(matchingMembers.length > 0 || hiddenCount > 0) && (
@@ -120,11 +119,7 @@ export const SchemaEnumView: React.FC<{
120119
fieldUrlBase={declPath}
121120
root={root}
122121
navigate={navigate}
123-
highlighted={
124-
collapseNonMatching ||
125-
(searchWords.length > 0 && matchesWords(member.name, searchWords)) ||
126-
(searchMetadata.length > 0 && matchesMetadataKeys(member.metadata, searchMetadata))
127-
}
122+
highlighted={highlightedMembers.has(member)}
128123
anchored={fieldParam === member.name}
129124
/>
130125
))}

src/components/Docs/utils/filtering.tsx

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,57 @@ export function matchesMetadataKeys(
8787
keys: string[],
8888
): boolean {
8989
if (!metadata || metadata.length === 0 || keys.length === 0) return false;
90-
return keys.every((key) => metadata.some((m) => m.name.toLowerCase().includes(key)));
90+
return keys.every((key) =>
91+
metadata.some(
92+
(m) =>
93+
m.name.toLowerCase().includes(key) ||
94+
(m.value != null && m.value.toLowerCase().includes(key)),
95+
),
96+
);
97+
}
98+
99+
interface HasNameAndMetadata {
100+
name: string;
101+
metadata?: api.SchemaMetadataEntry[];
102+
}
103+
104+
export function filterItems<T extends HasNameAndMetadata>(
105+
items: T[],
106+
parsed: ParsedSearch,
107+
declarationName: string,
108+
collapseNonMatching: boolean,
109+
extraMatch?: (item: T) => boolean,
110+
): { visible: T[]; highlighted: Set<T>; hiddenCount: number } {
111+
const { nameWords, metadataKeys } = parsed;
112+
// Words not already matched by the declaration name must match at the field level
113+
const declLower = declarationName.toLowerCase();
114+
const remainingWords = nameWords.filter((w) => !declLower.includes(w));
115+
116+
function isMatch(item: T): boolean {
117+
return (
118+
(remainingWords.length === 0 ||
119+
matchesWords(item.name, remainingWords) ||
120+
matchesMetadataKeys(item.metadata, remainingWords)) &&
121+
(metadataKeys.length === 0 || matchesMetadataKeys(item.metadata, metadataKeys)) &&
122+
(extraMatch == null || extraMatch(item))
123+
);
124+
}
125+
126+
const isSearching = nameWords.length > 0 || metadataKeys.length > 0 || parsed.offsets.size > 0;
127+
128+
if (!collapseNonMatching) {
129+
return {
130+
visible: items,
131+
highlighted: isSearching ? new Set(items.filter(isMatch)) : new Set(),
132+
hiddenCount: 0,
133+
};
134+
}
135+
const matching = items.filter(isMatch);
136+
return {
137+
visible: matching,
138+
highlighted: new Set(matching),
139+
hiddenCount: items.length - matching.length,
140+
};
91141
}
92142

93143
function doSearch(declarations: api.Declaration[], parsed: ParsedSearch): api.Declaration[] {
@@ -99,53 +149,61 @@ function doSearch(declarations: api.Declaration[], parsed: ParsedSearch): api.De
99149
return moduleWords.some((w) => module.includes(w));
100150
}
101151

102-
function matchesName(name: string): boolean {
103-
const lower = name.toLowerCase();
104-
return nameWords.every((word) => lower.includes(word));
105-
}
152+
function matchesNameWords(declaration: api.Declaration): boolean {
153+
function wordMatchesScope(
154+
word: string,
155+
name: string,
156+
metadata?: api.SchemaMetadataEntry[],
157+
): boolean {
158+
return (
159+
name.includes(word) ||
160+
(metadata?.some(
161+
(m) =>
162+
m.name.toLowerCase().includes(word) ||
163+
(m.value != null && m.value.toLowerCase().includes(word)),
164+
) ??
165+
false)
166+
);
167+
}
106168

107-
function matchesOffset(declaration: api.Declaration): boolean {
108-
if (declaration.kind !== "class") return false;
109-
return declaration.fields.some((f) => offsetSet.has(f.offset));
169+
const declLower = declaration.name.toLowerCase();
170+
let items: { name: string; metadata?: api.SchemaMetadataEntry[] }[] = [];
171+
if (declaration.kind === "class") items = declaration.fields;
172+
else if (declaration.kind === "enum") items = declaration.members;
173+
174+
return nameWords.every(
175+
(word) =>
176+
wordMatchesScope(word, declLower, declaration.metadata) ||
177+
items.some((item) => wordMatchesScope(word, item.name.toLowerCase(), item.metadata)),
178+
);
110179
}
111180

112181
function matchesMetadata(declaration: api.Declaration): boolean {
113182
if (matchesMetadataKeys(declaration.metadata, metadataKeys)) return true;
114-
if (declaration.kind === "class") {
183+
if (declaration.kind === "class")
115184
return declaration.fields.some((f) => matchesMetadataKeys(f.metadata, metadataKeys));
116-
}
117-
if (declaration.kind === "enum") {
185+
if (declaration.kind === "enum")
118186
return declaration.members.some((m) => matchesMetadataKeys(m.metadata, metadataKeys));
119-
}
120187
return false;
121188
}
122189

190+
function matchesOffset(declaration: api.Declaration): boolean {
191+
if (declaration.kind !== "class") return false;
192+
return declaration.fields.some((f) => offsetSet.has(f.offset));
193+
}
194+
123195
const hasNameFilter = nameWords.length > 0;
124196
const hasOffsetFilter = offsetSet.size > 0;
125197
const hasMetadataFilter = metadataKeys.length > 0;
126198

127199
return declarations.filter((declaration) => {
128200
if (!filterModule(declaration)) return false;
129201

130-
let nameMatch = false;
131-
if (hasNameFilter) {
132-
nameMatch =
133-
matchesName(declaration.name) ||
134-
(declaration.kind === "class" && declaration.fields.some((f) => matchesName(f.name))) ||
135-
(declaration.kind === "enum" && declaration.members.some((m) => matchesName(m.name)));
136-
}
137-
138-
const offsetMatch = hasOffsetFilter && matchesOffset(declaration);
139-
const metadataMatch = hasMetadataFilter && matchesMetadata(declaration);
140-
141-
// AND all active filters together
142-
const filters: boolean[] = [];
143-
if (hasNameFilter) filters.push(nameMatch);
144-
if (hasOffsetFilter) filters.push(offsetMatch);
145-
if (hasMetadataFilter) filters.push(metadataMatch);
202+
const nameMatch = !hasNameFilter || matchesNameWords(declaration);
203+
const metadataMatch = !hasMetadataFilter || matchesMetadata(declaration);
204+
const offsetMatch = !hasOffsetFilter || matchesOffset(declaration);
146205

147-
if (filters.length > 0) return filters.every(Boolean);
148-
// Module-only filter: already passed filterModule() above
149-
return moduleWords.length > 0;
206+
if (!hasNameFilter && !hasOffsetFilter && !hasMetadataFilter) return moduleWords.length > 0;
207+
return nameMatch && metadataMatch && offsetMatch;
150208
});
151209
}

0 commit comments

Comments
 (0)