1#[cfg(not(target_family = "wasm"))]
12use std::time::Instant;
13use std::{ops::Range, sync::Arc, time::Duration};
14#[cfg(target_family = "wasm")]
15use web_time::Instant;
16
17use gpui::{ElementId, SharedString};
18
19use super::{
20 document::ParsedDocument,
21 node::{BlockNode, InlineNode, Paragraph},
22};
23use crate::motion::{Easing, Timing};
24
25#[derive(Clone, Debug)]
28pub struct TextViewMotion {
29 stream_fade: Duration,
30 stream_fade_stagger: Duration,
31 stream_fade_easing: Easing,
32}
33
34impl Default for TextViewMotion {
35 fn default() -> Self {
36 Self {
37 stream_fade: Duration::ZERO,
38 stream_fade_stagger: Duration::ZERO,
39 stream_fade_easing: Easing::default(),
40 }
41 }
42}
43
44impl TextViewMotion {
45 pub fn with_stream_fade(mut self, duration: Duration) -> Self {
47 self.stream_fade = duration;
48 self
49 }
50
51 pub fn with_stream_fade_stagger(mut self, stagger: Duration) -> Self {
55 self.stream_fade_stagger = stagger;
56 self
57 }
58
59 pub fn with_stream_fade_easing(mut self, easing: Easing) -> Self {
61 self.stream_fade_easing = easing;
62 self
63 }
64
65 pub fn stream_fade(&self) -> Duration {
66 self.stream_fade
67 }
68
69 pub fn stream_fade_stagger(&self) -> Duration {
70 self.stream_fade_stagger
71 }
72
73 pub fn stream_fade_easing(&self) -> &Easing {
74 &self.stream_fade_easing
75 }
76
77 fn stagger_step(&self, words: usize) -> Duration {
87 if words < 2 {
88 return Duration::ZERO;
89 }
90 let span = self.stream_fade_stagger.as_nanos() * (words as u128 - 1);
91 if span > self.stream_fade.as_nanos() {
92 return Duration::ZERO;
93 }
94 self.stream_fade_stagger
95 }
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
103pub(crate) struct TextLeafKey {
104 block_start: usize,
105 ordinal: usize,
106}
107
108impl From<TextLeafKey> for ElementId {
112 fn from(key: TextLeafKey) -> Self {
113 let mut bytes = [0; 20];
114 bytes[..8].copy_from_slice(&(key.block_start as u64).to_le_bytes());
115 bytes[8..16].copy_from_slice(&(key.ordinal as u64).to_le_bytes());
116 ElementId::OpaqueId(bytes)
117 }
118}
119
120impl TextLeafKey {
121 pub(crate) fn block(start: usize) -> Self {
122 Self {
123 block_start: start,
124 ordinal: 0,
125 }
126 }
127
128 pub(crate) fn table_cell(table_start: usize, ordinal: usize) -> Self {
129 Self {
130 block_start: table_start,
131 ordinal: ordinal + 1,
132 }
133 }
134
135 pub(crate) fn block_start(&self) -> usize {
136 self.block_start
137 }
138
139 pub(crate) fn cell_ix(&self) -> Option<usize> {
142 self.ordinal.checked_sub(1)
143 }
144
145 pub(crate) fn moved_to(self, block_start: usize) -> Self {
147 Self {
148 block_start,
149 ordinal: self.ordinal,
150 }
151 }
152}
153
154pub(crate) type FadeRanges = Vec<(Range<usize>, f32)>;
157
158#[derive(Debug, Default)]
161pub(crate) struct StreamFadeFrame {
162 leaves: Vec<(TextLeafKey, FadeRanges)>,
163}
164
165impl StreamFadeFrame {
166 pub(crate) fn fades(&self, key: TextLeafKey) -> Option<&[(Range<usize>, f32)]> {
167 self.leaves
168 .iter()
169 .find(|(leaf, _)| *leaf == key)
170 .map(|(_, fades)| fades.as_slice())
171 }
172}
173
174struct FadeSegment {
175 range: Range<usize>,
176 started_at: Instant,
177}
178
179#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
180enum PendingUpdate {
181 #[default]
182 None,
183 Extend {
186 origin: usize,
187 },
188 Replace,
189}
190
191#[derive(Default)]
193pub(super) struct StreamFadeTracker {
194 motion: TextViewMotion,
195 pending: PendingUpdate,
196 segments: Vec<(TextLeafKey, Vec<FadeSegment>)>,
197}
198
199impl StreamFadeTracker {
200 pub(super) fn set_motion(&mut self, motion: TextViewMotion) {
201 if motion.stream_fade.is_zero() {
202 self.segments.clear();
203 self.pending = PendingUpdate::None;
204 }
205 self.motion = motion;
206 }
207
208 pub(super) fn is_enabled(&self) -> bool {
209 !self.motion.stream_fade.is_zero()
210 }
211
212 pub(super) fn note_extend(&mut self, len: usize) {
214 if !self.is_enabled() {
215 return;
216 }
217 self.pending = match self.pending {
218 PendingUpdate::None => PendingUpdate::Extend { origin: len },
219 PendingUpdate::Extend { origin } => PendingUpdate::Extend {
220 origin: origin.min(len),
221 },
222 PendingUpdate::Replace => PendingUpdate::Replace,
223 };
224 }
225
226 pub(super) fn note_replace(&mut self) {
228 if self.is_enabled() {
229 self.pending = PendingUpdate::Replace;
230 }
231 }
232
233 pub(super) fn discard_pending(&mut self) {
235 self.pending = PendingUpdate::None;
236 }
237
238 pub(super) fn record(&mut self, old: &ParsedDocument, new: &ParsedDocument, now: Instant) {
245 let pending = std::mem::take(&mut self.pending);
246 if !self.is_enabled() {
247 self.segments.clear();
248 return;
249 }
250 let origin = match pending {
251 PendingUpdate::None => return,
252 PendingUpdate::Replace => {
253 self.segments.clear();
254 return;
255 }
256 PendingUpdate::Extend { origin } => origin,
257 };
258
259 let mut affected = Vec::new();
260 for block in new.blocks.iter().rev() {
261 if block.span().is_some_and(|span| span.end <= origin) {
262 break;
263 }
264 text_leaves(block, &mut affected);
265 }
266 let Some(first_start) = affected.iter().map(|(key, _)| key.block_start).min() else {
267 return;
268 };
269
270 let mut previous = Vec::new();
271 for block in old.blocks.iter().rev() {
272 if block.span().is_some_and(|span| span.end < first_start) {
273 break;
274 }
275 text_leaves(block, &mut previous);
276 }
277
278 for (key, leaf) in affected {
279 let len = leaf.len();
280 let prefix = previous
281 .iter()
282 .find(|(previous_key, _)| *previous_key == key)
283 .map_or(0, |(_, old_leaf)| leaf.common_prefix_len(old_leaf));
284 let segments = match self.segments.iter().position(|(k, _)| *k == key) {
285 Some(ix) => &mut self.segments[ix].1,
286 None => {
287 self.segments.push((key, Vec::new()));
288 &mut self.segments.last_mut().expect("just pushed").1
289 }
290 };
291 segments.retain_mut(|segment| {
294 segment.range.end = segment.range.end.min(prefix);
295 segment.range.start < segment.range.end
296 });
297 if prefix >= len {
298 continue;
299 }
300 if self.motion.stream_fade_stagger.is_zero() {
301 segments.push(FadeSegment {
302 range: prefix..len,
303 started_at: now,
304 });
305 continue;
306 }
307 let words = fade_units(leaf.chunks(), prefix, len);
308 let step = self.motion.stagger_step(words.len());
309 for (ix, range) in words.into_iter().enumerate() {
310 segments.push(FadeSegment {
311 range,
312 started_at: now + step * ix as u32,
313 });
314 }
315 }
316 self.segments.retain(|(_, segments)| !segments.is_empty());
317 }
318
319 pub(super) fn frame(
322 &mut self,
323 now: Instant,
324 reduce_motion: bool,
325 ) -> Option<Arc<StreamFadeFrame>> {
326 if self.segments.is_empty() {
327 return None;
328 }
329 if reduce_motion || !self.is_enabled() {
330 self.segments.clear();
331 return None;
332 }
333 let timing =
334 Timing::new(self.motion.stream_fade).ease(self.motion.stream_fade_easing.clone());
335 let mut leaves = Vec::with_capacity(self.segments.len());
336 self.segments.retain_mut(|(key, segments)| {
337 let mut fades = Vec::with_capacity(segments.len());
338 segments.retain(|segment| {
339 let sample = timing.sample(now.saturating_duration_since(segment.started_at));
340 if sample.finished {
341 return false;
342 }
343 let fade_out = (1.0 - sample.directed_progress).clamp(0.0, 1.0);
344 fades.push((segment.range.clone(), fade_out));
345 true
346 });
347 if fades.is_empty() {
348 return false;
349 }
350 leaves.push((*key, fades));
351 true
352 });
353 (!leaves.is_empty()).then(|| Arc::new(StreamFadeFrame { leaves }))
354 }
355}
356
357pub(super) enum TextLeaf<'a> {
359 Paragraph(&'a Paragraph),
360 Code(SharedString),
361}
362
363enum Chunks<'a> {
364 Paragraph(std::slice::Iter<'a, InlineNode>),
365 Code(Option<&'a str>),
366}
367
368impl<'a> Iterator for Chunks<'a> {
369 type Item = &'a str;
370
371 fn next(&mut self) -> Option<&'a str> {
372 match self {
373 Self::Paragraph(nodes) => nodes.next().map(|node| node.text.as_ref()),
374 Self::Code(code) => code.take(),
375 }
376 }
377}
378
379impl TextLeaf<'_> {
380 fn chunks(&self) -> Chunks<'_> {
381 match self {
382 Self::Paragraph(paragraph) => Chunks::Paragraph(paragraph.children.iter()),
383 Self::Code(code) => Chunks::Code(Some(code.as_ref())),
384 }
385 }
386
387 fn len(&self) -> usize {
388 self.chunks().map(str::len).sum()
389 }
390
391 pub(super) fn common_prefix_len(&self, old: &Self) -> usize {
394 let prefix = common_prefix_len(self.chunks(), old.chunks());
395 floor_char_boundary(self.chunks(), prefix)
396 }
397}
398
399pub(super) fn text_leaves<'a>(block: &'a BlockNode, out: &mut Vec<(TextLeafKey, TextLeaf<'a>)>) {
400 match block {
401 BlockNode::Paragraph(paragraph) => {
402 if let Some(span) = paragraph.span {
403 out.push((
404 TextLeafKey::block(span.start),
405 TextLeaf::Paragraph(paragraph),
406 ));
407 }
408 }
409 BlockNode::Heading {
410 children,
411 span: Some(span),
412 ..
413 } => out.push((
414 TextLeafKey::block(span.start),
415 TextLeaf::Paragraph(children),
416 )),
417 BlockNode::CodeBlock(code_block) => {
418 if let Some(span) = code_block.span {
419 out.push((
420 TextLeafKey::block(span.start),
421 TextLeaf::Code(code_block.code()),
422 ));
423 }
424 }
425 BlockNode::Table(table) => {
426 if let Some(span) = table.span {
427 let cells = table.children.iter().flat_map(|row| row.children.iter());
428 for (ordinal, cell) in cells.enumerate() {
429 out.push((
430 TextLeafKey::table_cell(span.start, ordinal),
431 TextLeaf::Paragraph(&cell.children),
432 ));
433 }
434 }
435 }
436 BlockNode::Root { children, .. }
437 | BlockNode::Blockquote { children, .. }
438 | BlockNode::List { children, .. }
439 | BlockNode::ListItem { children, .. } => {
440 for child in children {
441 text_leaves(child, out);
442 }
443 }
444 _ => {}
445 }
446}
447
448fn common_prefix_len<'a>(
451 mut a: impl Iterator<Item = &'a str>,
452 mut b: impl Iterator<Item = &'a str>,
453) -> usize {
454 let (mut a_rest, mut b_rest): (&[u8], &[u8]) = (&[], &[]);
455 let mut len = 0;
456 loop {
457 if a_rest.is_empty() {
458 match a.next() {
459 Some(chunk) => a_rest = chunk.as_bytes(),
460 None => return len,
461 }
462 continue;
463 }
464 if b_rest.is_empty() {
465 match b.next() {
466 Some(chunk) => b_rest = chunk.as_bytes(),
467 None => return len,
468 }
469 continue;
470 }
471 let step = a_rest.len().min(b_rest.len());
472 if a_rest[..step] != b_rest[..step] {
473 return len
474 + a_rest
475 .iter()
476 .zip(b_rest)
477 .take_while(|(x, y)| x == y)
478 .count();
479 }
480 len += step;
481 a_rest = &a_rest[step..];
482 b_rest = &b_rest[step..];
483 }
484}
485
486fn fade_units<'a>(
490 chunks: impl Iterator<Item = &'a str>,
491 start: usize,
492 end: usize,
493) -> Vec<Range<usize>> {
494 let mut units = Vec::new();
495 let mut unit_start = start;
496 let mut unit_has_glyph = false;
497 let mut previous: Option<char> = None;
498 let mut offset = 0;
499 for chunk in chunks {
500 if offset + chunk.len() <= start {
501 offset += chunk.len();
502 previous = chunk.chars().next_back();
503 continue;
504 }
505 for (ix, c) in chunk.char_indices() {
506 let position = offset + ix;
507 if position >= end {
508 break;
509 }
510 if position >= start {
511 let starts_unit = unit_has_glyph
512 && !c.is_whitespace()
513 && (is_cjk(c) || previous.is_some_and(|p| p.is_whitespace() || is_cjk(p)));
514 if starts_unit && position > unit_start {
515 units.push(unit_start..position);
516 unit_start = position;
517 unit_has_glyph = false;
518 }
519 unit_has_glyph |= !c.is_whitespace();
520 }
521 previous = Some(c);
522 }
523 offset += chunk.len();
524 if offset >= end {
525 break;
526 }
527 }
528 if unit_start < end {
529 units.push(unit_start..end);
530 }
531 units
532}
533
534fn is_cjk(c: char) -> bool {
535 matches!(
536 u32::from(c),
537 0x3040..=0x30FF | 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xAC00..=0xD7AF | 0xF900..=0xFAFF | 0x20000..=0x2FA1F )
544}
545
546fn floor_char_boundary<'a>(chunks: impl Iterator<Item = &'a str>, offset: usize) -> usize {
547 let mut start = 0;
548 for chunk in chunks {
549 let end = start + chunk.len();
550 if offset < end {
551 let mut local = offset - start;
552 while !chunk.is_char_boundary(local) {
553 local -= 1;
554 }
555 return start + local;
556 }
557 start = end;
558 }
559 offset
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn common_prefix_spans_chunk_boundaries() {
568 assert_eq!(
569 common_prefix_len(["ab", "cd"].into_iter(), ["abc", "d"].into_iter()),
570 4
571 );
572 assert_eq!(
573 common_prefix_len(["ab", "cd"].into_iter(), ["abc", "x"].into_iter()),
574 3
575 );
576 assert_eq!(
577 common_prefix_len(["", "ab"].into_iter(), ["a", "", "b", "c"].into_iter()),
578 2
579 );
580 assert_eq!(common_prefix_len(["ab"].into_iter(), [].into_iter()), 0);
581 }
582
583 #[test]
584 fn fade_units_are_words_with_their_trailing_space() {
585 let text = ["hello", " one two", " three"];
586 assert_eq!(
587 fade_units(text.into_iter(), 5, 20),
588 vec![5..10, 10..15, 15..20]
589 );
590 assert_eq!(fade_units(["a b"].into_iter(), 1, 4), vec![1..4]);
592 assert_eq!(
593 fade_units(["abc"].into_iter(), 3, 3),
594 Vec::<Range<usize>>::new()
595 );
596 }
597
598 #[test]
599 fn fade_units_split_cjk_by_character() {
600 assert_eq!(
601 fade_units(["你好,世界 ok"].into_iter(), 0, 18),
602 vec![0..3, 3..6, 6..9, 9..12, 12..16, 16..18]
603 );
604 assert_eq!(fade_units(["ab中"].into_iter(), 0, 5), vec![0..2, 2..5]);
606 }
607
608 #[test]
609 fn stagger_holds_while_the_update_lights_up_within_one_fade() {
610 let motion = TextViewMotion::default()
611 .with_stream_fade(Duration::from_millis(600))
612 .with_stream_fade_stagger(Duration::from_millis(100));
613 assert_eq!(motion.stagger_step(1), Duration::ZERO);
614 assert_eq!(motion.stagger_step(3), Duration::from_millis(100));
615 assert_eq!(motion.stagger_step(7), Duration::from_millis(100));
617 }
618
619 #[test]
620 fn an_update_too_large_to_light_up_in_one_fade_fades_as_one_chunk() {
621 let motion = TextViewMotion::default()
622 .with_stream_fade(Duration::from_millis(600))
623 .with_stream_fade_stagger(Duration::from_millis(100));
624 assert_eq!(motion.stagger_step(8), Duration::ZERO);
626 assert_eq!(motion.stagger_step(200), Duration::ZERO);
627 let plain = TextViewMotion::default().with_stream_fade(Duration::from_millis(600));
629 assert_eq!(plain.stagger_step(200), Duration::ZERO);
630 }
631
632 #[test]
633 fn prefix_never_splits_a_character() {
634 let new = "a中";
636 let old = "a串";
637 let prefix = common_prefix_len([new].into_iter(), [old].into_iter());
638 assert!(prefix > 1 && !new.is_char_boundary(prefix));
639 assert_eq!(floor_char_boundary([new].into_iter(), prefix), 1);
640 assert_eq!(floor_char_boundary(["a", "中"].into_iter(), 4), 4);
641 }
642}