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)]
101pub(crate) struct TextLeafKey {
102 block_start: usize,
103 ordinal: usize,
104}
105
106impl From<TextLeafKey> for ElementId {
110 fn from(key: TextLeafKey) -> Self {
111 let mut bytes = [0; 20];
112 bytes[..8].copy_from_slice(&(key.block_start as u64).to_le_bytes());
113 bytes[8..16].copy_from_slice(&(key.ordinal as u64).to_le_bytes());
114 ElementId::OpaqueId(bytes)
115 }
116}
117
118impl TextLeafKey {
119 pub(crate) fn block(start: usize) -> Self {
120 Self {
121 block_start: start,
122 ordinal: 0,
123 }
124 }
125
126 pub(crate) fn table_cell(table_start: usize, ordinal: usize) -> Self {
127 Self {
128 block_start: table_start,
129 ordinal: ordinal + 1,
130 }
131 }
132}
133
134pub(crate) type FadeRanges = Vec<(Range<usize>, f32)>;
137
138#[derive(Debug, Default)]
141pub(crate) struct StreamFadeFrame {
142 leaves: Vec<(TextLeafKey, FadeRanges)>,
143}
144
145impl StreamFadeFrame {
146 pub(crate) fn fades(&self, key: TextLeafKey) -> Option<&[(Range<usize>, f32)]> {
147 self.leaves
148 .iter()
149 .find(|(leaf, _)| *leaf == key)
150 .map(|(_, fades)| fades.as_slice())
151 }
152}
153
154struct FadeSegment {
155 range: Range<usize>,
156 started_at: Instant,
157}
158
159#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
160enum PendingUpdate {
161 #[default]
162 None,
163 Extend {
166 origin: usize,
167 },
168 Replace,
169}
170
171#[derive(Default)]
173pub(super) struct StreamFadeTracker {
174 motion: TextViewMotion,
175 pending: PendingUpdate,
176 segments: Vec<(TextLeafKey, Vec<FadeSegment>)>,
177}
178
179impl StreamFadeTracker {
180 pub(super) fn set_motion(&mut self, motion: TextViewMotion) {
181 if motion.stream_fade.is_zero() {
182 self.segments.clear();
183 self.pending = PendingUpdate::None;
184 }
185 self.motion = motion;
186 }
187
188 pub(super) fn is_enabled(&self) -> bool {
189 !self.motion.stream_fade.is_zero()
190 }
191
192 pub(super) fn note_extend(&mut self, len: usize) {
194 if !self.is_enabled() {
195 return;
196 }
197 self.pending = match self.pending {
198 PendingUpdate::None => PendingUpdate::Extend { origin: len },
199 PendingUpdate::Extend { origin } => PendingUpdate::Extend {
200 origin: origin.min(len),
201 },
202 PendingUpdate::Replace => PendingUpdate::Replace,
203 };
204 }
205
206 pub(super) fn note_replace(&mut self) {
208 if self.is_enabled() {
209 self.pending = PendingUpdate::Replace;
210 }
211 }
212
213 pub(super) fn discard_pending(&mut self) {
215 self.pending = PendingUpdate::None;
216 }
217
218 pub(super) fn record(&mut self, old: &ParsedDocument, new: &ParsedDocument, now: Instant) {
225 let pending = std::mem::take(&mut self.pending);
226 if !self.is_enabled() {
227 self.segments.clear();
228 return;
229 }
230 let origin = match pending {
231 PendingUpdate::None => return,
232 PendingUpdate::Replace => {
233 self.segments.clear();
234 return;
235 }
236 PendingUpdate::Extend { origin } => origin,
237 };
238
239 let mut affected = Vec::new();
240 for block in new.blocks.iter().rev() {
241 if block.span().is_some_and(|span| span.end <= origin) {
242 break;
243 }
244 text_leaves(block, &mut affected);
245 }
246 let Some(first_start) = affected.iter().map(|(key, _)| key.block_start).min() else {
247 return;
248 };
249
250 let mut previous = Vec::new();
251 for block in old.blocks.iter().rev() {
252 if block.span().is_some_and(|span| span.end < first_start) {
253 break;
254 }
255 text_leaves(block, &mut previous);
256 }
257
258 for (key, leaf) in affected {
259 let len = leaf.len();
260 let prefix = previous
261 .iter()
262 .find(|(previous_key, _)| *previous_key == key)
263 .map_or(0, |(_, old_leaf)| leaf.common_prefix_len(old_leaf));
264 let segments = match self.segments.iter().position(|(k, _)| *k == key) {
265 Some(ix) => &mut self.segments[ix].1,
266 None => {
267 self.segments.push((key, Vec::new()));
268 &mut self.segments.last_mut().expect("just pushed").1
269 }
270 };
271 segments.retain_mut(|segment| {
274 segment.range.end = segment.range.end.min(prefix);
275 segment.range.start < segment.range.end
276 });
277 if prefix >= len {
278 continue;
279 }
280 if self.motion.stream_fade_stagger.is_zero() {
281 segments.push(FadeSegment {
282 range: prefix..len,
283 started_at: now,
284 });
285 continue;
286 }
287 let words = fade_units(leaf.chunks(), prefix, len);
288 let step = self.motion.stagger_step(words.len());
289 for (ix, range) in words.into_iter().enumerate() {
290 segments.push(FadeSegment {
291 range,
292 started_at: now + step * ix as u32,
293 });
294 }
295 }
296 self.segments.retain(|(_, segments)| !segments.is_empty());
297 }
298
299 pub(super) fn frame(
302 &mut self,
303 now: Instant,
304 reduce_motion: bool,
305 ) -> Option<Arc<StreamFadeFrame>> {
306 if self.segments.is_empty() {
307 return None;
308 }
309 if reduce_motion || !self.is_enabled() {
310 self.segments.clear();
311 return None;
312 }
313 let timing =
314 Timing::new(self.motion.stream_fade).ease(self.motion.stream_fade_easing.clone());
315 let mut leaves = Vec::with_capacity(self.segments.len());
316 self.segments.retain_mut(|(key, segments)| {
317 let mut fades = Vec::with_capacity(segments.len());
318 segments.retain(|segment| {
319 let sample = timing.sample(now.saturating_duration_since(segment.started_at));
320 if sample.finished {
321 return false;
322 }
323 let fade_out = (1.0 - sample.directed_progress).clamp(0.0, 1.0);
324 fades.push((segment.range.clone(), fade_out));
325 true
326 });
327 if fades.is_empty() {
328 return false;
329 }
330 leaves.push((*key, fades));
331 true
332 });
333 (!leaves.is_empty()).then(|| Arc::new(StreamFadeFrame { leaves }))
334 }
335}
336
337enum TextLeaf<'a> {
339 Paragraph(&'a Paragraph),
340 Code(SharedString),
341}
342
343enum Chunks<'a> {
344 Paragraph(std::slice::Iter<'a, InlineNode>),
345 Code(Option<&'a str>),
346}
347
348impl<'a> Iterator for Chunks<'a> {
349 type Item = &'a str;
350
351 fn next(&mut self) -> Option<&'a str> {
352 match self {
353 Self::Paragraph(nodes) => nodes.next().map(|node| node.text.as_ref()),
354 Self::Code(code) => code.take(),
355 }
356 }
357}
358
359impl TextLeaf<'_> {
360 fn chunks(&self) -> Chunks<'_> {
361 match self {
362 Self::Paragraph(paragraph) => Chunks::Paragraph(paragraph.children.iter()),
363 Self::Code(code) => Chunks::Code(Some(code.as_ref())),
364 }
365 }
366
367 fn len(&self) -> usize {
368 self.chunks().map(str::len).sum()
369 }
370
371 fn common_prefix_len(&self, old: &Self) -> usize {
374 let prefix = common_prefix_len(self.chunks(), old.chunks());
375 floor_char_boundary(self.chunks(), prefix)
376 }
377}
378
379fn text_leaves<'a>(block: &'a BlockNode, out: &mut Vec<(TextLeafKey, TextLeaf<'a>)>) {
380 match block {
381 BlockNode::Paragraph(paragraph) => {
382 if let Some(span) = paragraph.span {
383 out.push((
384 TextLeafKey::block(span.start),
385 TextLeaf::Paragraph(paragraph),
386 ));
387 }
388 }
389 BlockNode::Heading {
390 children,
391 span: Some(span),
392 ..
393 } => out.push((
394 TextLeafKey::block(span.start),
395 TextLeaf::Paragraph(children),
396 )),
397 BlockNode::CodeBlock(code_block) => {
398 if let Some(span) = code_block.span {
399 out.push((
400 TextLeafKey::block(span.start),
401 TextLeaf::Code(code_block.code()),
402 ));
403 }
404 }
405 BlockNode::Table(table) => {
406 if let Some(span) = table.span {
407 let cells = table.children.iter().flat_map(|row| row.children.iter());
408 for (ordinal, cell) in cells.enumerate() {
409 out.push((
410 TextLeafKey::table_cell(span.start, ordinal),
411 TextLeaf::Paragraph(&cell.children),
412 ));
413 }
414 }
415 }
416 BlockNode::Root { children, .. }
417 | BlockNode::Blockquote { children, .. }
418 | BlockNode::List { children, .. }
419 | BlockNode::ListItem { children, .. } => {
420 for child in children {
421 text_leaves(child, out);
422 }
423 }
424 _ => {}
425 }
426}
427
428fn common_prefix_len<'a>(
431 mut a: impl Iterator<Item = &'a str>,
432 mut b: impl Iterator<Item = &'a str>,
433) -> usize {
434 let (mut a_rest, mut b_rest): (&[u8], &[u8]) = (&[], &[]);
435 let mut len = 0;
436 loop {
437 if a_rest.is_empty() {
438 match a.next() {
439 Some(chunk) => a_rest = chunk.as_bytes(),
440 None => return len,
441 }
442 continue;
443 }
444 if b_rest.is_empty() {
445 match b.next() {
446 Some(chunk) => b_rest = chunk.as_bytes(),
447 None => return len,
448 }
449 continue;
450 }
451 let step = a_rest.len().min(b_rest.len());
452 if a_rest[..step] != b_rest[..step] {
453 return len
454 + a_rest
455 .iter()
456 .zip(b_rest)
457 .take_while(|(x, y)| x == y)
458 .count();
459 }
460 len += step;
461 a_rest = &a_rest[step..];
462 b_rest = &b_rest[step..];
463 }
464}
465
466fn fade_units<'a>(
470 chunks: impl Iterator<Item = &'a str>,
471 start: usize,
472 end: usize,
473) -> Vec<Range<usize>> {
474 let mut units = Vec::new();
475 let mut unit_start = start;
476 let mut unit_has_glyph = false;
477 let mut previous: Option<char> = None;
478 let mut offset = 0;
479 for chunk in chunks {
480 if offset + chunk.len() <= start {
481 offset += chunk.len();
482 previous = chunk.chars().next_back();
483 continue;
484 }
485 for (ix, c) in chunk.char_indices() {
486 let position = offset + ix;
487 if position >= end {
488 break;
489 }
490 if position >= start {
491 let starts_unit = unit_has_glyph
492 && !c.is_whitespace()
493 && (is_cjk(c) || previous.is_some_and(|p| p.is_whitespace() || is_cjk(p)));
494 if starts_unit && position > unit_start {
495 units.push(unit_start..position);
496 unit_start = position;
497 unit_has_glyph = false;
498 }
499 unit_has_glyph |= !c.is_whitespace();
500 }
501 previous = Some(c);
502 }
503 offset += chunk.len();
504 if offset >= end {
505 break;
506 }
507 }
508 if unit_start < end {
509 units.push(unit_start..end);
510 }
511 units
512}
513
514fn is_cjk(c: char) -> bool {
515 matches!(
516 u32::from(c),
517 0x3040..=0x30FF | 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xAC00..=0xD7AF | 0xF900..=0xFAFF | 0x20000..=0x2FA1F )
524}
525
526fn floor_char_boundary<'a>(chunks: impl Iterator<Item = &'a str>, offset: usize) -> usize {
527 let mut start = 0;
528 for chunk in chunks {
529 let end = start + chunk.len();
530 if offset < end {
531 let mut local = offset - start;
532 while !chunk.is_char_boundary(local) {
533 local -= 1;
534 }
535 return start + local;
536 }
537 start = end;
538 }
539 offset
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545
546 #[test]
547 fn common_prefix_spans_chunk_boundaries() {
548 assert_eq!(
549 common_prefix_len(["ab", "cd"].into_iter(), ["abc", "d"].into_iter()),
550 4
551 );
552 assert_eq!(
553 common_prefix_len(["ab", "cd"].into_iter(), ["abc", "x"].into_iter()),
554 3
555 );
556 assert_eq!(
557 common_prefix_len(["", "ab"].into_iter(), ["a", "", "b", "c"].into_iter()),
558 2
559 );
560 assert_eq!(common_prefix_len(["ab"].into_iter(), [].into_iter()), 0);
561 }
562
563 #[test]
564 fn fade_units_are_words_with_their_trailing_space() {
565 let text = ["hello", " one two", " three"];
566 assert_eq!(
567 fade_units(text.into_iter(), 5, 20),
568 vec![5..10, 10..15, 15..20]
569 );
570 assert_eq!(fade_units(["a b"].into_iter(), 1, 4), vec![1..4]);
572 assert_eq!(
573 fade_units(["abc"].into_iter(), 3, 3),
574 Vec::<Range<usize>>::new()
575 );
576 }
577
578 #[test]
579 fn fade_units_split_cjk_by_character() {
580 assert_eq!(
581 fade_units(["你好,世界 ok"].into_iter(), 0, 18),
582 vec![0..3, 3..6, 6..9, 9..12, 12..16, 16..18]
583 );
584 assert_eq!(fade_units(["ab中"].into_iter(), 0, 5), vec![0..2, 2..5]);
586 }
587
588 #[test]
589 fn stagger_holds_while_the_update_lights_up_within_one_fade() {
590 let motion = TextViewMotion::default()
591 .with_stream_fade(Duration::from_millis(600))
592 .with_stream_fade_stagger(Duration::from_millis(100));
593 assert_eq!(motion.stagger_step(1), Duration::ZERO);
594 assert_eq!(motion.stagger_step(3), Duration::from_millis(100));
595 assert_eq!(motion.stagger_step(7), Duration::from_millis(100));
597 }
598
599 #[test]
600 fn an_update_too_large_to_light_up_in_one_fade_fades_as_one_chunk() {
601 let motion = TextViewMotion::default()
602 .with_stream_fade(Duration::from_millis(600))
603 .with_stream_fade_stagger(Duration::from_millis(100));
604 assert_eq!(motion.stagger_step(8), Duration::ZERO);
606 assert_eq!(motion.stagger_step(200), Duration::ZERO);
607 let plain = TextViewMotion::default().with_stream_fade(Duration::from_millis(600));
609 assert_eq!(plain.stagger_step(200), Duration::ZERO);
610 }
611
612 #[test]
613 fn prefix_never_splits_a_character() {
614 let new = "a中";
616 let old = "a串";
617 let prefix = common_prefix_len([new].into_iter(), [old].into_iter());
618 assert!(prefix > 1 && !new.is_char_boundary(prefix));
619 assert_eq!(floor_char_boundary([new].into_iter(), prefix), 1);
620 assert_eq!(floor_char_boundary(["a", "中"].into_iter(), 4), 4);
621 }
622}