1use std::{
2 collections::{VecDeque, hash_map::RandomState},
3 ops::Range,
4 path::Path,
5 sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, mpsc},
6 thread,
7};
8
9use ratatui::style::Color;
10use syntect::{
11 highlighting::{HighlightIterator, HighlightState, Highlighter},
12 parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
13};
14
15#[cfg(feature = "delivery-trace")]
16use crate::delivery_trace::{Outcome, ReplayReason, RequestWork};
17#[cfg(feature = "delivery-trace")]
18use std::time::Instant;
19
20use crate::{
21 model::Kind,
22 storage::FileIndex,
23 text::display_text,
24 theme::{self, Appearance},
25 view::LineId,
26};
27
28pub(crate) fn syntax_for_path<'s>(
36 path: &str,
37 syntaxes: &'s SyntaxSet,
38) -> Option<&'s SyntaxReference> {
39 let path = Path::new(path);
40 let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
41 let by_name = (!name.is_empty())
42 .then(|| {
43 syntaxes
44 .syntaxes()
45 .iter()
46 .rev()
47 .find(|syntax| syntax.file_extensions.iter().any(|known| known == name))
48 })
49 .flatten();
50 by_name.or_else(|| {
51 let extension = path.extension().and_then(|s| s.to_str())?;
52 syntaxes.find_syntax_by_extension(extension)
53 })
54}
55
56mod checkpoint;
57use checkpoint::Checkpoints;
58mod full_source;
59mod input;
60pub use input::{FileSource, Origin};
61
62mod inline;
63pub use inline::{ChangeSide, changed_spans};
64
65#[cfg(feature = "owner-census")]
66mod cache_activity;
67#[cfg(feature = "owner-census")]
68mod census;
69
70#[derive(Clone, Debug, PartialEq)]
71pub struct Token {
72 pub range: Range<usize>,
73 pub color: Color,
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct LineStyle {
78 pub tokens: Vec<Token>,
79 pub old_tokens: Option<Vec<Token>>,
80 pub changed: Vec<Range<usize>>,
81 pub fallback: bool,
82 pub fallback_reason: Option<Fallback>,
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum Fallback {
87 UnsupportedLanguage,
88 LongLine,
89 ParserFailure,
90}
91
92impl Fallback {
93 pub fn status(self) -> &'static str {
94 match self {
95 Self::UnsupportedLanguage => "Plain syntax · unsupported language",
96 Self::LongLine => "Plain syntax · oversized line in source prefix",
97 Self::ParserFailure => "Plain syntax · source parser failed",
98 }
99 }
100}
101
102impl LineStyle {
103 fn cache_weight(&self) -> usize {
104 size_of::<Self>()
105 + self.tokens.capacity() * size_of::<Token>()
106 + self
107 .old_tokens
108 .as_ref()
109 .map_or(0, |tokens| tokens.capacity() * size_of::<Token>())
110 + self.changed.capacity() * size_of::<Range<usize>>()
111 }
112
113 pub fn tokens_for(&self, old: bool) -> &[Token] {
114 if old {
115 self.old_tokens.as_deref().unwrap_or(&self.tokens)
116 } else {
117 &self.tokens
118 }
119 }
120}
121
122fn tokens(fragments: Vec<(syntect::highlighting::Style, &str)>) -> Vec<Token> {
123 let mut byte = 0;
124 fragments
125 .into_iter()
126 .map(|(style, text)| {
127 let start = byte;
128 byte += text.len();
129 Token {
130 range: start..byte,
131 color: Color::Rgb(style.foreground.r, style.foreground.g, style.foreground.b),
132 }
133 })
134 .collect()
135}
136
137struct Side {
138 parse: ParseState,
139 highlight: HighlightState,
140 needs_colors: bool,
141 fallback: Option<Fallback>,
142}
143
144impl Side {
145 fn new(syntax: &SyntaxReference, highlighter: &Highlighter<'_>) -> Self {
146 Self {
147 parse: ParseState::new(syntax),
148 highlight: HighlightState::new(highlighter, ScopeStack::new()),
149 needs_colors: false,
150 fallback: None,
151 }
152 }
153
154 fn advance(
155 &mut self,
156 text: &str,
157 syntaxes: &SyntaxSet,
158 highlighter: &Highlighter<'_>,
159 visible: bool,
160 #[cfg(feature = "delivery-trace")] work: &mut RequestWork,
161 #[cfg(feature = "delivery-trace")] source_side: (LineId, bool),
162 ) -> Option<Vec<Token>> {
163 if self.fallback.is_some() {
164 return None;
165 }
166 #[cfg(feature = "delivery-trace")]
167 let initialization = syntect::parsing::syntax_timing::Window::parser_call();
168 #[cfg(feature = "delivery-trace")]
169 let parse_start = Instant::now();
170 let parsed = self.parse.parse_line(text, syntaxes);
171 #[cfg(feature = "delivery-trace")]
172 let parse_elapsed = parse_start.elapsed();
173 #[cfg(feature = "delivery-trace")]
174 work.parsed(
175 parse_elapsed,
176 visible,
177 source_side.0,
178 source_side.1,
179 initialization.finish(),
180 );
181 let ops = match parsed {
182 Ok(ops) => ops,
183 Err(_) => {
184 self.fallback = Some(Fallback::ParserFailure);
185 return None;
186 }
187 };
188 #[cfg(feature = "delivery-trace")]
189 let scope_start = Instant::now();
190 if visible {
191 if self.needs_colors {
192 let path = std::mem::replace(&mut self.highlight.path, ScopeStack::new());
193 self.highlight = HighlightState::new(highlighter, path);
194 self.needs_colors = false;
195 }
196 let colored = tokens(
197 HighlightIterator::new(&mut self.highlight, &ops, text, highlighter).collect(),
198 );
199 #[cfg(feature = "delivery-trace")]
200 work.summary.visible_colors.record(scope_start);
201 Some(colored)
202 } else {
203 self.needs_colors = true;
204 for (_, op) in ops {
205 if self.highlight.path.apply(&op).is_err() {
206 self.fallback = Some(Fallback::ParserFailure);
207 #[cfg(feature = "delivery-trace")]
208 work.summary.replay_scopes.record(scope_start);
209 return None;
210 }
211 }
212 #[cfg(feature = "delivery-trace")]
213 work.summary.replay_scopes.record(scope_start);
214 Some(Vec::new())
215 }
216 }
217}
218
219#[derive(Clone, Copy, PartialEq, Eq)]
220enum SyntaxScope {
221 PatchHunk(usize),
222 SourceSide { old: bool },
223}
224
225#[derive(Clone, Copy, PartialEq, Eq)]
226struct SyntaxIdentity {
227 origin: Origin,
228 file: usize,
229 scope: SyntaxScope,
230 epoch: u64,
231 appearance: Appearance,
232}
233
234#[derive(Clone, Copy, PartialEq, Eq)]
235struct SyntaxKey {
236 origin: Origin,
237 file: usize,
238 scope: SyntaxScope,
239}
240
241impl SyntaxIdentity {
242 fn key(self) -> SyntaxKey {
243 SyntaxKey {
244 origin: self.origin,
245 file: self.file,
246 scope: self.scope,
247 }
248 }
249}
250
251struct Continuation {
252 identity: SyntaxIdentity,
253 next: usize,
254 old: Side,
255 new: Side,
256}
257
258pub struct Request<S = usize> {
259 pub epoch: u64,
260 pub appearance: Appearance,
261 pub ids: Vec<S>,
262 pub files: Vec<FileSource>,
263}
264pub struct Reply {
265 pub origin: Origin,
266 pub source_error: Option<Arc<str>>,
267 #[cfg(feature = "delivery-trace")]
268 pub generation: u64,
269 pub epoch: u64,
270 pub id: LineId,
271 pub style: LineStyle,
272}
273
274pub struct Worker {
275 requests: Arc<Mailbox>,
276 replies: mpsc::Receiver<anyhow::Result<Reply>>,
277 thread: Option<thread::JoinHandle<()>>,
278}
279
280#[derive(Default)]
281struct Pending {
282 request: Option<Request<LineId>>,
283 #[cfg(feature = "owner-census")]
284 census: Option<crate::owner_census::Request>,
285 generation: u64,
286 closed: bool,
287}
288
289struct Mailbox {
290 pending: Mutex<Pending>,
291 ready: Condvar,
292 wake: mpsc::SyncSender<()>,
293}
294
295impl Worker {
296 #[cfg(feature = "owner-census")]
297 pub(crate) fn request_census(
298 &self,
299 request: crate::owner_census::Request,
300 ) -> anyhow::Result<()> {
301 let mut pending = self.requests.lock();
302 anyhow::ensure!(
303 !pending.closed && pending.census.is_none(),
304 "Census worker unavailable"
305 );
306 pending.census = Some(request);
307 self.requests.ready.notify_one();
308 Ok(())
309 }
310
311 pub fn try_reply(&self) -> Result<anyhow::Result<Reply>, mpsc::TryRecvError> {
312 let _pending = self.requests.lock();
314 let reply = self.replies.try_recv()?;
315 self.requests.ready.notify_one();
316 Ok(reply)
317 }
318
319 pub fn request<S: Into<LineId>>(&self, request: Request<S>) {
320 let mut pending = self.requests.lock();
321 if pending.closed {
322 return;
323 }
324 let epoch = request.epoch;
325 pending.request = Some(Request {
326 epoch,
327 appearance: request.appearance,
328 ids: request.ids.into_iter().map(Into::into).collect(),
329 files: request.files,
330 });
331 pending.generation = pending.generation.wrapping_add(1);
332 crate::delivery_event!("request", pending.generation, epoch);
333 self.requests.ready.notify_one();
334 }
335
336 pub fn cancel(&self) {
337 let mut pending = self.requests.lock();
338 pending.request = None;
339 pending.generation = pending.generation.wrapping_add(1);
340 crate::delivery_event!("cancel", pending.generation, 0);
341 self.requests.ready.notify_one();
342 }
343
344 pub fn close(&mut self) -> anyhow::Result<()> {
346 {
347 let mut pending = self.requests.lock();
348 pending.closed = true;
349 pending.request = None;
350 #[cfg(feature = "owner-census")]
351 {
352 pending.census = None;
353 }
354 self.requests.ready.notify_one();
355 }
356 let result = self.thread.take().map_or(Ok(()), |thread| {
357 thread
358 .join()
359 .map_err(|_| anyhow::anyhow!("The syntax worker panicked"))
360 });
361 while self.replies.try_recv().is_ok() {}
362 result
363 }
364}
365
366impl Drop for Worker {
367 fn drop(&mut self) {
368 let _ = self.close();
369 }
370}
371
372impl Mailbox {
373 fn lock(&self) -> MutexGuard<'_, Pending> {
377 self.pending.lock().unwrap_or_else(PoisonError::into_inner)
378 }
379
380 fn wait<'a>(&self, pending: MutexGuard<'a, Pending>) -> MutexGuard<'a, Pending> {
381 self.ready
382 .wait(pending)
383 .unwrap_or_else(PoisonError::into_inner)
384 }
385
386 fn take(
387 &self,
388 #[cfg(feature = "owner-census")] syntaxes: &syntect::parsing::SyntaxSet,
389 #[cfg(feature = "owner-census")] inspect: impl Fn() -> serde_json::Value,
390 ) -> Option<(u64, Request<LineId>)> {
391 let mut pending = self.lock();
392 loop {
393 if pending.closed {
394 return None;
395 }
396 if let Some(request) = pending.request.take() {
397 return Some((pending.generation, request));
398 }
399 #[cfg(feature = "owner-census")]
400 if let Some(census) = pending.census.take() {
401 drop(pending);
402 census.capture(syntaxes, &inspect);
403 let _ = self.wake.try_send(());
404 pending = self.lock();
405 continue;
406 }
407 pending = self.wait(pending);
408 }
409 }
410
411 fn current(&self, generation: u64) -> bool {
412 let pending = self.lock();
413 !pending.closed && pending.generation == generation
414 }
415
416 fn publish(
417 &self,
418 generation: u64,
419 mut reply: anyhow::Result<Reply>,
420 output: &mpsc::SyncSender<anyhow::Result<Reply>>,
421 ) -> bool {
422 #[cfg(feature = "delivery-trace")]
423 crate::delivery_trace::source_event("ready", generation, reply.as_ref().ok().map(|r| r.id));
424 let mut pending = self.lock();
425 loop {
426 if pending.closed || pending.generation != generation {
427 return false;
428 }
429 #[cfg(feature = "delivery-trace")]
430 crate::delivery_trace::source_event(
431 "offer",
432 generation,
433 reply.as_ref().ok().map(|r| r.id),
434 );
435 match output.try_send(reply) {
436 Ok(()) => {
437 crate::delivery_event!("published", generation, 0);
438 drop(pending);
439 let _ = self.wake.try_send(());
440 return true;
441 }
442 Err(mpsc::TrySendError::Disconnected(_)) => return false,
443 Err(mpsc::TrySendError::Full(value)) => reply = value,
444 }
445 crate::delivery_event!("full", generation, 0);
446 pending = self.wait(pending);
447 }
448 }
449}
450
451pub fn start(wake: mpsc::SyncSender<()>) -> Worker {
452 let requests = Arc::new(Mailbox {
453 pending: Mutex::default(),
454 ready: Condvar::default(),
455 wake,
456 });
457 let input = requests.clone();
458 let (output, replies) = mpsc::sync_channel(16);
459 let thread = thread::spawn(move || {
460 let _notify_exit = crate::wake::NotifyOnExit(input.wake.clone());
462 let output = output;
463 #[cfg(feature = "delivery-trace")]
464 let syntax_start = Instant::now();
465 let syntaxes = two_face::syntax::extra_newlines();
466 #[cfg(feature = "regex-census")]
467 let _census = crate::regex_census::Capture::new(&syntaxes);
468 #[cfg(feature = "delivery-trace")]
469 crate::delivery_event!("syntax_set_startup", syntax_start.elapsed().as_nanos(), 0);
470 let mut continuation: Option<Continuation> = None;
471 let mut full = full_source::Replay::default();
472 let mut checkpoints = Checkpoints::new();
473 'requests: while let Some((generation, request)) = input.take(
474 #[cfg(feature = "owner-census")]
475 &syntaxes,
476 #[cfg(feature = "owner-census")]
477 || census::worker(&continuation, &full, &checkpoints),
478 ) {
479 #[cfg(feature = "delivery-trace")]
480 let mut work = RequestWork::new(generation, request.epoch, &request.ids);
481 #[cfg(feature = "delivery-trace")]
482 let theme_start = Instant::now();
483 let Ok((_, theme)) = theme::load(request.appearance) else {
484 #[cfg(feature = "delivery-trace")]
485 {
486 work.summary.theme.record(theme_start);
487 work.summary.outcome = Outcome::ThemeError;
488 }
489 break;
490 };
491 let highlighter = Highlighter::new(&theme);
492 #[cfg(feature = "delivery-trace")]
493 work.summary.theme.record(theme_start);
494 let mut ids = request.ids.clone();
495 ids.sort_unstable();
496 ids.dedup();
497 #[cfg(feature = "delivery-trace")]
498 {
499 work.summary.requested_unique = Some(ids.len());
500 }
501 for source in &request.files {
502 let mut checkpoint_scope = checkpoints.scope();
503 let checkpoints = checkpoint_scope.cache();
504 let file = source.file();
505 let origin = source.origin();
506 let selected: Vec<_> = ids
507 .iter()
508 .copied()
509 .filter(|id| id.belongs_to(file))
510 .collect();
511 if !matches!(source, FileSource::Patch(_)) {
512 continuation = None;
513 let completed = full.run(full_source::Job {
514 input: &input,
515 output: &output,
516 generation,
517 request: &request,
518 source,
519 wanted: &selected,
520 syntaxes: &syntaxes,
521 highlighter: &highlighter,
522 checkpoints,
523 #[cfg(feature = "delivery-trace")]
524 work: &mut work,
525 });
526 match completed {
527 Ok(true) => continue,
528 Ok(false) => continue 'requests,
529 Err(error) => {
530 input.publish(generation, Err(error), &output);
531 return;
532 }
533 }
534 }
535 let patch_ids: Vec<_> = selected.iter().filter_map(|id| id.patch()).collect();
536 let mut wanted = patch_ids.as_slice();
537 full = full_source::Replay::default();
538 let syntax = syntax_for_path(file.path.as_ref(), &syntaxes)
539 .unwrap_or_else(|| syntaxes.find_syntax_plain_text());
540 while let Some(&first) = wanted.first() {
541 let hunk_index = file.hunks.partition_point(|h| h.lines.end <= first);
542 let hunk = &file.hunks[hunk_index];
543 let count = wanted.partition_point(|id| *id < hunk.lines.end);
544 let (current, remaining) = wanted.split_at(count);
545 wanted = remaining;
546 let last = current[current.len() - 1];
547 let identity = SyntaxIdentity {
548 origin,
549 file: file.id,
550 scope: SyntaxScope::PatchHunk(hunk_index),
551 epoch: request.epoch,
552 appearance: request.appearance,
553 };
554 #[cfg(feature = "delivery-trace")]
555 let mut reason = match continuation.as_ref() {
556 None => ReplayReason::Missing,
557 Some(c) if c.identity.file != file.id => ReplayReason::File,
558 Some(c) if c.identity.scope != identity.scope => ReplayReason::Hunk,
559 Some(c)
560 if c.identity.epoch != request.epoch
561 || c.identity.appearance != request.appearance =>
562 {
563 ReplayReason::Epoch
564 }
565 Some(c) if c.next > first => ReplayReason::Backward,
566 Some(_) => ReplayReason::Resume,
567 };
568 let active = continuation
569 .as_ref()
570 .filter(|c| c.identity == identity && c.next <= first)
571 .map(|c| c.next);
572 if let Some(checkpoint) = checkpoints.nearest(identity, first)
573 && active.is_none_or(|next| checkpoint.next > next)
574 {
575 if !input.current(generation) {
576 #[cfg(feature = "delivery-trace")]
577 {
578 work.summary.outcome = Outcome::SupersededOrClosed;
579 }
580 continue 'requests;
581 }
582 let Some(restored) = checkpoint.restore(identity, &highlighter) else {
583 continue 'requests;
584 };
585 if !input.current(generation) {
586 #[cfg(feature = "delivery-trace")]
587 {
588 work.summary.outcome = Outcome::SupersededOrClosed;
589 }
590 continue 'requests;
591 }
592 continuation = Some(restored);
593 #[cfg(feature = "delivery-trace")]
594 {
595 reason = ReplayReason::Checkpoint;
596 }
597 } else if active.is_none() {
598 continuation = Some(Continuation {
599 identity,
600 next: hunk.lines.start,
601 old: Side::new(syntax, &highlighter),
602 new: Side::new(syntax, &highlighter),
603 });
604 }
605 let state = continuation.as_mut().unwrap();
606 #[cfg(feature = "delivery-trace")]
607 work.group(file.id, hunk_index, state.next, first..=last, reason);
608 for id in state.next..=last {
609 #[cfg(feature = "delivery-trace")]
610 if id % 32 == 0 {
611 work.summary.replay_cancellation_checks += 1;
612 }
613 if id % 32 == 0 && !input.current(generation) {
614 #[cfg(feature = "delivery-trace")]
615 {
616 work.summary.outcome = Outcome::SupersededOrClosed;
617 }
618 continue 'requests;
619 }
620 let source = file.info(id);
621 let visible = current.binary_search(&id).is_ok();
622 let bytes = match file.bytes(id) {
623 Ok(bytes) => bytes,
624 Err(error) => {
625 #[cfg(feature = "delivery-trace")]
626 let publish_start = Instant::now();
627 input.publish(generation, Err(error), &output);
628 #[cfg(feature = "delivery-trace")]
629 {
630 work.summary.publication.record(publish_start);
631 work.summary.outcome = Outcome::SourceError;
632 }
633 return;
634 }
635 };
636 let text = display_text(&bytes);
637 let text = format!("{text}\n");
638 let too_long = text.len() > 40_004 || text.chars().count() > 10_001;
639 let mut style = LineStyle {
640 fallback: too_long,
641 ..LineStyle::default()
642 };
643 if too_long {
644 if source.old.is_some() {
645 state.old.fallback = Some(Fallback::LongLine);
646 }
647 if source.new.is_some() {
648 state.new.fallback = Some(Fallback::LongLine);
649 }
650 }
651 if !too_long {
652 let old_tokens = if source.old.is_some() {
653 state.old.advance(
654 &text,
655 &syntaxes,
656 &highlighter,
657 visible,
658 #[cfg(feature = "delivery-trace")]
659 &mut work,
660 #[cfg(feature = "delivery-trace")]
661 (LineId::Patch(id), true),
662 )
663 } else {
664 None
665 };
666 let new_tokens = if source.new.is_some() {
667 state.new.advance(
668 &text,
669 &syntaxes,
670 &highlighter,
671 visible,
672 #[cfg(feature = "delivery-trace")]
673 &mut work,
674 #[cfg(feature = "delivery-trace")]
675 (LineId::Patch(id), false),
676 )
677 } else {
678 None
679 };
680 if visible {
681 style.fallback |= (source.old.is_some() && old_tokens.is_none())
682 || (source.new.is_some() && new_tokens.is_none());
683 if source.kind == Kind::Deletion {
684 style.tokens = old_tokens.unwrap_or_default();
685 } else {
686 style.tokens = new_tokens.unwrap_or_default();
687 if source.kind == Kind::Context {
688 let old = old_tokens.unwrap_or_default();
689 if old != style.tokens {
690 style.old_tokens = Some(old);
691 }
692 }
693 }
694 }
695 }
696 state.next = id + 1;
697 #[cfg(feature = "delivery-trace")]
698 {
699 work.summary.processed_records += 1;
700 if visible {
701 work.summary.requested_records += 1;
702 work.summary.fallback_requested += usize::from(style.fallback);
703 } else if id < first {
704 work.summary.predecessor_records += 1;
705 } else {
706 work.summary.other_replay_records += 1;
707 }
708 }
709 if !checkpoints
710 .capture(state, hunk.lines.start, || input.current(generation))
711 {
712 #[cfg(feature = "delivery-trace")]
713 {
714 work.summary.outcome = Outcome::SupersededOrClosed;
715 }
716 continue 'requests;
717 }
718 if !visible {
719 continue;
720 }
721 if !too_long && let Some(partner) = file.partner(id) {
722 let bytes = match file.bytes(partner) {
723 Ok(bytes) => bytes,
724 Err(error) => {
725 #[cfg(feature = "delivery-trace")]
726 let publish_start = Instant::now();
727 input.publish(generation, Err(error), &output);
728 #[cfg(feature = "delivery-trace")]
729 {
730 work.summary.publication.record(publish_start);
731 work.summary.outcome = Outcome::SourceError;
732 }
733 return;
734 }
735 };
736 let other = display_text(&bytes);
737 if other.len() <= 40_000 && other.chars().count() <= 10_000 {
738 #[cfg(feature = "delivery-trace")]
739 let comparison_start = Instant::now();
740 let text = text.trim_end_matches('\n');
741 style.changed = if source.kind == Kind::Deletion {
742 changed_spans(text, &other, ChangeSide::Old)
743 } else {
744 changed_spans(&other, text, ChangeSide::New)
745 };
746 #[cfg(feature = "delivery-trace")]
747 work.summary.word_comparison.record(comparison_start);
748 }
749 }
750 #[cfg(feature = "delivery-trace")]
751 let publish_start = Instant::now();
752 let published = input.publish(
753 generation,
754 Ok(Reply {
755 origin,
756 source_error: None,
757 #[cfg(feature = "delivery-trace")]
758 generation,
759 epoch: request.epoch,
760 id: LineId::Patch(id),
761 style,
762 }),
763 &output,
764 );
765 #[cfg(feature = "delivery-trace")]
766 {
767 work.summary.publication.record(publish_start);
768 work.summary.style_replies += usize::from(published);
769 }
770 if !published {
771 #[cfg(feature = "delivery-trace")]
772 {
773 work.summary.outcome = Outcome::PublicationStopped;
774 }
775 continue 'requests;
776 }
777 }
778 }
779 }
780 #[cfg(feature = "delivery-trace")]
781 {
782 work.summary.outcome = Outcome::Complete;
783 }
784 }
785 });
786 Worker {
787 requests,
788 replies,
789 thread: Some(thread),
790 }
791}
792
793#[derive(Default)]
794pub struct Cache {
795 lines: hashbrown::HashMap<LineId, LineStyle, RandomState>,
796 order: VecDeque<LineId>,
797 visible: Vec<LineId>,
798 bytes: usize,
799 #[cfg(feature = "owner-census")]
800 activity: cache_activity::Activity,
801}
802
803impl Cache {
804 pub(crate) fn protect_visible(&mut self, mut ids: Vec<LineId>) {
805 ids.sort_unstable();
806 ids.dedup();
807 if self.visible == ids {
808 return;
809 }
810 #[cfg(feature = "owner-census")]
811 let before = cache_activity::Point::observe(self);
812 self.visible = ids;
813 self.trim();
814 #[cfg(feature = "owner-census")]
815 self.activity
816 .release(before, cache_activity::Point::observe(self));
817 }
818
819 fn trim(&mut self) {
820 while self.bytes > 32 * 1024 * 1024 {
821 let Some(index) = self
822 .order
823 .iter()
824 .position(|id| self.visible.binary_search(id).is_err())
825 else {
826 break;
827 };
828 let id = self.order.remove(index).expect("Eviction index is missing");
829 let style = self.lines.remove(&id).expect("Queued style is missing");
830 self.bytes -= style.cache_weight();
831 }
832 }
833
834 pub(crate) fn invalidate(&mut self, file: &FileIndex) {
835 #[cfg(feature = "owner-census")]
836 let before = cache_activity::Point::observe(self);
837 self.order.retain(|id| !id.belongs_to(file));
838 self.lines.retain(|id, style| {
839 if !id.belongs_to(file) {
840 return true;
841 }
842 self.bytes -= style.cache_weight();
843 false
844 });
845 #[cfg(feature = "owner-census")]
846 {
847 let after = cache_activity::Point::observe(self);
848 self.activity.invalidate(before, after);
849 }
850 }
851 pub fn get(&self, id: impl Into<LineId>) -> Option<&LineStyle> {
852 self.lines.get(&id.into())
853 }
854 pub fn insert(&mut self, id: impl Into<LineId>, style: LineStyle) {
855 let id = id.into();
856 if self.lines.contains_key(&id) {
857 #[cfg(feature = "owner-census")]
858 {
859 self.activity.duplicates += 1;
860 }
861 return;
862 }
863 #[cfg(feature = "owner-census")]
864 let before = cache_activity::Point::observe(self);
865 self.bytes += style.cache_weight();
866 self.lines.insert(id, style);
867 self.order.push_back(id);
868 #[cfg(feature = "owner-census")]
869 let inserted = cache_activity::Point::observe(self);
870 self.trim();
871 #[cfg(feature = "owner-census")]
872 {
873 let settled = cache_activity::Point::observe(self);
874 self.activity.admit(before, inserted, settled);
875 }
876 }
877}
878
879#[cfg(test)]
880#[path = "highlight/cache_tests.rs"]
881mod cache_tests;
882
883#[cfg(test)]
884#[path = "highlight/syntax_tests.rs"]
885mod syntax_tests;
886
887#[cfg(test)]
888#[path = "highlight/mailbox_tests.rs"]
889mod mailbox_tests;