1use super::{InlineToken, InlineTokenSpan, InputBaseState, InputModeKind, rope_ext::RopeExt as _};
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#[derive(Clone)]
68pub struct InlineTokenHoverEvent {
69 span: InlineTokenSpan,
70 bounds: Bounds<Pixels>,
71 hovered: bool,
72 range_utf16: (usize, usize),
73}
74impl InlineTokenHoverEvent {
75 pub fn token(&self) -> &InlineToken {
76 self.span.token()
77 }
78 pub fn range(&self) -> Range<usize> {
79 self.span.range()
80 }
81 pub fn bounds(&self) -> Bounds<Pixels> {
82 self.bounds
83 }
84 pub fn is_hovered(&self) -> bool {
86 self.hovered
87 }
88 pub fn range_utf16(&self) -> (usize, usize) {
93 self.range_utf16
94 }
95}
96
97#[derive(Clone)]
102pub(super) struct HoverSnapshot {
103 pub(super) span: InlineTokenSpan,
104 pub(super) bounds: Bounds<Pixels>,
105 pub(super) range_utf16: (usize, usize),
106}
107
108#[doc(hidden)]
110pub type InlineTokenRenderer = Rc<dyn Fn(&InlineTokenContext, &mut Window, &mut App) -> AnyElement>;
111#[doc(hidden)]
113pub type InlineTokenClickListener = Rc<dyn Fn(&InlineTokenClickEvent, &mut Window, &mut App)>;
114#[doc(hidden)]
116pub type InlineTokenHoverListener = Rc<dyn Fn(&InlineTokenHoverEvent, &mut Window, &mut App)>;
117
118#[derive(Clone, Default)]
120pub(crate) struct InlineTokenPresentation {
121 renderer: Option<InlineTokenRenderer>,
122 listener: Option<InlineTokenClickListener>,
123 hover_listener: Option<InlineTokenHoverListener>,
124 secret: bool,
125}
126impl InlineTokenPresentation {
127 pub(crate) fn token<R: IntoElement>(
128 mut self,
129 render: impl Fn(&InlineTokenContext, &mut Window, &mut App) -> R + 'static,
130 ) -> Self {
131 self.renderer = Some(Rc::new(move |token, window, cx| {
132 render(token, window, cx).into_any_element()
133 }));
134 self
135 }
136 pub(crate) fn on_token_click(
137 mut self,
138 listener: impl Fn(&InlineTokenClickEvent, &mut Window, &mut App) + 'static,
139 ) -> Self {
140 self.listener = Some(Rc::new(listener));
141 self
142 }
143 pub(crate) fn on_token_hover(
144 mut self,
145 listener: impl Fn(&InlineTokenHoverEvent, &mut Window, &mut App) + 'static,
146 ) -> Self {
147 self.hover_listener = Some(Rc::new(listener));
148 self
149 }
150 pub(super) fn has_hover_listener(&self) -> bool {
151 self.hover_listener.is_some()
152 }
153 pub(super) fn has_listener(&self) -> bool {
154 self.listener.is_some()
155 }
156 pub(super) fn render(
157 &self,
158 token: &InlineTokenContext,
159 window: &mut Window,
160 cx: &mut App,
161 ) -> AnyElement {
162 if let Some(render) = &self.renderer {
163 render(token, window, cx)
164 } else {
165 gpui::div()
166 .child(token.token().label().clone())
167 .into_any_element()
168 }
169 }
170}
171use gpui::ParentElement as _;
172
173#[derive(Default)]
174pub(super) struct TokenLayoutCache {
175 pub(super) key: Option<(Font, Pixels, Pixels, Pixels, bool)>,
176 pub(super) revision: u64,
177 pub(super) unwrapped_width: Pixels,
178 pub(super) metrics: Rc<[(Range<usize>, Pixels)]>,
179 pub(super) widths: HashMap<InlineToken, Pixels>,
180}
181
182impl<M: InputModeKind> InputBaseState<M> {
183 pub(crate) fn set_token_presentation(&mut self, presentation: InlineTokenPresentation) {
185 self.token_presentation = presentation;
186 }
187 #[doc(hidden)]
190 pub fn install_token_presentation(
191 &mut self,
192 renderer: Option<InlineTokenRenderer>,
193 listener: Option<InlineTokenClickListener>,
194 secret: bool,
195 ) {
196 self.token_presentation = InlineTokenPresentation {
197 renderer,
198 listener,
199 hover_listener: self.token_presentation.hover_listener.clone(),
200 secret,
201 };
202 }
203 #[doc(hidden)]
207 pub fn install_token_hover_presentation(
208 &mut self,
209 hover_listener: Option<InlineTokenHoverListener>,
210 ) {
211 self.token_presentation.hover_listener = hover_listener;
212 }
213 pub(super) fn tokens_visible(&self) -> bool {
214 !self.masked
215 && !self.token_presentation.secret
216 && self.mask_pattern.is_none()
217 && !self.token_spans().is_empty()
218 }
219 pub(super) fn token_context(
220 &self,
221 span: &InlineTokenSpan,
222 line_height: Pixels,
223 width: Pixels,
224 ) -> InlineTokenContext {
225 let range = span.range();
226 let selection = self.selected_range();
227 InlineTokenContext {
228 span: span.clone(),
229 selected: selection.start < range.end && range.start < selection.end,
230 disabled: self.disabled,
231 readonly: self.readonly,
232 line_height,
233 available_width: width,
234 }
235 }
236 pub(super) fn token_activation(
238 &self,
239 start: usize,
240 bounds: Bounds<Pixels>,
241 event: ClickEvent,
242 ) -> Option<(InlineTokenClickListener, InlineTokenClickEvent)> {
243 if self.disabled || !self.tokens_visible() {
244 return None;
245 }
246 let span = self
247 .token_spans()
248 .iter()
249 .find(|span| span.range().start == start)?
250 .clone();
251 Some((
252 self.token_presentation.listener.clone()?,
253 InlineTokenClickEvent {
254 span,
255 bounds,
256 event,
257 },
258 ))
259 }
260 pub(super) fn token_hover(
269 &mut self,
270 start: usize,
271 bounds: Bounds<Pixels>,
272 hovered: bool,
273 expected_token: Option<&InlineToken>,
274 ) -> Option<(InlineTokenHoverListener, InlineTokenHoverEvent)> {
275 let listener = self.token_presentation.hover_listener.clone()?;
276 let matches = |snapshot: &HoverSnapshot| {
277 snapshot.span.range().start == start
278 && expected_token.is_none_or(|token| snapshot.span.token() == token)
279 };
280 let snapshot = if hovered {
281 if self.disabled || !self.tokens_visible() {
282 return None;
283 }
284 let span = self
285 .token_spans()
286 .iter()
287 .find(|span| {
288 span.range().start == start
289 && expected_token.is_none_or(|token| span.token() == token)
290 })?
291 .clone();
292 if self
293 .hovered_token
294 .as_ref()
295 .is_some_and(|snapshot| snapshot.span == span)
296 {
297 return None;
298 }
299 let range = span.range();
300 let snapshot = HoverSnapshot {
301 span,
302 bounds,
303 range_utf16: (
304 self.text.offset_to_offset_utf16(range.start),
305 self.text.offset_to_offset_utf16(range.end),
306 ),
307 };
308 if let Some(previous) = self.hovered_token.replace(snapshot.clone()) {
309 self.pending_token_hover_exits.push(previous);
310 }
311 snapshot
312 } else if self.hovered_token.as_ref().is_some_and(matches) {
313 self.hovered_token.take()?
314 } else {
315 let index = self.pending_token_hover_exits.iter().position(matches)?;
316 self.pending_token_hover_exits.remove(index)
317 };
318 Some((
319 listener,
320 InlineTokenHoverEvent {
321 span: snapshot.span,
322 bounds: snapshot.bounds,
323 hovered,
324 range_utf16: snapshot.range_utf16,
325 },
326 ))
327 }
328 pub(super) fn reconcile_token_hover(
331 &mut self,
332 ) -> Option<(InlineTokenHoverListener, InlineTokenHoverEvent)> {
333 let is_placed = |snapshot: &HoverSnapshot| {
334 !self.disabled
335 && self.tokens_visible()
336 && self.token_presentation.has_hover_listener()
337 && self.token_bounds.contains_key(&snapshot.span.range().start)
338 && self.token_spans().contains(&snapshot.span)
339 };
340 let snapshot = if let Some(index) = self
341 .pending_token_hover_exits
342 .iter()
343 .position(|snapshot| !is_placed(snapshot))
344 {
345 self.pending_token_hover_exits.remove(index)
346 } else if self
347 .hovered_token
348 .as_ref()
349 .is_some_and(|snapshot| !is_placed(snapshot))
350 {
351 self.hovered_token.take()?
352 } else {
353 return None;
354 };
355 Some((
356 self.token_presentation.hover_listener.clone()?,
357 InlineTokenHoverEvent {
358 span: snapshot.span,
359 bounds: snapshot.bounds,
360 hovered: false,
361 range_utf16: snapshot.range_utf16,
362 },
363 ))
364 }
365 pub(super) fn token_is_secret(&self) -> bool {
366 self.token_presentation.secret
367 }
368}