strop_engine/editor/picker/
ranking.rs1use super::{Editor, PickerId};
4use std::collections::HashSet;
5use std::sync::mpsc::{self, Receiver, Sender};
6use strop_core::worker::{FailureKind, Outcome, Ticket};
7use strop_picker::{RankingEvent, RankingWorker};
8
9#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
10pub struct Key {
11 pub picker: PickerId,
12 pub query: String,
13 pub items: usize,
14}
15#[derive(serde::Serialize, serde::Deserialize)]
16pub struct Event {
17 pub picker: PickerId,
18 pub update: RankingEvent<Key>,
19}
20pub(crate) struct State {
21 pub tx: Sender<Event>,
22 pub rx: Option<Receiver<Event>>,
23 pub retiring: HashSet<PickerId>,
24}
25impl Default for State {
26 fn default() -> Self {
27 let (tx, rx) = mpsc::channel();
28 Self {
29 tx,
30 rx: Some(rx),
31 retiring: HashSet::new(),
32 }
33 }
34}
35impl Editor {
36 pub(super) fn start_picker_ranking(&mut self) {
37 let Some(glue) = self.picker.as_mut() else {
38 return;
39 };
40 let id = glue.id;
41 glue.rank_alive = true;
42 match self.tape.request("picker.ranker", &id) {
43 Ok(false) => {
44 self.request_picker_ranking();
45 return;
46 }
47 Err(error) => {
48 glue.rank_alive = false;
49 glue.picker.error = Some(error.to_string());
50 return;
51 }
52 Ok(true) => {}
53 }
54 let tx = self.picker_ranking.tx.clone();
55 match RankingWorker::start(move |update| tx.send(Event { picker: id, update }).is_ok()) {
56 Ok(worker) => glue.rank_worker = Some(worker),
57 Err(error) => {
58 glue.rank_alive = false;
59 glue.picker.error = Some(format!("picker ranking unavailable: {error}"));
60 return;
61 }
62 }
63 self.request_picker_ranking();
64 }
65
66 pub(crate) fn request_picker_ranking(&mut self) {
67 let Some(glue) = self.picker.as_mut() else {
68 return;
69 };
70 if !glue.rank_alive {
71 return;
72 }
73 let effective = glue
77 .picker
78 .rank_query
79 .clone()
80 .unwrap_or_else(|| glue.picker.input.text.clone());
81 if glue.ranked_query.as_deref() != Some(effective.as_str()) {
82 glue.picker.clear_results();
83 }
84 if glue.rank_pending.is_some() {
85 if !glue.rank_dirty {
86 if let Some(worker) = &glue.rank_worker {
87 worker.cancel_pending();
88 }
89 }
90 glue.rank_dirty = true;
91 return;
92 }
93 glue.rank_dirty = false;
94 let request = match self.worker_ids.allocate() {
95 Ok(request) => request,
96 Err(error) => {
97 glue.picker.error = Some(error.message);
98 return;
99 }
100 };
101 let filter = glue.picker.filter_request();
102 let ticket = Ticket {
103 request,
104 key: Key {
105 picker: glue.id,
106 query: filter.query.clone(),
107 items: filter.catalog.len(),
108 },
109 };
110 glue.rank_pending = Some(ticket.clone());
111 match self.tape.request("picker.rank", &ticket) {
112 Ok(false) => return,
113 Ok(true) => {}
114 Err(error) => {
115 self.handle_picker_ranking(Event {
116 picker: ticket.key.picker,
117 update: RankingEvent::Completed(strop_core::worker::Completion {
118 ticket,
119 outcome: Outcome::failed(FailureKind::Protocol, error.to_string()),
120 }),
121 });
122 return;
123 }
124 }
125 let result = glue
126 .rank_worker
127 .as_ref()
128 .ok_or_else(|| std::io::Error::other("picker ranking worker is absent"))
129 .and_then(|worker| worker.submit(ticket.clone(), filter));
130 if let Err(error) = result {
131 self.handle_picker_ranking(Event {
132 picker: ticket.key.picker,
133 update: RankingEvent::Completed(strop_core::worker::Completion {
134 ticket,
135 outcome: Outcome::failed(FailureKind::Disconnected, error.to_string()),
136 }),
137 });
138 }
139 }
140
141 pub(crate) fn handle_picker_ranking(&mut self, event: Event) {
142 if matches!(&event.update, RankingEvent::Stopped) {
143 self.picker_ranking.retiring.remove(&event.picker);
144 }
145 let Some(glue) = self.picker.as_mut().filter(|glue| glue.id == event.picker) else {
146 return;
147 };
148 match event.update {
149 RankingEvent::Completed(completion) => {
150 if glue.rank_pending.as_ref() != Some(&completion.ticket) {
151 return;
152 }
153 glue.rank_pending = None;
154 if std::mem::take(&mut glue.rank_dirty) {
155 self.request_picker_ranking();
156 return;
157 }
158 match completion.outcome {
159 Outcome::Success(ranking) => {
160 let effective = glue
164 .picker
165 .rank_query
166 .clone()
167 .unwrap_or_else(|| glue.picker.input.text.clone());
168 if completion.ticket.key.query != effective
169 || !glue.picker.install_ranking(ranking)
170 {
171 return;
172 }
173 glue.ranked_query = Some(completion.ticket.key.query);
174 }
175 Outcome::Failed { failure, .. } => {
176 if failure.kind == strop_core::worker::FailureKind::Protocol {
177 if let Some(query) = &glue.query {
178 glue.query_highlights
179 .push(strop_picker::query::HighlightSpan {
180 range: query.content_range(),
181 role: strop_picker::query::Role::Error,
182 });
183 }
184 }
185 glue.picker.error = Some(failure.message);
186 glue.accept_when_ranked = false;
187 }
188 Outcome::Cancelled(_) => glue.accept_when_ranked = false,
189 }
190 }
191 RankingEvent::Stopped => {
192 glue.rank_alive = false;
193 if glue.rank_pending.take().is_some() {
194 glue.picker.error = Some("picker ranking worker stopped".into());
195 glue.revoke(strop_core::worker::CancelReason::OwnerClosed);
196 glue.picker.streaming = false;
197 }
198 }
199 }
200 self.finish_search_refresh();
201 self.finish_pending_picker_accept();
202 }
203}