1use crate::{CoreMessage, performance::Stopwatch, scoring, timed};
2use abi_stable::std_types::RString;
3use abi_stable::{external_types::crossbeam_channel::RSender, sabi_trait, std_types::RStr, traits::IntoReprRust};
4use gravel_ffi::{ActionKind, ArcDynHit, FrontendMessage, HitActionContext, ProviderResult, RefDynHitActionContext};
5use gravel_ffi::{BoxDynProvider, QueryResult};
6use itertools::Itertools;
7use std::iter::once;
8
9struct ProviderInfo {
11 pub name: String,
12 pub provider: BoxDynProvider,
13 pub keyword: Option<String>,
14}
15
16pub struct QueryEngine {
18 providers: Vec<ProviderInfo>,
19 action_context: ActionContext,
20}
21
22impl QueryEngine {
23 pub fn new(sender: RSender<CoreMessage>) -> Self {
24 Self {
25 providers: vec![],
26 action_context: ActionContext::new(sender),
27 }
28 }
29
30 pub fn register(&mut self, name: String, provider: BoxDynProvider, keyword: Option<String>) -> &mut Self {
32 let info = ProviderInfo {
33 name,
34 provider,
35 keyword,
36 };
37
38 self.providers.push(info);
39 self
40 }
41
42 pub fn query(&self, query: RStr<'_>) -> QueryResult {
43 fn inner(engine: &QueryEngine, query: &str) -> QueryResult {
44 if let Some(result) = engine.try_keyword_query(query) {
45 return result;
46 }
47
48 engine.full_query(query)
49 }
50
51 let query = query.into_rust();
52
53 if query.trim().is_empty() {
54 return QueryResult::default();
55 }
56
57 inner(self, query)
58 }
59
60 pub fn run_hit_action(&self, hit: &ArcDynHit, kind: ActionKind) {
61 let context = (&self.action_context).into();
62
63 match kind {
64 ActionKind::Primary => hit.action(context),
65 ActionKind::Secondary => hit.secondary_action(context),
66 };
67 }
68
69 pub fn clear_caches(&self) {
70 for provider in &self.providers {
71 provider.provider.clear_caches();
72 }
73 }
74
75 fn full_query(&self, query: &str) -> QueryResult {
77 let providers = self.providers.iter().filter(|provider| provider.keyword.is_none());
78
79 query_all(providers, query)
80 }
81
82 fn try_keyword_query(&self, query: &str) -> Option<QueryResult> {
86 let first_word = query.split(' ').next()?;
87
88 let provider = self.check_keywords(first_word)?;
89
90 let new_query = &query[first_word.len()..query.len()].trim_start();
92
93 Some(query_all(once(provider), new_query))
94 }
95
96 fn check_keywords(&self, first_word: &str) -> Option<&ProviderInfo> {
98 self.providers
99 .iter()
100 .find(|p| matches!(&p.keyword, Some(k) if k == first_word))
101 }
102}
103
104#[expect(single_use_lifetimes)]
106fn query_all<'a>(providers: impl Iterator<Item = &'a ProviderInfo>, query: &str) -> QueryResult {
107 let hits = providers.flat_map(|p| query_one(p, query).hits).collect_vec();
108
109 timed!("scoring took", {
110 let hits = match query.trim() {
111 "*" => scoring::to_unscored(hits),
112 _ => scoring::to_scored(hits, query),
113 };
114
115 QueryResult::new(hits)
116 })
117}
118
119fn query_one(info: &ProviderInfo, query: &str) -> ProviderResult {
120 let stopwatch = Stopwatch::start();
121
122 let result = info.provider.query(query.into());
123
124 log::trace!(
125 "query for provider '{}' took {stopwatch} and produced {} hits",
126 info.name,
127 result.hits.len()
128 );
129
130 result
131}
132
133struct ActionContext {
134 sender: RSender<CoreMessage>,
135}
136
137impl ActionContext {
138 pub fn new(sender: RSender<CoreMessage>) -> Self {
139 Self { sender }
140 }
141
142 fn send(&self, message: CoreMessage) {
143 self.sender
144 .send(message)
145 .inspect_err(|e| log::error!("unable to send core message: {e}"))
146 .ok();
147 }
148
149 fn send_frontend(&self, message: FrontendMessage) {
150 self.send(CoreMessage::Frontend(message));
151 }
152}
153
154impl<'a> From<&'a ActionContext> for RefDynHitActionContext<'a> {
155 fn from(value: &'a ActionContext) -> Self {
156 Self::from_ptr(value, sabi_trait::TD_Opaque)
157 }
158}
159
160impl HitActionContext for ActionContext {
161 fn hide_frontend(&self) {
162 self.send_frontend(FrontendMessage::Hide);
163 }
164
165 fn refresh_frontend(&self) {
166 self.send_frontend(FrontendMessage::Refresh);
167 }
168
169 fn exit(&self) {
170 self.send_frontend(FrontendMessage::Exit);
171 }
172
173 fn restart(&self) {
174 self.send_frontend(FrontendMessage::Restart);
175 }
176
177 fn set_query(&self, query: RString) {
178 self.send_frontend(FrontendMessage::ShowWithQuery(query));
179 }
180
181 fn set_clipboard_text(&self, content: RString) {
182 self.send(CoreMessage::SetClipboardText(content.into_rust()));
183 }
184
185 fn clear_caches(&self) {
186 self.send(CoreMessage::ClearCaches);
187 }
188}