1use gpui::{App, Global, SharedString};
11use icons::Icon;
12use ui::menu::{self, Item};
13
14use markdown::Cursor;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Mention {
19 pub icon: Option<Icon>,
20 pub label: SharedString,
21 pub description: Option<SharedString>,
22 pub url: String,
24}
25
26pub type MentionSource = fn(query: &str, cx: &App) -> Vec<Mention>;
29
30#[derive(Default)]
32pub(crate) struct Installed(pub Vec<(char, MentionSource)>);
33
34impl Global for Installed {}
35
36impl Installed {
37 pub(crate) fn source(cx: &App, trigger: &str) -> Option<(char, MentionSource)> {
38 let mut chars = trigger.chars();
39 let trigger = chars.next().filter(|_| chars.next().is_none())?;
40 cx.try_global::<Self>()?
41 .0
42 .iter()
43 .find(|(installed, _)| *installed == trigger)
44 .copied()
45 }
46}
47
48pub struct MentionMenu {
51 pub at: Cursor,
53 pub trigger: char,
54 source: MentionSource,
55 query: String,
56 asked: Option<String>,
58 rows: Vec<Mention>,
59 pub cursor: menu::Cursor,
60}
61
62impl MentionMenu {
63 pub fn open(at: Cursor, (trigger, source): (char, MentionSource)) -> Self {
64 Self {
65 at,
66 trigger,
67 source,
68 query: String::new(),
69 asked: None,
70 rows: Vec::new(),
71 cursor: menu::Cursor::default(),
72 }
73 }
74
75 pub fn set_query(&mut self, query: String) {
76 self.query = query;
77 }
78
79 pub fn refresh(&mut self, cx: &App) {
81 if self.asked.as_ref() == Some(&self.query) {
82 return;
83 }
84 self.rows = (self.source)(&self.query, cx);
85 self.asked = Some(self.query.clone());
86 self.cursor.clear();
87 self.cursor.step(&self.menu(), 1);
88 }
89
90 pub fn menu(&self) -> Vec<Item> {
92 self.rows
93 .iter()
94 .map(|row| {
95 let mut item = Item::action(row.label.clone());
96 if let Some(icon) = &row.icon {
97 item = item.with_icon(icon.clone());
98 }
99 match &row.description {
100 Some(description) => item.with_description(description.clone()),
101 None => item,
102 }
103 })
104 .collect()
105 }
106
107 pub fn step(&mut self, delta: isize) {
108 let menu = self.menu();
109 self.cursor.step(&menu, delta);
110 }
111
112 pub fn row(&self, ix: Option<usize>) -> Option<&Mention> {
114 self.rows.get(ix.or(self.cursor.row())?)
115 }
116}