1use super::{InlineToken, InlineTokenSpan, InputBaseState, InputModeKind};
3use gpui::{AnyElement, App, Bounds, ClickEvent, Font, IntoElement, Pixels, Window};
4use std::{collections::HashMap, ops::Range, rc::Rc};
5
6#[derive(Clone)]
8pub struct InlineTokenContext {
9 span: InlineTokenSpan,
10 selected: bool,
11 disabled: bool,
12 readonly: bool,
13 line_height: Pixels,
14 available_width: Pixels,
15}
16impl InlineTokenContext {
17 pub fn token(&self) -> &InlineToken {
18 self.span.token()
19 }
20 pub fn range(&self) -> Range<usize> {
21 self.span.range()
22 }
23 pub fn is_selected(&self) -> bool {
24 self.selected
25 }
26 pub fn is_disabled(&self) -> bool {
27 self.disabled
28 }
29 pub fn is_readonly(&self) -> bool {
30 self.readonly
31 }
32 pub fn line_height(&self) -> Pixels {
33 self.line_height
34 }
35 pub fn available_width(&self) -> Pixels {
36 self.available_width
37 }
38}
39
40#[derive(Clone)]
42pub struct InlineTokenClickEvent {
43 span: InlineTokenSpan,
44 bounds: Bounds<Pixels>,
45 event: ClickEvent,
46}
47impl InlineTokenClickEvent {
48 pub fn token(&self) -> &InlineToken {
49 self.span.token()
50 }
51 pub fn range(&self) -> Range<usize> {
52 self.span.range()
53 }
54 pub fn bounds(&self) -> Bounds<Pixels> {
55 self.bounds
56 }
57 pub fn click(&self) -> &ClickEvent {
60 &self.event
61 }
62}
63
64#[doc(hidden)]
66pub type InlineTokenRenderer = Rc<dyn Fn(&InlineTokenContext, &mut Window, &mut App) -> AnyElement>;
67#[doc(hidden)]
69pub type InlineTokenClickListener = Rc<dyn Fn(&InlineTokenClickEvent, &mut Window, &mut App)>;
70
71#[derive(Clone, Default)]
73pub(crate) struct InlineTokenPresentation {
74 renderer: Option<InlineTokenRenderer>,
75 listener: Option<InlineTokenClickListener>,
76 secret: bool,
77}
78impl InlineTokenPresentation {
79 pub(crate) fn token<R: IntoElement>(
80 mut self,
81 render: impl Fn(&InlineTokenContext, &mut Window, &mut App) -> R + 'static,
82 ) -> Self {
83 self.renderer = Some(Rc::new(move |token, window, cx| {
84 render(token, window, cx).into_any_element()
85 }));
86 self
87 }
88 pub(crate) fn on_token_click(
89 mut self,
90 listener: impl Fn(&InlineTokenClickEvent, &mut Window, &mut App) + 'static,
91 ) -> Self {
92 self.listener = Some(Rc::new(listener));
93 self
94 }
95 pub(super) fn has_listener(&self) -> bool {
96 self.listener.is_some()
97 }
98 pub(super) fn render(
99 &self,
100 token: &InlineTokenContext,
101 window: &mut Window,
102 cx: &mut App,
103 ) -> AnyElement {
104 if let Some(render) = &self.renderer {
105 render(token, window, cx)
106 } else {
107 gpui::div()
108 .child(token.token().label().clone())
109 .into_any_element()
110 }
111 }
112}
113use gpui::ParentElement as _;
114
115#[derive(Default)]
116pub(super) struct TokenLayoutCache {
117 pub(super) key: Option<(Font, Pixels, Pixels, Pixels, bool)>,
118 pub(super) revision: u64,
119 pub(super) unwrapped_width: Pixels,
120 pub(super) metrics: Rc<[(Range<usize>, Pixels)]>,
121 pub(super) widths: HashMap<InlineToken, Pixels>,
122}
123
124impl<M: InputModeKind> InputBaseState<M> {
125 pub(crate) fn set_token_presentation(&mut self, presentation: InlineTokenPresentation) {
127 self.token_presentation = presentation;
128 }
129 #[doc(hidden)]
132 pub fn install_token_presentation(
133 &mut self,
134 renderer: Option<InlineTokenRenderer>,
135 listener: Option<InlineTokenClickListener>,
136 secret: bool,
137 ) {
138 self.token_presentation = InlineTokenPresentation {
139 renderer,
140 listener,
141 secret,
142 };
143 }
144 pub(super) fn tokens_visible(&self) -> bool {
145 !self.masked
146 && !self.token_presentation.secret
147 && self.mask_pattern.is_none()
148 && !self.token_spans().is_empty()
149 }
150 pub(super) fn token_context(
151 &self,
152 span: &InlineTokenSpan,
153 line_height: Pixels,
154 width: Pixels,
155 ) -> InlineTokenContext {
156 let range = span.range();
157 let selection = self.selected_range();
158 InlineTokenContext {
159 span: span.clone(),
160 selected: selection.start < range.end && range.start < selection.end,
161 disabled: self.disabled,
162 readonly: self.readonly,
163 line_height,
164 available_width: width,
165 }
166 }
167 pub(super) fn token_activation(
169 &self,
170 start: usize,
171 bounds: Bounds<Pixels>,
172 event: ClickEvent,
173 ) -> Option<(InlineTokenClickListener, InlineTokenClickEvent)> {
174 if self.disabled || !self.tokens_visible() {
175 return None;
176 }
177 let span = self
178 .token_spans()
179 .iter()
180 .find(|span| span.range().start == start)?
181 .clone();
182 Some((
183 self.token_presentation.listener.clone()?,
184 InlineTokenClickEvent {
185 span,
186 bounds,
187 event,
188 },
189 ))
190 }
191 pub(super) fn token_is_secret(&self) -> bool {
192 self.token_presentation.secret
193 }
194}