gravel_ffi/
frontend.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use crate::{ActionKind, ArcDynHit, PluginConfigAdapter, ScoredHit};
use abi_stable::std_types::{RBox, ROption, RString, RVec};
use abi_stable::traits::IntoReprRust;
use abi_stable::{sabi_trait, StableAbi};

/// FFI-safe [`FrontendInner`] trait object.
pub type BoxDynFrontend = FrontendInner_TO<'static, RBox<()>>;

/// This trait is auto-implemented with the [`crate::gravel_frontend`] macro.
///
/// It does some boilerplate conversions to reduce complexity in the real [`Frontend`].
#[sabi_trait]
pub trait FrontendInner {
	fn run(&mut self) -> FrontendExitStatusNe;
}

/// Abstracts functionality required for a frontend.
///
/// Implement this and add the [`crate::gravel_frontend`] macro to write a frontend plugin:
/// ```
/// use gravel_ffi::prelude::*;
///
/// pub struct MyFrontend {
///     // put state here
/// }
///
/// // give the plugin a memorable name
/// #[gravel_frontend("my_frontend")]
/// impl Frontend for MyFrontend {
///     fn new(context: BoxDynFrontendContext, config: &PluginConfigAdapter<'_>) -> Self {
///         // initialize UI here
///         Self { }
///     }
///
///     fn run(&mut self) -> FrontendExitStatus {
///         // run UI here
///
///         // gravel will exit when this function returns
///         FrontendExitStatus::Exit
///     }
/// }
/// ```
pub trait Frontend {
	/// Constructs a new frontend. Events must be received and handled by calling [`FrontendContextExt::recv`].
	fn new(engine: BoxDynFrontendContext, config: &PluginConfigAdapter<'_>) -> Self;

	/// Runs the UI; must block until ready to exit.
	fn run(&mut self) -> FrontendExitStatus;
}

/// FFI-safe [`FrontendContext`] trait object.
pub type BoxDynFrontendContext = FrontendContext_TO<'static, RBox<()>>;

/// Context object providing core functionality to the [`Frontend`].
#[sabi_trait]
pub trait FrontendContext {
	/// Use [`FrontendContextExt::recv`] instead.
	#[doc(hidden)]
	fn recv_raw(&self) -> ROption<FrontendMessageNe>;

	/// Runs the query against configured providers and returns results.
	///
	/// The return value is a token unique to each query, allowing frontends to
	/// ignore out-of-date results in lieu of cancellations.
	fn query(&self, query: RString) -> u32;

	/// Executes the passed hit's action.
	fn run_hit_action(&self, hit: &ArcDynHit, kind: ActionKind);
}

pub trait FrontendContextExt: FrontendContext {
	/// Attempts to receive one [`FrontendMessage`].
	///
	/// Returns [`None`] if there is no message or the message is from a newer version of the plugin interface.
	fn recv(&self) -> Option<FrontendMessage> {
		self.recv_raw()
			.into_rust()?
			.into_enum()
			.inspect_err(|e| log::warn!("unknown FrontendMessage, this plugin is out of date: {e}"))
			.ok()
	}
}

impl<T: FrontendContext> FrontendContextExt for T {}

/// A Collection of scored hits returned by the [`FrontendContext`].
#[repr(C)]
#[derive(StableAbi, Default, Debug)]
pub struct QueryResult {
	pub hits: RVec<ScoredHit>,
}

impl QueryResult {
	pub fn new(hits: impl Into<RVec<ScoredHit>>) -> Self {
		Self { hits: hits.into() }
	}
}

/// Non-exhaustive wrapper around [`FrontendMessage`].
pub type FrontendMessageNe = FrontendMessage_NE;

/// Represents actions the [`Frontend`] should take.
///
/// These values are to be received by the frontend via
/// [`FrontendContextExt::recv`] and must be handled.
#[repr(u8)]
#[derive(StableAbi, Debug)]
#[sabi(kind(WithNonExhaustive(size = 40, traits(Debug))))]
pub enum FrontendMessage {
	ShowOrHide,
	Show,
	Hide,

	/// Show the window and pre-populate the query with the parameter.
	ShowWithQuery(RString),

	/// Run the same query again.
	Refresh,

	/// Exit the appplication with [`FrontendExitStatus::Exit`].
	Exit,

	/// Exit the appplication with [`FrontendExitStatus::Restart`].
	Restart,

	/// Clear all caches the frontend may keep.
	ClearCaches,

	/// Result from a previous query.
	///
	/// The first value is a token unique to each query, allowing frontends to
	/// ignore out-of-date results in lieu of cancellations.
	QueryResult(u32, QueryResult),
}

/// Non-exhaustive wrapper around [`FrontendExitStatus`].
pub type FrontendExitStatusNe = FrontendExitStatus_NE;

/// This is returned when a [`Frontend`] exits and tells gravel what to do.
#[repr(u8)]
#[derive(StableAbi, Debug, Copy, Clone)]
#[sabi(kind(WithNonExhaustive(size = 40, traits(Debug, Clone))))]
pub enum FrontendExitStatus {
	Exit,
	Restart,
}