Skip to content

Commit f4e26a0

Browse files
committed
refactor(global-search): seperate matcher and filter
1 parent 7d30cc4 commit f4e26a0

3 files changed

Lines changed: 57 additions & 49 deletions

File tree

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
import { assertNever } from "../assert"
21
import type { IndexCollectionItemEntry, IndexEntry, IndexNodeEntry } from "../indexer/types"
32
import { findRanges } from "./ranges"
4-
import { type Filter, FilterType, type Result, ResultType, type RootNodesFilter, type TextFilter } from "./types"
3+
import { type Filter, type Matcher, type Result, ResultType, type RootNodesFilter, type TextMatcher } from "./types"
54

65
/** Execute a list of filters on a list of entries and return the results. */
7-
export function executeFilters(filters: readonly Filter[], index: readonly IndexEntry[]): Result[] {
6+
export function executeFilters(
7+
/** The matchers to execute on the index. */
8+
matchers: readonly Matcher[],
9+
/** A filter to narrow down the results. */
10+
filters: readonly Filter[],
11+
/** The index to search on */
12+
index: readonly IndexEntry[]
13+
): Result[] {
814
const results: Result[] = []
915

1016
for (const entry of index) {
1117
let include = true
1218
let result: Result | undefined
1319

14-
for (const filter of filters) {
15-
const filterResult = executeFilter(filter, entry)
20+
for (const matcher of matchers) {
21+
const matchResult = executeMatcher(matcher, entry)
1622

17-
if (typeof filterResult !== "boolean") {
18-
result = filterResult
19-
}
20-
21-
if (filterResult === false) {
23+
if (matchResult === undefined) {
2224
include = false
2325
break
2426
}
27+
28+
if (filters.some(filter => executeFilter(filter, matchResult))) {
29+
result = matchResult
30+
}
2531
}
2632

2733
if (include && result) {
@@ -32,30 +38,21 @@ export function executeFilters(filters: readonly Filter[], index: readonly Index
3238
return results
3339
}
3440

35-
/** Execute a filter on a single entry and routes to the appropriate filter function. */
36-
function executeFilter(filter: Filter, entry: IndexEntry): FilterResult {
37-
switch (filter.type) {
38-
case FilterType.Text:
39-
if (entry.type === "CollectionItem") {
40-
return executeTextFilterForCollectionItems(filter, entry)
41-
}
42-
return executeTextFilterForNodes(filter, entry)
43-
case FilterType.RootNodes:
44-
return executeRootNodesFilter(filter, entry)
45-
default:
46-
assertNever(filter)
41+
/** Execute a matcher on a single entry and routes to the appropriate matcher function. */
42+
function executeMatcher(matcher: Matcher, entry: IndexEntry): Result | undefined {
43+
// When more matchers are added, we can add more matcher functions here and use this as a router
44+
if (entry.type === "CollectionItem") {
45+
return executeTextMatcherForCollectionItems(matcher, entry)
4746
}
47+
return executeTextMatcherForNodes(matcher, entry)
4848
}
4949

50-
/** Internal type for filter execution. When result is `false`, the entry is excluded. When result is `true` or `Result`, the entry is included. */
51-
type FilterResult = Result | boolean
52-
53-
function executeTextFilterForNodes(filter: TextFilter, entry: IndexNodeEntry): FilterResult {
50+
function executeTextMatcherForNodes(matcher: TextMatcher, entry: IndexNodeEntry): Result | undefined {
5451
const text = entry.text ?? entry.name
55-
if (!text) return false
52+
if (!text) return undefined
5653

57-
const ranges = findRanges(text, filter.query, filter.caseSensitive)
58-
if (!ranges.length) return false
54+
const ranges = findRanges(text, matcher.query, matcher.caseSensitive)
55+
if (!ranges.length) return undefined
5956

6057
return {
6158
id: entry.id,
@@ -66,11 +63,14 @@ function executeTextFilterForNodes(filter: TextFilter, entry: IndexNodeEntry): F
6663
}
6764
}
6865

69-
function executeTextFilterForCollectionItems(filter: TextFilter, entry: IndexCollectionItemEntry): FilterResult {
66+
function executeTextMatcherForCollectionItems(
67+
matcher: TextMatcher,
68+
entry: IndexCollectionItemEntry
69+
): Result | undefined {
7070
// FIXME: This only returns the first matching field
7171
// Instead of having multiple fields in the index, we should have a single entry per field in the index
7272
for (const [field, text] of Object.entries(entry.fields)) {
73-
const ranges = findRanges(text, filter.query, filter.caseSensitive)
73+
const ranges = findRanges(text, matcher.query, matcher.caseSensitive)
7474
if (ranges.length) {
7575
return {
7676
id: `${entry.id}-${field}`,
@@ -83,9 +83,15 @@ function executeTextFilterForCollectionItems(filter: TextFilter, entry: IndexCol
8383
}
8484
}
8585

86-
return false
86+
return undefined
87+
}
88+
89+
/** Execute a filter on a result and return true if the result should be included. */
90+
function executeFilter(filter: Filter, result: Result): boolean {
91+
// When more filters are added, we can add more filter functions here and use this as a router
92+
return executeRootNodesFilter(filter, result)
8793
}
8894

89-
function executeRootNodesFilter(filter: RootNodesFilter, entry: IndexEntry): FilterResult {
90-
return filter.rootNodes.includes(entry.rootNodeType)
95+
function executeRootNodesFilter(filter: RootNodesFilter, result: Result): boolean {
96+
return filter.rootNodes.includes(result.entry.rootNodeType)
9197
}

plugins/global-search/src/utils/filter/types.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@ import type { IndexCollectionItemEntry, IndexEntry, IndexNodeEntry, RootNodeType
22
import type { Range } from "./ranges"
33

44
export const enum FilterType {
5-
Text = "text",
65
RootNodes = "rootNodes",
76
}
87

9-
interface BaseFilter {
10-
readonly type: FilterType
8+
export const enum MatcherType {
9+
Text = "text",
1110
}
1211

13-
export interface TextFilter extends BaseFilter {
14-
readonly type: FilterType.Text
12+
// A matcher produces a result, while a filter can narrows down the results
13+
export interface TextMatcher {
14+
readonly type: MatcherType.Text
1515
readonly query: string
1616
readonly caseSensitive: boolean
1717
}
1818

19-
export interface RootNodesFilter extends BaseFilter {
19+
export interface RootNodesFilter {
2020
readonly type: FilterType.RootNodes
2121
readonly rootNodes: readonly RootNodeType[]
2222
}
2323

24-
export type Filter = TextFilter | RootNodesFilter
24+
export type Matcher = TextMatcher
25+
export type Filter = RootNodesFilter
2526

2627
export enum ResultType {
2728
CollectionItem = "CollectionItem",

plugins/global-search/src/utils/filter/useFilter.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { IndexEntry, RootNodeType } from "../indexer/types"
33
import { executeFilters } from "./execute-filter"
44
import type { ReadonlyGroupedResults } from "./group-results"
55
import { groupResults } from "./group-results"
6-
import { type Filter, FilterType } from "./types"
6+
import { type Filter, FilterType, type Matcher, MatcherType } from "./types"
77

88
export function useFilter(
99
query: string,
@@ -14,18 +14,19 @@ export function useFilter(
1414
} {
1515
const deferredQuery = useDeferredValue(query)
1616

17+
const matchers = useMemo((): readonly Matcher[] => {
18+
return [{ type: MatcherType.Text, query: deferredQuery, caseSensitive: false }]
19+
}, [deferredQuery])
20+
1721
const filters = useMemo((): readonly Filter[] => {
18-
return [
19-
{ type: FilterType.Text, query: deferredQuery, caseSensitive: false },
20-
{ type: FilterType.RootNodes, rootNodes: searchOptions },
21-
]
22-
}, [deferredQuery, searchOptions])
22+
return [{ type: FilterType.RootNodes, rootNodes: searchOptions }]
23+
}, [searchOptions])
2324

2425
const results = useMemo(() => {
25-
const items = executeFilters(filters, index)
26+
const items = executeFilters(matchers, filters, index)
2627

2728
return groupResults(items)
28-
}, [filters, index])
29+
}, [matchers, filters, index])
2930

3031
return {
3132
results,

0 commit comments

Comments
 (0)