gravel_ffi/
hit.rs

1use abi_stable::pointer_trait::ImmutableRef;
2use abi_stable::std_types::{RArc, ROption, RStr, RString};
3use abi_stable::{RRef, StableAbi, sabi_trait, traits::IntoReprC};
4use std::fmt::Debug;
5
6/// The maximum score a [`Hit`] can have.
7pub const MAX_SCORE: u32 = u32::MAX;
8
9/// The minimum score a [`Hit`] can have.
10pub const MIN_SCORE: u32 = u32::MIN;
11
12/// FFI-safe [`Hit`] trait object.
13///
14/// It uses [`RArc`] to facilitate caching hits.
15pub type ArcDynHit = Hit_TO<'static, RArc<()>>;
16
17/// This is the trait for hits returned from [`crate::Provider`]s.
18///
19/// It must contain some information about the hit, as well as
20/// a function to execute when it is selected.
21///
22/// For most situations, a [`SimpleHit`] is sufficient.
23#[sabi_trait]
24pub trait Hit: Sync + Send + Debug {
25	fn title(&self) -> RStr<'_>;
26
27	fn subtitle(&self) -> RStr<'_> {
28		RStr::default()
29	}
30
31	/// If [`ROption::RSome`], skips normal scoring for this hit,
32	/// instead using the contained score as-is.
33	///
34	/// This is useful for pinning a hit to the top or bottom of the results,
35	/// but probably not very useful for actual scoring, as the underlying scoring
36	/// implementation used in gravel may change.
37	fn override_score(&self) -> ROption<u32> {
38		ROption::RNone
39	}
40
41	fn action(&self, context: RefDynHitActionContext<'_>);
42
43	/// Secondary action for related functionality.
44	///
45	/// How this is triggered and what it does is plugin-defined. By default,
46	/// it calls the regular action.
47	fn secondary_action(&self, context: RefDynHitActionContext<'_>) {
48		log::trace!("no secondary action set for hit '{}'", self.title());
49
50		self.action(context);
51	}
52}
53
54/// Clones an [`ArcDynHit`], as this is not straightforward.
55///
56/// Like [`std::sync::Arc`], cloning just increments the reference counter.
57pub fn clone_hit_arc(hit: &ArcDynHit) -> ArcDynHit {
58	ArcDynHit::from_sabi(hit.obj.shallow_clone())
59}
60
61#[repr(C)]
62#[derive(StableAbi, Debug, PartialEq, Eq, Copy, Clone)]
63pub enum ActionKind {
64	Primary,
65	Secondary,
66}
67
68/// Wraps an [`ArcDynHit`] with scoring metadata.
69#[repr(C)]
70#[derive(StableAbi, Debug)]
71pub struct ScoredHit {
72	pub hit: ArcDynHit,
73	pub score: u32,
74}
75
76impl ScoredHit {
77	pub fn from(hit: ArcDynHit, score: u32) -> Self {
78		Self { hit, score }
79	}
80}
81
82/// FFI-safe reference to a [`HitActionContext`] trait object.
83pub type RefDynHitActionContext<'a> = HitActionContext_TO<'static, RRef<'a, ()>>;
84
85/// Abstracts interaction between a hit action and the frontend.
86#[sabi_trait]
87pub trait HitActionContext {
88	/// Asks the frontend to hide.
89	fn hide_frontend(&self);
90
91	/// Asks the frontend to query again.
92	///
93	/// Useful if the plugin just did something that changes the results of the next query.
94	fn refresh_frontend(&self);
95
96	/// Exits the whole application.
97	fn exit(&self);
98
99	/// Restarts the whole application.
100	fn restart(&self);
101
102	/// Sets the query and runs it.
103	fn set_query(&self, query: RString);
104
105	/// Clears caches in the entire application.
106	fn clear_caches(&self);
107
108	/// Writes to the system clipboard.
109	fn set_clipboard_text(&self, content: RString);
110}
111
112type SimpleHitAction = Box<dyn Fn(&SimpleHit, RefDynHitActionContext<'_>) + Send + Sync>;
113
114/// Standard implementation of [`Hit`] using closures.
115#[repr(C)]
116pub struct SimpleHit {
117	pub title: RString,
118	pub subtitle: RString,
119	pub override_score: ROption<u32>,
120	pub action: SimpleHitAction,
121	pub secondary_action: Option<SimpleHitAction>,
122}
123
124impl Debug for SimpleHit {
125	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126		f.debug_struct("SimpleHit")
127			.field("title", &self.title)
128			.field("subtitle", &self.subtitle)
129			.field("override_score", &self.override_score)
130			.field("action", &format_args!("Fn@{:p}", self.action.to_raw_ptr()))
131			.finish()
132	}
133}
134
135impl SimpleHit {
136	#[must_use]
137	pub fn new(
138		title: impl Into<RString>,
139		subtitle: impl Into<RString>,
140		func: impl Fn(&Self, RefDynHitActionContext<'_>) + Send + Sync + 'static,
141	) -> Self {
142		Self {
143			title: title.into(),
144			subtitle: subtitle.into(),
145			override_score: ROption::RNone,
146			action: Box::new(func),
147			secondary_action: None,
148		}
149	}
150
151	/// Sets the override score for this hit.
152	///
153	/// See [`Hit::override_score`] for more information.
154	#[must_use]
155	pub fn with_score(mut self, score: impl Into<Option<u32>>) -> Self {
156		let option: Option<u32> = score.into();
157		self.override_score = option.into_c();
158		self
159	}
160
161	/// Sets the secondary action for this hit.
162	///
163	/// See [`Hit::secondary_action`] for more information.
164	#[must_use]
165	pub fn with_secondary(
166		mut self,
167		action: impl Fn(&Self, RefDynHitActionContext<'_>) + Send + Sync + 'static,
168	) -> Self {
169		self.secondary_action = Some(Box::new(action));
170		self
171	}
172}
173
174impl Hit for SimpleHit {
175	fn action(&self, context: RefDynHitActionContext<'_>) {
176		(self.action)(self, context);
177	}
178
179	fn secondary_action(&self, context: RefDynHitActionContext<'_>) {
180		let Some(action) = &self.secondary_action else {
181			log::trace!("no secondary action set for hit '{}'", self.title());
182
183			return self.action(context);
184		};
185
186		(action)(self, context);
187	}
188
189	fn title(&self) -> RStr<'_> {
190		self.title.as_rstr()
191	}
192
193	fn subtitle(&self) -> RStr<'_> {
194		self.subtitle.as_rstr()
195	}
196
197	fn override_score(&self) -> ROption<u32> {
198		self.override_score
199	}
200}
201
202impl From<SimpleHit> for ArcDynHit {
203	fn from(value: SimpleHit) -> Self {
204		Self::from_ptr(RArc::new(value), sabi_trait::TD_Opaque)
205	}
206}