1use super::*;
4
5pub struct SourceText {
14 code: Arc<str>,
15 ranges: Rc<[Range<usize>]>,
16 hashes: Rc<[u64]>,
17 spans: Option<Rc<[Span]>>,
18 generation: u64,
21 highlighter: u64,
23 sized: RefCell<Option<Sized>>,
24}
25
26#[derive(Clone)]
28struct Sized {
29 at: (u32, Pixels, Pixels),
31 keys: Rc<[u64]>,
32 gaps: Rc<[Pixels]>,
33 guesses: Rc<[Guess]>,
34}
35
36pub struct SpanJob {
39 code: Arc<str>,
40 highlighter: Option<crate::Highlighter>,
41 generation: u64,
42}
43
44pub struct Spans {
46 spans: Option<Vec<Span>>,
47 generation: u64,
48}
49
50impl SpanJob {
51 pub fn run(self) -> Spans {
52 Spans {
53 spans: highlight_source(self.highlighter, &self.code),
54 generation: self.generation,
55 }
56 }
57}
58
59fn highlight_source(highlighter: Option<crate::Highlighter>, code: &str) -> Option<Vec<Span>> {
60 let language = crate::source::LANGUAGES[0];
61 highlighter
62 .and_then(|highlighter| highlighter(language, code))
63 .or_else(|| crate::source::is_markdown(language).then(|| crate::source::spans(code)))
64}
65
66impl SourceText {
67 pub fn new(code: impl Into<Arc<str>>, cx: &App) -> Self {
68 let code: Arc<str> = code.into();
69 let spans = highlight_source(crate::highlight::installed(cx), &code).map(Into::into);
70 let ranges = split_lines(&code, 0..code.len());
71 let hashes = ranges.iter().map(|range| hash_line(&code, range)).collect();
72 Self {
73 code,
74 ranges: ranges.into(),
75 hashes,
76 spans,
77 generation: 0,
78 highlighter: crate::highlight::generation(cx),
79 sized: RefCell::new(None),
80 }
81 }
82
83 pub fn text(&self) -> &str {
84 &self.code
85 }
86
87 pub fn lines(&self) -> usize {
89 self.ranges.len()
90 }
91
92 pub fn line_of(&self, offset: usize) -> usize {
94 self.ranges
95 .partition_point(|range| range.end < offset)
96 .min(self.ranges.len() - 1)
97 }
98
99 pub fn spans(&self) -> Option<&[(Range<usize>, theme::HighlightKind)]> {
103 self.spans.as_deref()
104 }
105
106 pub fn edit(&mut self, code: &str, cx: &App) -> Option<SpanJob> {
110 let old = self.code.as_bytes();
111 let new = code.as_bytes();
112 let prefix = old.iter().zip(new).take_while(|(a, b)| a == b).count();
113 if prefix == old.len() && prefix == new.len() {
114 return None;
115 }
116 let room = old.len().min(new.len()) - prefix;
117 let suffix = old
118 .iter()
119 .rev()
120 .zip(new.iter().rev())
121 .take(room)
122 .take_while(|(a, b)| a == b)
123 .count();
124 let old_end = old.len() - suffix;
125 let delta = (new.len() - suffix) as isize - old_end as isize;
126 let shift = |at: usize| at.wrapping_add_signed(delta);
127
128 let first = self.line_of(prefix);
129 let last = self.line_of(old_end);
130 let code: Arc<str> = code.into();
131 let lines = split_lines(
132 &code,
133 self.ranges[first].start..shift(self.ranges[last].end),
134 );
135 let hashes: Vec<u64> = lines.iter().map(|range| hash_line(&code, range)).collect();
136 let cut = first..last + 1;
137
138 if let Some(sized) = self.sized.get_mut() {
139 let (size, line, indent) = sized.at;
140 sized.keys = splice(
141 &sized.keys,
142 cut.clone(),
143 hashes.iter().map(|hash| key(*hash, size)),
144 );
145 sized.guesses = splice(
146 &sized.guesses,
147 cut.clone(),
148 lines.iter().map(|range| guess(range, line, indent)),
149 );
150 sized.gaps = vec![px(0.0); sized.keys.len()].into();
151 }
152 self.hashes = splice(&self.hashes, cut.clone(), hashes);
153 self.ranges = self.ranges[..first]
154 .iter()
155 .cloned()
156 .chain(lines)
157 .chain(
158 self.ranges[last + 1..]
159 .iter()
160 .map(|range| shift(range.start)..shift(range.end)),
161 )
162 .collect();
163 self.spans = self.spans.as_ref().map(|spans| {
164 let mut kept = Vec::with_capacity(spans.len());
165 for (range, kind) in spans.iter() {
166 if range.start < prefix {
167 kept.push((range.start..range.end.min(prefix), *kind));
168 }
169 if range.end > old_end {
170 kept.push((shift(range.start.max(old_end))..shift(range.end), *kind));
171 }
172 }
173 kept.into()
174 });
175 self.code = code;
176 Some(self.span_job(cx))
177 }
178
179 pub fn rehighlight(&mut self, cx: &App) -> Option<SpanJob> {
182 (self.highlighter != crate::highlight::generation(cx)).then(|| self.span_job(cx))
183 }
184
185 fn span_job(&mut self, cx: &App) -> SpanJob {
186 self.generation += 1;
187 self.highlighter = crate::highlight::generation(cx);
188 SpanJob {
189 code: self.code.clone(),
190 highlighter: crate::highlight::installed(cx),
191 generation: self.generation,
192 }
193 }
194
195 pub fn set_spans(&mut self, spans: Spans) -> bool {
198 if spans.generation != self.generation {
199 return false;
200 }
201 self.spans = spans.spans.map(Into::into);
202 true
203 }
204
205 fn sized(&self, size: f32, line: Pixels, indent: Pixels) -> Sized {
206 let at = (size.to_bits(), line, indent);
207 let mut held = self.sized.borrow_mut();
208 if let Some(sized) = held.as_ref()
209 && sized.at == at
210 {
211 return sized.clone();
212 }
213 let sized = Sized {
214 at,
215 keys: self.hashes.iter().map(|hash| key(*hash, at.0)).collect(),
216 gaps: vec![px(0.0); self.ranges.len()].into(),
217 guesses: self
218 .ranges
219 .iter()
220 .map(|range| guess(range, line, indent))
221 .collect(),
222 };
223 *held = Some(sized.clone());
224 sized
225 }
226}
227
228fn split_lines(code: &str, within: Range<usize>) -> Vec<Range<usize>> {
231 let mut ranges = Vec::new();
232 let mut at = within.start;
233 for line in code[within].split('\n') {
234 ranges.push(at..at + line.len());
235 at += line.len() + 1;
236 }
237 ranges
238}
239
240fn hash_line(code: &str, range: &Range<usize>) -> u64 {
241 let mut hasher = DefaultHasher::new();
242 "source".hash(&mut hasher);
243 code[range.clone()].hash(&mut hasher);
244 hasher.finish()
245}
246
247fn key(hash: u64, size: u32) -> u64 {
248 let mut hasher = DefaultHasher::new();
249 hash.hash(&mut hasher);
250 size.hash(&mut hasher);
251 hasher.finish()
252}
253
254fn guess(range: &Range<usize>, line: Pixels, indent: Pixels) -> Guess {
255 Guess {
256 chars: range.len(),
257 line,
258 rows: 0,
259 extra: px(0.0),
260 indent,
261 }
262}
263
264fn splice<T: Clone>(old: &[T], cut: Range<usize>, new: impl IntoIterator<Item = T>) -> Rc<[T]> {
266 old[..cut.start]
267 .iter()
268 .cloned()
269 .chain(new)
270 .chain(old[cut.end..].iter().cloned())
271 .collect()
272}
273
274pub fn render_source(source: &SourceText, editing: Editing, cx: &mut App) -> AnyElement {
284 let code: &str = &source.code;
285 let Editing {
286 selection,
287 caret_on,
288 layouts,
289 annotations,
290 typography,
291 keep,
292 scroll,
293 ..
294 } = editing;
295 let mut theme = Theme::of(cx).clone();
296 theme.font_mono = theme.font_body.clone();
297 let mut typography = typography.unwrap_or_else(|| cx.typography());
298 typography.code = typography.body;
299 let overlay = Overlay {
300 block: 0,
301 part: Part::Code,
302 selection,
303 caret_on,
304 caret_shape: cx.caret_shape(),
305 caret_height: cx.caret_height(),
306 caret_inactive: cx.inactive_caret(),
307 window_active: cx.active_window().is_some(),
309 layouts,
310 annotations,
311 placeholder: None,
312 caption: Caption::default(),
313 toggle: None,
315 image: None,
316 image_menu: None,
317 image_overlay: None,
318 image_overlay_corner: ImageOverlayCorner::BottomRight,
319 fence: None,
320 sizing: None,
321 table_controls: false,
322 copy: CopyButton::Hidden,
324 base: None,
325 highlight: crate::marks::highlight_paint_of(cx),
326 find: crate::find::find_paint_of(cx),
327 comment: crate::comment::comment_paint_of(cx),
328 jump: None,
329 };
330 let style = cx.source_style();
331 let count = source.ranges.len();
332 let gutter = Gutter::new(&style, count, &typography, &theme);
333
334 let Some(layouts) = layouts else {
335 let (underlay, over, lines) = code_lines(
336 Some(crate::source::LANGUAGES[0]),
337 code,
338 overlay,
339 &typography,
340 &theme,
341 cx,
342 );
343 let lines = lines
344 .into_iter()
345 .enumerate()
346 .map(|(index, line)| gutter.row(index, line))
347 .collect();
348 return div()
349 .flex()
350 .flex_col()
351 .child(code_body(0, underlay, over, lines, &typography, true))
352 .into_any_element();
353 };
354
355 let spans = source.spans.clone();
358 let ranges = source.ranges.clone();
359 let mut kept: Vec<usize> = keep.to_vec();
360 if let Some(selection) = selection {
361 kept.push(source.line_of(selection.head.offset));
362 kept.push(source.line_of(selection.anchor.offset));
363 }
364 let line = px(typography.code.line_height());
365 let indent = px(gutter.width() + 2.0 * CODE_PADDING_X);
366 let sized = source.sized(typography.code.size(), line, indent);
367 layouts.prune(&sized.keys);
368 let paint = RowPaint::new(&overlay, code, typography.code.size(), &theme);
369 let code = source.code.clone();
370 let sink = layouts.clone();
371 let column = Column {
372 layouts: layouts.clone(),
373 keys: sized.keys,
374 gaps: sized.gaps,
375 guesses: sized.guesses,
376 keep: kept,
377 scroll: scroll.cloned(),
378 item_of: {
379 let ranges = ranges.clone();
380 Rc::new(move |at| ranges.partition_point(|line| line.end < at.offset))
381 },
382 build: Box::new(move |index, window, _| {
383 let span = ranges[index].clone();
384 let paint = RowPaint {
385 hollow: !window.is_window_active(),
386 ..paint.clone()
387 };
388 let (styled, runs) =
389 code_line(&code[span.clone()], span.start, spans.as_deref(), &theme);
390 let layout = styled.layout().clone();
391 let sink = sink.clone();
392 let over = {
393 let (span, layout, paint) = (span.clone(), layout.clone(), paint.clone());
394 canvas(
395 |_, _, _| (),
396 move |_, _, window, _| paint.paint_over(&span, &layout, &runs, window),
397 )
398 .absolute()
399 .size_full()
400 };
401 let underlay = canvas(
402 |_, _, _| (),
403 move |_, _, window, _| {
404 sink.record(
405 0,
406 Part::Code,
407 span.clone(),
408 layout.clone(),
409 Shown::default(),
410 );
411 paint.paint_under(&span, &layout, window);
412 },
413 )
414 .absolute()
415 .size_full();
416 let text = div().relative().child(underlay).child(styled).child(over);
417 div()
418 .px(px(CODE_PADDING_X))
419 .child(gutter.row(index, text.into_any_element()))
420 .into_any_element()
421 }),
422 };
423 div()
424 .flex()
425 .flex_col()
426 .py(px(CODE_PADDING_Y))
427 .text_size(px(typography.code.size()))
428 .line_height(line)
429 .child(column)
430 .into_any_element()
431}
432
433#[derive(Clone)]
435struct Gutter {
436 shown: bool,
437 width: f32,
438 gap: f32,
439 color: Hsla,
440 font: SharedString,
441}
442
443impl Gutter {
444 fn new(
445 style: &crate::SourceStyle,
446 lines: usize,
447 typography: &Typography,
448 theme: &Theme,
449 ) -> Self {
450 let digits = lines.to_string().len().max(style.gutter_min_digits);
451 let gap = style.gutter_gap.max(0.0) * typography.code.size();
452 Self {
453 shown: style.line_numbers,
454 width: digits as f32 * typography.code.size() + gap,
455 gap,
456 color: style.gutter_color.unwrap_or(theme.text_faint),
457 font: theme.font_mono.clone(),
458 }
459 }
460
461 fn width(&self) -> f32 {
462 if self.shown { self.width } else { 0.0 }
463 }
464
465 fn row(&self, index: usize, line: AnyElement) -> AnyElement {
468 if !self.shown {
469 return line;
470 }
471 div()
472 .flex()
473 .items_start()
474 .child(
475 div()
476 .w(px(self.width))
477 .flex_shrink_0()
478 .pr(px(self.gap))
479 .font_family(self.font.clone())
480 .text_color(self.color)
481 .text_right()
482 .child((index + 1).to_string()),
483 )
484 .child(div().flex_1().min_w_0().child(line))
485 .into_any_element()
486 }
487}
488
489pub(super) fn code_lines(
492 language: Option<&str>,
493 code: &str,
494 overlay: Overlay,
495 typography: &Typography,
496 theme: &Theme,
497 cx: &App,
498) -> (AnyElement, AnyElement, Vec<AnyElement>) {
499 let ix = overlay.block;
500 let spans = crate::highlight::spans(cx, language, code).or_else(|| {
505 language
506 .filter(|language| crate::source::is_markdown(language))
507 .map(|_| crate::source::spans(code))
508 });
509 let paint = RowPaint::new(&overlay, code, typography.code.size(), theme);
514 let mut rows: Vec<(Range<usize>, TextLayout, Vec<TextRun>)> = Vec::new();
515 let mut offset = 0usize;
516 let lines: Vec<AnyElement> = code
517 .split('\n')
518 .map(|line| {
519 let start = offset;
520 offset += line.len() + 1;
521 let (styled, runs) = code_line(line, start, spans.as_deref(), theme);
522 rows.push((start..start + line.len(), styled.layout().clone(), runs));
523 styled.into_any_element()
524 })
525 .collect();
526
527 let sink = overlay.layouts.cloned();
528 let over = {
529 let (rows, paint) = (rows.clone(), paint.clone());
530 canvas(
531 |_, _, _| (),
532 move |_, _, window, _| {
533 for (span, layout, runs) in &rows {
534 paint.paint_over(span, layout, runs, window);
535 }
536 },
537 )
538 .absolute()
539 .size_full()
540 };
541 let underlay = canvas(
542 |_, _, _| (),
543 move |_, _, window, _| {
544 for (span, layout, _) in &rows {
545 if let Some(sink) = &sink {
546 sink.record(
547 ix,
548 Part::Code,
549 span.clone(),
550 layout.clone(),
551 Shown::default(),
552 );
553 }
554 paint.paint_under(span, layout, window);
555 }
556 },
557 )
558 .absolute()
559 .size_full();
560
561 (underlay.into_any_element(), over.into_any_element(), lines)
562}
563
564type Span = (Range<usize>, theme::HighlightKind);
566
567fn code_line(
573 line: &str,
574 start: usize,
575 spans: Option<&[Span]>,
576 theme: &Theme,
577) -> (StyledText, Vec<TextRun>) {
578 let mono = font(theme.font_mono.clone());
579 let run = |len: usize, color: Hsla| TextRun {
580 len,
581 font: mono.clone(),
582 color,
583 background_color: None,
584 underline: None,
585 strikethrough: None,
586 };
587 let mut runs = Vec::new();
588 let mut pos = 0usize;
589 if let Some(spans) = spans {
590 let end = start + line.len();
591 for (range, kind) in spans.iter().filter(|(r, _)| r.end > start && r.start < end) {
592 let s = range.start.clamp(start, end) - start;
593 let e = range.end.min(end) - start;
594 if s > pos {
595 runs.push(run(s - pos, theme.text));
596 }
597 runs.push(run(e - s, theme.syntax.color(*kind)));
598 pos = e;
599 }
600 }
601 if pos < line.len() {
602 runs.push(run(line.len() - pos, theme.text));
603 }
604 if runs.is_empty() {
605 runs.push(run(0, theme.text));
606 }
607 let styled = StyledText::new(SharedString::from(line.to_string())).with_runs(runs.clone());
608 (styled, runs)
609}
610
611#[derive(Clone)]
615struct RowPaint {
616 caret: Option<usize>,
617 glyph: Option<Range<usize>>,
619 affinity: Affinity,
620 shape: ui::input::CaretShape,
621 height: ui::input::CaretHeight,
622 hollow: bool,
623 font: SharedString,
624 selected: Option<Range<usize>>,
625 annotated: Vec<(Range<usize>, Hsla)>,
626 caret_color: Hsla,
627 cover: Hsla,
628 selection_color: Hsla,
629 page: Hsla,
631 code_size: f32,
632}
633
634impl RowPaint {
635 fn new(overlay: &Overlay, code: &str, code_size: f32, theme: &Theme) -> Self {
636 let caret = overlay.caret_painted();
637 Self {
638 caret,
639 glyph: caret.and_then(|offset| overlay.covered(code, offset)),
640 affinity: overlay.affinity(),
641 shape: overlay.caret_shape,
642 height: overlay.caret_height,
643 hollow: overlay.caret_hollow(),
644 font: theme.font_mono.clone(),
645 selected: overlay.selected(code.len()),
646 annotated: overlay.annotated(code.len(), theme),
647 caret_color: theme.caret,
648 cover: theme::legible(theme.bg, theme.caret, theme::LEGIBLE),
649 selection_color: theme.selection,
650 page: theme.bg,
651 code_size,
652 }
653 }
654
655 fn cut(&self) -> Option<&Range<usize>> {
657 self.glyph
658 .as_ref()
659 .filter(|_| self.shape.cuts_out(self.hollow))
660 }
661
662 fn paint_under(&self, span: &Range<usize>, layout: &TextLayout, window: &mut Window) {
663 let wash = |range: &Range<usize>, color: Hsla, window: &mut Window| {
664 let (from, to) = (range.start.max(span.start), range.end.min(span.end));
665 if from < to {
666 for rect in range_rects(layout, &(from - span.start..to - span.start), 0.0, 0.0) {
667 window.paint_quad(quad(
668 rect,
669 px(2.0),
670 color,
671 px(0.0),
672 gpui::transparent_black(),
673 BorderStyle::default(),
674 ));
675 }
676 }
677 };
678 for (range, color) in self.layers() {
679 wash(&range, color, window);
680 }
681 }
682
683 fn layers(&self) -> impl Iterator<Item = (Range<usize>, Hsla)> + '_ {
687 self.selected
688 .iter()
689 .map(|range| (range.clone(), self.selection_color))
690 .chain(self.annotated.iter().cloned())
691 }
692
693 fn paint_over(
695 &self,
696 span: &Range<usize>,
697 layout: &TextLayout,
698 runs: &[TextRun],
699 window: &mut Window,
700 ) {
701 let layers: Vec<_> = self
702 .layers()
703 .filter_map(|(range, color)| {
704 let (from, to) = (range.start.max(span.start), range.end.min(span.end));
705 (from < to).then(|| (from - span.start..to - span.start, color))
706 })
707 .collect();
708 ui::input::overdraw::paint(
709 layout.line_layouts().iter().map(|line| &**line),
710 layout.bounds().origin,
711 layout.line_height(),
712 px(2.0),
713 &ui::input::overdraw::overdraws(runs, self.page, &layers),
714 window,
715 );
716 if let Some(offset) = self.caret.filter(|at| span.contains(at) || *at == span.end) {
717 let glyph = self
718 .cut()
719 .map(|glyph| glyph.start - span.start..glyph.end - span.start);
720 paint_caret(
721 layout,
722 offset - span.start,
723 self.affinity,
724 glyph.as_ref(),
725 CaretPaint {
726 shape: self.shape,
727 height: self.height,
728 hollow: self.hollow,
729 color: self.caret_color,
730 cover: self.cover,
731 size: self.code_size,
732 face: font(self.font.clone()),
733 },
734 window,
735 );
736 }
737 }
738}
739
740pub(super) fn code_block(
741 language: Option<&str>,
742 code: &str,
743 overlay: Overlay,
744 typography: &Typography,
745 theme: &Theme,
746 window: &mut Window,
747 cx: &mut App,
748) -> AnyElement {
749 let ix = overlay.block;
750 let (underlay, over, lines) = code_lines(language, code, overlay, typography, theme, cx);
751 let body = code_body(
752 ix,
753 underlay,
754 over,
755 lines,
756 typography,
757 cx.markdown_layout().wrap_code,
758 );
759
760 fence_panel(theme)
761 .child(
765 fence_band(theme)
766 .text_color(match language {
767 Some(_) => theme.text_muted,
768 None => theme.text_faint,
769 })
770 .child(
774 div()
775 .relative()
776 .children(overlay.layouts.map(|layouts| {
777 let layouts = layouts.clone();
778 canvas(
779 move |bounds, _, _| layouts.record_language(ix, bounds),
780 |_, _, _, _| (),
781 )
782 .absolute()
783 .size_full()
784 }))
785 .child(SharedString::from(
786 language.unwrap_or(PLAIN_LANGUAGE).to_string(),
787 )),
788 ),
789 )
790 .child(body)
791 .children(
792 (overlay.copy == CopyButton::Shown).then(|| copy_button(code, ix, theme, window, cx)),
793 )
794 .into_any_element()
795}
796
797pub fn fence_panel(theme: &Theme) -> gpui::Div {
799 div()
800 .rounded(px(Theme::panel_radius()))
801 .bg(theme.ink(0.035))
802 .border_1()
803 .border_color(theme.border)
804 .overflow_hidden()
805 .relative()
806}
807
808pub fn fence_band(theme: &Theme) -> gpui::Div {
811 div()
812 .relative()
813 .flex()
814 .flex_row()
815 .items_center()
816 .px(px(CODE_PADDING_X))
817 .py(px(5.0))
818 .border_b_1()
819 .border_color(theme.border)
820 .bg(theme.ink(0.02))
821 .text_style(TextStyle::Subheadline)
822}
823
824pub(super) fn code_body(
826 ix: usize,
827 underlay: AnyElement,
828 over: AnyElement,
829 lines: Vec<AnyElement>,
830 typography: &Typography,
831 wrap: bool,
832) -> AnyElement {
833 let column = div()
834 .flex()
835 .flex_col()
836 .px(px(CODE_PADDING_X))
837 .children(lines);
838 let body = div()
839 .id(ElementId::named_usize("md-code", ix))
840 .relative()
841 .py(px(CODE_PADDING_Y))
842 .text_size(px(typography.code.size()))
843 .line_height(px(typography.code.line_height()))
844 .child(underlay);
845 if wrap {
846 body.child(column.w_full()).child(over).into_any_element()
849 } else {
850 ui::scroll::Viewport::new(
851 format!("md-code-scroll-{ix}"),
852 body.flex()
853 .flex_row()
854 .whitespace_nowrap()
855 .child(column.items_start())
862 .child(over),
863 gpui::Axis::Horizontal,
864 )
865 .into_any_element()
866 }
867}
868
869pub(crate) fn copy_button(
876 code: &str,
877 ix: usize,
878 theme: &Theme,
879 window: &mut Window,
880 cx: &mut App,
881) -> AnyElement {
882 let copied = window.use_keyed_state(ElementId::named_usize("md-copied", ix), cx, |_, _| false);
883 let showing = *copied.read(cx);
884 let text: SharedString = code.to_string().into();
885
886 div()
887 .id(ElementId::named_usize("md-copy", ix))
888 .absolute()
889 .top(px(3.0))
890 .right(px(5.0))
891 .h(px(20.0))
892 .px(px(6.0))
893 .rounded(px(5.0))
894 .flex()
895 .items_center()
896 .cursor_pointer()
897 .text_style(TextStyle::Caption)
898 .text_color(theme.text_muted)
899 .hover(|el| el.bg(theme.element_hover))
900 .child(if showing { "Copied" } else { "Copy" })
901 .on_click({
902 let copied = copied.clone();
903 move |_, _, cx| {
904 cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.to_string()));
905 copied.update(cx, |state, cx| {
906 *state = true;
907 cx.notify();
908 });
909 }
910 })
911 .on_hover(move |hovering, _, cx| {
912 if !*hovering && *copied.read(cx) {
913 copied.update(cx, |state, cx| {
914 *state = false;
915 cx.notify();
916 });
917 }
918 })
919 .into_any_element()
920}