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
use crate::{ArcDynHit, PluginConfigAdapter, ScoredHit};
use abi_stable::std_types::{RBox, RStr, RString, RVec};
use abi_stable::{external_types::crossbeam_channel::RReceiver, 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, receiver: RReceiver<FrontendMessageNe>) -> 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, receiver: RReceiver<FrontendMessageNe>) -> FrontendExitStatus {
///         // run UI here
///
///         // gravel will exit when this function returns
///         FrontendExitStatus::Exit
///     }
/// }
/// ```
pub trait Frontend {
	/// Constructs a new frontend.
	fn new(engine: BoxDynFrontendContext, config: &PluginConfigAdapter<'_>) -> Self;

	/// Runs the UI. Messages sent to the `receiver` must be handled.
	fn run(&mut self, receiver: RReceiver<FrontendMessageNe>) -> 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 {
	/// Runs the query against configured providers and returns results.
	fn query(&self, query: RStr<'_>) -> QueryResult;

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

	/// Executes the passed hit's secondary action.
	fn run_secondary_hit_action(&self, hit: &ArcDynHit);
}

/// 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 variant of [`FrontendMessage`].
pub type FrontendMessageNe = FrontendMessage_NE;

/// Represents actions the [`Frontend`] should take.
///
/// These values are to be received by the frontend via a provided
/// [`RReceiver`] and must be handled.
#[repr(u8)]
#[derive(StableAbi, Debug, Clone)]
#[sabi(kind(WithNonExhaustive(size = 40, traits(Debug, Clone))))]
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,
}

/// Non-exhaustive variant of [`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,
}