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