1use gpui::{App, Global, SharedString};
10use ui::menu::{self, Item};
11
12use markdown::Cursor;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Mention {
17 pub label: SharedString,
18 pub description: Option<SharedString>,
19 pub url: String,
21}
22
23pub type MentionSource = fn(query: &str, cx: &App) -> Vec<Mention>;
25
26pub(crate) struct Installed(pub MentionSource);
27
28impl Global for Installed {}
29
30pub struct MentionMenu {
33 pub at: Cursor,
35 query: String,
36 asked: Option<String>,
38 rows: Vec<Mention>,
39 pub cursor: menu::Cursor,
40}
41
42impl MentionMenu {
43 pub fn open(at: Cursor) -> Self {
44 Self {
45 at,
46 query: String::new(),
47 asked: None,
48 rows: Vec::new(),
49 cursor: menu::Cursor::default(),
50 }
51 }
52
53 pub fn set_query(&mut self, query: String) {
54 self.query = query;
55 }
56
57 pub fn refresh(&mut self, cx: &App) {
59 if self.asked.as_ref() == Some(&self.query) {
60 return;
61 }
62 self.rows = cx
63 .try_global::<Installed>()
64 .map(|Installed(source)| source(&self.query, cx))
65 .unwrap_or_default();
66 self.asked = Some(self.query.clone());
67 self.cursor.clear();
68 self.cursor.step(&self.menu(), 1);
69 }
70
71 pub fn menu(&self) -> Vec<Item> {
73 self.rows
74 .iter()
75 .map(|row| {
76 let item = Item::action(row.label.clone());
77 match &row.description {
78 Some(description) => item.with_description(description.clone()),
79 None => item,
80 }
81 })
82 .collect()
83 }
84
85 pub fn step(&mut self, delta: isize) {
86 let menu = self.menu();
87 self.cursor.step(&menu, delta);
88 }
89
90 pub fn row(&self, ix: Option<usize>) -> Option<&Mention> {
92 self.rows.get(ix.or(self.cursor.row())?)
93 }
94}