1use sim_kernel::{Expr, Symbol};
10use sim_lib_intent::{Origin, intent};
11use sim_lib_scene::{node, sym};
12
13use crate::value::panes;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum EntryKind {
18 Resource,
20 Export,
22 Card,
24 Lens,
26 Command,
28 Recent,
30 Workspace,
32}
33
34impl EntryKind {
35 fn token(self) -> &'static str {
36 match self {
37 EntryKind::Resource => "resource",
38 EntryKind::Export => "export",
39 EntryKind::Card => "card",
40 EntryKind::Lens => "lens",
41 EntryKind::Command => "command",
42 EntryKind::Recent => "recent",
43 EntryKind::Workspace => "workspace",
44 }
45 }
46}
47
48#[derive(Clone, Debug)]
50pub struct PaletteEntry {
51 pub id: Symbol,
53 pub label: String,
55 pub kind: EntryKind,
57 pub target: Expr,
59}
60
61impl PaletteEntry {
62 pub fn new(id: &str, label: &str, kind: EntryKind, target: Expr) -> Self {
64 Self {
65 id: Symbol::new(id),
66 label: label.to_owned(),
67 kind,
68 target,
69 }
70 }
71}
72
73#[derive(Default)]
75pub struct Palette {
76 entries: Vec<PaletteEntry>,
77}
78
79impl Palette {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn add(&mut self, entry: PaletteEntry) {
87 self.entries.push(entry);
88 }
89
90 pub fn add_workspace_resources(&mut self, workspace: &Expr) {
92 for pane in panes(workspace) {
93 if let Some(Expr::Symbol(id)) = crate::value::get(&pane, "id") {
94 let resource = crate::value::get(&pane, "resource")
95 .cloned()
96 .unwrap_or(Expr::Nil);
97 self.add(PaletteEntry::new(
98 &id.name,
99 &id.name,
100 EntryKind::Resource,
101 resource,
102 ));
103 }
104 }
105 }
106
107 pub fn entries(&self) -> &[PaletteEntry] {
109 &self.entries
110 }
111
112 pub fn search(&self, query: &str) -> Vec<&PaletteEntry> {
114 let query = query.to_lowercase();
115 let mut scored: Vec<(i32, usize, &PaletteEntry)> = self
116 .entries
117 .iter()
118 .enumerate()
119 .filter_map(|(index, entry)| {
120 match_score(&query, &entry.label.to_lowercase()).map(|score| (score, index, entry))
121 })
122 .collect();
123 scored.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
124 scored.into_iter().map(|(_, _, entry)| entry).collect()
125 }
126
127 pub fn scene(&self, query: &str) -> Expr {
129 let results = self
130 .search(query)
131 .into_iter()
132 .map(|entry| {
133 node(
134 "button",
135 vec![
136 ("control", Expr::Symbol(entry.id.clone())),
137 (
138 "label",
139 Expr::String(format!("{} [{}]", entry.label, entry.kind.token())),
140 ),
141 ("target", entry.target.clone()),
142 ],
143 )
144 })
145 .collect();
146 node(
147 "box",
148 vec![
149 ("role", sym("palette")),
150 (
151 "children",
152 Expr::List(vec![
153 node(
154 "field",
155 vec![
156 ("datatype", sym("search")),
157 ("value", Expr::String(query.to_owned())),
158 ],
159 ),
160 node(
161 "stack",
162 vec![("dir", sym("column")), ("children", Expr::List(results))],
163 ),
164 ]),
165 ),
166 ],
167 )
168 }
169
170 pub fn open_entry(&self, entry: &PaletteEntry, pane: &str) -> Expr {
174 match entry.kind {
175 EntryKind::Lens => intent(
176 "set-lens",
177 Origin::human(0),
178 vec![("pane", sym(pane)), ("lens", entry.target.clone())],
179 ),
180 EntryKind::Command => intent(
181 "invoke",
182 Origin::human(0),
183 vec![
184 ("target", entry.target.clone()),
185 ("op", Expr::Symbol(entry.id.clone())),
186 ("args", Expr::Map(Vec::new())),
187 ],
188 ),
189 _ => intent(
190 "open",
191 Origin::human(0),
192 vec![("value", entry.target.clone()), ("pane", sym(pane))],
193 ),
194 }
195 }
196}
197
198pub fn card_target(card: &Expr) -> Option<Expr> {
200 for name in ["target", "subject", "ref", "value"] {
201 if let Some(value) = crate::value::get(card, name) {
202 return Some(value.clone());
203 }
204 }
205 None
206}
207
208pub fn open_card(card: &Expr, pane: &str) -> Option<Expr> {
211 let target = card_target(card)?;
212 Some(intent(
213 "open",
214 Origin::human(0),
215 vec![("value", target), ("pane", sym(pane))],
216 ))
217}
218
219fn match_score(query: &str, label: &str) -> Option<i32> {
221 if query.is_empty() {
222 return Some(0);
223 }
224 if let Some(index) = label.find(query) {
225 return Some(1000 - index as i32);
227 }
228 if is_subsequence(query, label) {
230 Some(100)
231 } else {
232 None
233 }
234}
235
236fn is_subsequence(query: &str, label: &str) -> bool {
237 let mut chars = label.chars();
238 query.chars().all(|needle| chars.any(|c| c == needle))
239}