# (c) 2023-present Eroax # (c) 2023-present Yagich # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) class_name SearchProvider ## A class facilitating the searching of nodes. ## ## Allows a renderer or subscribing client to search for nodes registered in NodeDB, optionally with filters. ## A list of all filters that can be applied to a search string. static var filters: Array[Filter] = [ # favorites filter. will only show nodes marked as favorite. syntax: "#f" Filter.new( func(search_string: String) -> bool: return "#f" in search_string, func(element: NodeDB.NodeDescriptor, _search_string: String, _pre_strip_string: String) -> bool: return NodeDB.is_node_favorite(element.type), func(search_string: String) -> String: return search_string.replace("#f", "") ), # category filter. will only match nodes that are in a certain category. syntax: "#c category_name" Filter.new( func(search_string: String) -> bool: const p := r"#c\s[\w]+" var r := RegEx.create_from_string(p) var c := r.search(search_string) return c != null, func(element: NodeDB.NodeDescriptor, _search_string: String, pre_strip_string: String) -> bool: const p := r"#c\s[\w]+" var r := RegEx.create_from_string(p) #print("pre: ", pre_strip_string) var c := r.search(pre_strip_string).get_string().split("#c ", false)[0] return c.is_subsequence_ofn(element.category), func(search_string: String) -> String: const p := r"#c\s[\w]+" var r := RegEx.create_from_string(p) var c := r.search(search_string).get_string() #prints("c:", c, "r:", search_string.replace(c, "")) return search_string.replace(c, "") ), ] ## Performs a search for nodes. Filters can be provided directly in the search [param term]. static func search(term: String) -> Array[NodeDB.NodeDescriptor]: var res: Array[NodeDB.NodeDescriptor] = [] var filters_to_apply := filters.filter( func(f: Filter): return f.should_apply.call(term) ) var cleaned_search_string := term # strip string of filter-specific substrings for f: Filter in filters_to_apply: f.pre_strip_string = cleaned_search_string cleaned_search_string = f.strip_string.call(cleaned_search_string) cleaned_search_string = cleaned_search_string.strip_edges() for node_type: String in NodeDB.nodes: var nd: NodeDB.NodeDescriptor = NodeDB.nodes[node_type] if not nd.appears_in_search: continue var full_search_string := nd.name + nd.aliases if cleaned_search_string.is_subsequence_ofn(full_search_string): res.append(nd) # no filters apply, just return the results straight if filters_to_apply.is_empty(): return res # apply filters var filtered_res: Array[NodeDB.NodeDescriptor] = res.duplicate() for f: Filter in filters_to_apply: filtered_res = filtered_res.filter(f.match_term.bind(cleaned_search_string, f.pre_strip_string)) return filtered_res ## A filter that can be applied to a search term in [SearchProvider]. ## ## Filters have a set of functions that are run by the [SearchProvider] ## to determine if any items should be removed from the final match list. class Filter: ## Return [code]true[/code] if this filter should be applied to the search.[br] ## [code]Callable(search_string: String) -> bool[/code] var should_apply: Callable ## Return a [code]bool[/code] if the provided [code]NodeDescriptor[/code] ## should be included in the search results array.[br] ## [code]Callable(element: NodeDB.NodeDescriptor, search_string: String, pre_strip_string: String) -> bool[/code] var match_term: Callable ## Return a string that's stripped of this filter's shorthand. ## [code]Callable(search_string: String) -> String[/code] var strip_string: Callable ## The search string as it was before [member strip_string] was called. Useful for filters that use arguments, like the category filter. var pre_strip_string: String func _init(p_should_apply: Callable, p_match_term: Callable, p_strip_string: Callable) -> void: should_apply = p_should_apply match_term = p_match_term strip_string = p_strip_string