1use std::hash::Hash;
2use std::num::NonZeroUsize;
3use std::ops::DerefMut;
4use std::sync::OnceLock;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::{collections::HashSet, ops::Deref};
7
8use comemo::{Track, Tracked};
9use ecow::EcoString;
10use lsp_types::Url;
11use parking_lot::Mutex;
12use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
13use rustc_hash::FxHashMap;
14use tinymist_analysis::docs::DocString;
15use tinymist_analysis::stats::{AllocStats, QueryStatReportEntry};
16use tinymist_analysis::syntax::classify_def_loosely;
17use tinymist_analysis::ty::{BuiltinTy, InsTy, term_value};
18use tinymist_analysis::{analyze_expr_, analyze_import_};
19use tinymist_lint::{KnownIssues, LintInfo};
20use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
21use tinymist_std::hash::{FxDashMap, hash128};
22use tinymist_std::typst::TypstDocument;
23use tinymist_world::debug_loc::DataSource;
24use tinymist_world::package::registry::PackageIndexEntry;
25use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
26use tinymist_world::{DETACHED_ENTRY, EntryReader};
27use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult};
28use typst::foundations::{Bytes, IntoValue, Module, NativeElement, StyleChain, Styles};
29use typst::introspection::Introspector;
30use typst::introspection::PagedPosition as Position;
31use typst::model::BibliographyElem;
32use typst::syntax::package::PackageManifest;
33use typst::syntax::{Span, VirtualPath};
34use typst_shim::eval::{Eval, eval_compat};
35use typst_shim::syntax::VirtualPathExt;
36
37use super::{LspQuerySnapshot, TypeEnv};
38use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
39use crate::analysis::prelude::*;
40use crate::analysis::{
41 AnalysisStats, BibInfo, CompletionFeat, Definition, PathKind, QueryStatGuard,
42 SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
43 TypeInfo, analyze_signature, bib_info, definition, post_type_check,
44};
45use crate::docs::{DefDocs, TidyModuleDocs};
46use crate::syntax::{
47 Decl, DefKind, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, SyntaxClass,
48 classify_syntax, construct_module_dependencies, is_mark, resolve_id_by_path,
49 scan_workspace_files,
50};
51use crate::upstream::{Tooltip, tooltip_};
52use crate::{
53 ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
54};
55
56macro_rules! interned_str {
57 ($name:ident, $value:expr) => {
58 static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
59 };
60}
61
62#[derive(Default, Clone)]
64pub struct Analysis {
65 pub position_encoding: PositionEncoding,
67 pub allow_overlapping_token: bool,
69 pub allow_multiline_token: bool,
71 pub remove_html: bool,
73 pub support_client_codelens: bool,
75 pub extended_code_action: bool,
83 pub completion_feat: CompletionFeat,
85 pub color_theme: ColorTheme,
87 pub lint: TaskWhen,
89 pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
91 pub workers: Arc<AnalysisGlobalWorkers>,
93 pub local_packages: Arc<Mutex<OnceLock<EcoVec<PackageIndexEntry>>>>,
95 pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
97 pub caches: AnalysisGlobalCaches,
99 pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
101 pub stats: Arc<AnalysisStats>,
103}
104
105impl Analysis {
106 pub fn enter(&self, g: LspComputeGraph) -> LocalContextGuard {
108 self.enter_(g, self.lock_revision(None))
109 }
110
111 pub(crate) fn enter_(&self, g: LspComputeGraph, mut lg: AnalysisRevLock) -> LocalContextGuard {
113 let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst);
114 let slot = self
115 .analysis_rev_cache
116 .lock()
117 .find_revision(g.world().revision(), &lg);
118 let tokens = lg.tokens.take();
119 LocalContextGuard {
120 _rev_lock: lg,
121 local: LocalContext {
122 tokens,
123 caches: AnalysisLocalCaches::default(),
124 shared: Arc::new(SharedContext {
125 slot,
126 lifetime,
127 graph: g,
128 analysis: self.clone(),
129 }),
130 },
131 }
132 }
133
134 pub fn query_snapshot(
136 self: Arc<Self>,
137 snap: LspComputeGraph,
138 req: Option<&CompilerQueryRequest>,
139 ) -> LspQuerySnapshot {
140 let rev_lock = self.lock_revision(req);
141 LspQuerySnapshot {
142 snap,
143 analysis: self,
144 rev_lock,
145 }
146 }
147
148 #[must_use]
150 pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock {
151 let mut grid = self.analysis_rev_cache.lock();
152
153 AnalysisRevLock {
154 tokens: match req {
155 Some(CompilerQueryRequest::SemanticTokensFull(req)) => Some(
156 SemanticTokenCache::acquire(self.tokens_caches.clone(), &req.path, None),
157 ),
158 Some(CompilerQueryRequest::SemanticTokensDelta(req)) => {
159 Some(SemanticTokenCache::acquire(
160 self.tokens_caches.clone(),
161 &req.path,
162 Some(&req.previous_result_id),
163 ))
164 }
165 _ => None,
166 },
167 inner: grid.manager.lock_estimated(),
168 grid: self.analysis_rev_cache.clone(),
169 }
170 }
171
172 pub fn clear_cache(&self) {
174 self.caches.signatures.clear();
175 self.caches.docstrings.clear();
176 self.caches.def_signatures.clear();
177 self.caches.static_signatures.clear();
178 self.caches.terms.clear();
179 *self.local_packages.lock() = OnceLock::default();
180 self.tokens_caches.lock().clear();
181 self.analysis_rev_cache.lock().clear();
182 }
183
184 pub fn report_query_stats(&self) -> String {
186 self.stats.report()
187 }
188
189 pub fn report_query_stats_json(&self) -> Vec<QueryStatReportEntry> {
191 self.stats.report_json()
192 }
193
194 pub fn report_alloc_stats(&self) -> String {
196 AllocStats::report()
197 }
198
199 pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
201 interned_str!(INTERNED, "editor.action.triggerSuggest");
202
203 (self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
204 }
205
206 pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
208 interned_str!(INTERNED, "editor.action.triggerParameterHints");
209 (self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
210 }
211
212 pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
219 if !self.completion_feat.trigger_on_snippet_placeholders {
220 return None;
221 }
222
223 self.trigger_suggest(context)
224 }
225
226 pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
228 interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
229 if !self.completion_feat.trigger_on_snippet_placeholders {
230 return self.trigger_parameter_hints(context);
231 }
232
233 (self.completion_feat.trigger_suggest_and_parameter_hints && context)
234 .then(|| INTERNED.clone())
235 }
236}
237
238pub trait PeriscopeProvider {
240 fn periscope_at(
242 &self,
243 _ctx: &mut LocalContext,
244 _doc: &TypstDocument,
245 _pos: Position,
246 ) -> Option<String> {
247 None
248 }
249}
250
251pub struct LocalContextGuard {
253 pub local: LocalContext,
255 _rev_lock: AnalysisRevLock,
257}
258
259impl Deref for LocalContextGuard {
260 type Target = LocalContext;
261
262 fn deref(&self) -> &Self::Target {
263 &self.local
264 }
265}
266
267impl DerefMut for LocalContextGuard {
268 fn deref_mut(&mut self) -> &mut Self::Target {
269 &mut self.local
270 }
271}
272
273impl Drop for LocalContextGuard {
275 fn drop(&mut self) {
276 self.gc();
277 }
278}
279
280impl LocalContextGuard {
281 fn gc(&self) {
282 let lifetime = self.lifetime;
283 loop {
284 let latest_clear_lifetime = self.analysis.caches.clear_lifetime.load(Ordering::Relaxed);
285 if latest_clear_lifetime >= lifetime {
286 return;
287 }
288
289 if self.analysis.caches.clear_lifetime.compare_exchange(
290 latest_clear_lifetime,
291 lifetime,
292 Ordering::SeqCst,
293 Ordering::SeqCst,
294 ) != Ok(latest_clear_lifetime)
295 {
296 continue;
297 }
298
299 break;
300 }
301
302 let retainer = |l: u64| lifetime.saturating_sub(l) < 60;
303 let caches = &self.analysis.caches;
304 caches.def_signatures.retain(|(l, _)| retainer(*l));
305 caches.static_signatures.retain(|(l, _)| retainer(*l));
306 caches.terms.retain(|(l, _)| retainer(*l));
307 caches.signatures.retain(|(l, _)| retainer(*l));
308 caches.docstrings.retain(|(l, _)| retainer(*l));
309 }
310}
311
312pub struct LocalContext {
315 pub(crate) tokens: Option<SemanticTokenContext>,
317 pub caches: AnalysisLocalCaches,
319 pub shared: Arc<SharedContext>,
321}
322
323impl Deref for LocalContext {
324 type Target = Arc<SharedContext>;
325
326 fn deref(&self) -> &Self::Target {
327 &self.shared
328 }
329}
330
331impl DerefMut for LocalContext {
332 fn deref_mut(&mut self) -> &mut Self::Target {
333 &mut self.shared
334 }
335}
336
337impl LocalContext {
338 #[cfg(test)]
340 pub fn test_package_list(&mut self, f: impl FnOnce() -> Vec<PackageIndexEntry> + Clone) {
341 self.world().registry.test_package_list(f.clone());
342 self.analysis
343 .local_packages
344 .lock()
345 .get_or_init(|| f().into_iter().collect());
346 }
347
348 #[cfg(test)]
350 pub fn test_completion_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
351 self.caches.completion_files.get_or_init(f);
352 }
353
354 #[cfg(test)]
356 pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
357 self.caches.root_files.get_or_init(f);
358 }
359
360 pub(crate) fn completion_files(&self, pref: &PathKind) -> impl Iterator<Item = &TypstFileId> {
362 let regexes = pref.ext_matcher();
363 self.caches
364 .completion_files
365 .get_or_init(|| {
366 if let Some(root) = self.world().entry_state().workspace_root() {
367 scan_workspace_files(&root, PathKind::Special.ext_matcher(), |path| {
368 VirtualPath::virtualize(&root, &root.join(path))
369 .ok()
370 .map(|path| WorkspaceResolver::workspace_file(Some(&root), path))
371 })
372 .into_iter()
373 .flatten()
374 .collect()
375 } else {
376 vec![]
377 }
378 })
379 .iter()
380 .filter(move |fid| {
381 fid.vpath()
382 .as_rooted_path_compat()
383 .extension()
384 .and_then(|path| path.to_str())
385 .is_some_and(|path| regexes.is_match(path))
386 })
387 }
388
389 pub fn source_files(&self) -> &Vec<TypstFileId> {
391 self.caches.root_files.get_or_init(|| {
392 self.completion_files(&PathKind::Source {
393 allow_package: false,
394 })
395 .copied()
396 .collect()
397 })
398 }
399
400 pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
402 if self.caches.module_deps.get().is_some() {
403 self.caches.module_deps.get().unwrap()
404 } else {
405 let deps = construct_module_dependencies(self);
408 self.caches.module_deps.get_or_init(|| deps)
409 }
410 }
411
412 pub fn depended_source_files(&self) -> EcoVec<TypstFileId> {
414 let mut ids = self.depended_files();
415 let preference = PathKind::Source {
416 allow_package: false,
417 };
418 ids.retain(|id| preference.is_match(id.vpath().as_rooted_path_compat()));
419 ids
420 }
421
422 pub fn depended_files(&self) -> EcoVec<TypstFileId> {
425 self.world().depended_files()
426 }
427
428 pub fn shared(&self) -> &Arc<SharedContext> {
430 &self.shared
431 }
432
433 pub fn shared_(&self) -> Arc<SharedContext> {
435 self.shared.clone()
436 }
437
438 pub fn fork_for_search(&mut self) -> SearchCtx<'_> {
440 SearchCtx {
441 ctx: self,
442 searched: Default::default(),
443 worklist: Default::default(),
444 }
445 }
446
447 pub(crate) fn preload_package(&self, entry_point: TypstFileId) {
448 self.shared_().preload_package(entry_point);
449 }
450
451 pub(crate) fn preload_expr_stages<I>(&self, files: I)
452 where
453 I: IntoIterator<Item = TypstFileId>,
454 {
455 self.shared_().preload_expr_stages(files);
456 }
457
458 pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst_shim::eval::Vm) -> T) -> T {
459 crate::upstream::with_vm((self.world() as &dyn World).track(), f)
460 }
461
462 pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
463 SharedContext::const_eval(rr)
464 }
465
466 pub(crate) fn mini_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
467 self.const_eval(rr)
468 .or_else(|| self.with_vm(|vm| rr.eval(vm).ok()))
469 }
470
471 pub(crate) fn cached_tokens(&mut self, source: &Source) -> (SemanticTokens, Option<String>) {
472 let tokens = crate::analysis::semantic_tokens::get_semantic_tokens(self.shared(), source);
473
474 let result_id = self.tokens.as_ref().map(|t| {
475 let id = t.next.revision;
476 t.next
477 .data
478 .set(tokens.clone())
479 .unwrap_or_else(|_| panic!("unexpected slot overwrite {id}"));
480 id.to_string()
481 });
482 (tokens, result_id)
483 }
484
485 pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
487 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
488 }
489
490 pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo {
492 let id = source.id();
493 let cache = &self.caches.modules.entry(id).or_default().expr_stage;
494 cache.get_or_init(|| self.shared.expr_stage(source)).clone()
495 }
496
497 pub(crate) fn type_check(&mut self, source: &Source) -> Arc<TypeInfo> {
499 let id = source.id();
500 let cache = &self.caches.modules.entry(id).or_default().type_check;
501 cache.get_or_init(|| self.shared.type_check(source)).clone()
502 }
503
504 pub(crate) fn lint(
505 &mut self,
506 source: &Source,
507 known_issues: &KnownIssues,
508 ) -> EcoVec<SourceDiagnostic> {
509 self.shared.lint(source, known_issues).diagnostics
510 }
511
512 pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
514 let cache = &self.caches.modules.entry(id).or_default().type_check;
515 cache
516 .clone()
517 .get_or_init(|| {
518 let source = self.source_by_id(id).ok();
519 source
520 .map(|s| self.shared.type_check(&s))
521 .unwrap_or_default()
522 })
523 .clone()
524 }
525
526 pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
527 let scheme = self.type_check_by_id(s.id()?);
528 let ty = scheme.type_of_span(s)?;
529 Some(scheme.simplify(ty, false))
530 }
531
532 pub(crate) fn def_docs(&mut self, def: &Definition) -> Option<DefDocs> {
533 match def.decl.kind() {
536 DefKind::Function => {
537 let sig = self.sig_of_def(def.clone())?;
538 let docs = crate::docs::sig_docs(self.shared(), &sig)?;
539 Some(DefDocs::Function(Box::new(docs)))
540 }
541 DefKind::Struct | DefKind::Constant | DefKind::Variable => {
542 let docs = crate::docs::var_docs(self.shared(), def.decl.span())?;
543 Some(DefDocs::Variable(docs))
544 }
545 DefKind::Module => {
546 let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
547 Some(DefDocs::Module(TidyModuleDocs {
548 docs: ei.module_docstring.docs.clone().unwrap_or_default(),
549 }))
550 }
551 DefKind::Reference => None,
552 }
553 }
554}
555
556#[derive(Clone)]
558pub struct SharedQueryCache<K, V> {
559 slots: Arc<FxDashMap<K, Arc<OnceLock<V>>>>,
560}
561
562impl<K, V> Default for SharedQueryCache<K, V>
563where
564 K: Eq + Hash,
565{
566 fn default() -> Self {
567 Self {
568 slots: Arc::new(FxDashMap::default()),
569 }
570 }
571}
572
573impl<K, V> SharedQueryCache<K, V>
574where
575 K: Eq + Hash,
576 V: Clone,
577{
578 pub fn get_or_init(&self, key: K, init: impl FnOnce() -> V) -> V {
580 let slot = self
581 .slots
582 .entry(key)
583 .or_insert_with(|| Arc::new(OnceLock::new()))
584 .clone();
585 slot.get_or_init(init).clone()
586 }
587}
588
589pub struct SharedContext {
591 pub lifetime: u64,
593 pub graph: LspComputeGraph,
597 pub analysis: Analysis,
599 slot: Arc<RevisionSlot<AnalysisRevSlot>>,
601}
602
603impl SharedContext {
604 pub fn revision(&self) -> usize {
606 self.slot.revision
607 }
608
609 pub(crate) fn position_encoding(&self) -> PositionEncoding {
611 self.analysis.position_encoding
612 }
613
614 pub fn world(&self) -> &LspWorld {
616 self.graph.world()
617 }
618
619 pub fn success_doc(&self) -> Option<&TypstDocument> {
621 self.graph.snap.success_doc.as_ref()
622 }
623
624 pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
626 crate::to_typst_position(position, self.analysis.position_encoding, src)
627 }
628
629 pub fn to_typst_pos_offset(
631 &self,
632 source: &Source,
633 position: LspPosition,
634 shift: usize,
635 ) -> Option<usize> {
636 let offset = self.to_typst_pos(position, source)?;
637 Some(ceil_char_boundary(source.text(), offset + shift))
638 }
639
640 pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
642 crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src)
643 }
644
645 pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<Range<usize>> {
647 crate::to_typst_range(position, self.analysis.position_encoding, src)
648 }
649
650 pub fn to_lsp_range(&self, position: Range<usize>, src: &Source) -> LspRange {
652 crate::to_lsp_range(position, src, self.analysis.position_encoding)
653 }
654
655 pub fn to_lsp_range_(&self, position: Range<usize>, fid: TypstFileId) -> Option<LspRange> {
657 let ext = fid
658 .vpath()
659 .as_rootless_path_compat()
660 .extension()
661 .and_then(|ext| ext.to_str());
662 if matches!(ext, Some("yaml" | "yml" | "bib")) {
664 let bytes = self.file_by_id(fid).ok()?;
665 let bytes_len = bytes.len();
666 let loc = loc_info(bytes)?;
667 let start = find_loc(bytes_len, &loc, position.start, self.position_encoding())?;
669 let end = find_loc(bytes_len, &loc, position.end, self.position_encoding())?;
670 return Some(LspRange { start, end });
671 }
672
673 let source = self.source_by_id(fid).ok()?;
674
675 Some(self.to_lsp_range(position, &source))
676 }
677
678 pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
680 self.world().path_for_id(id)
681 }
682
683 pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
685 self.world().uri_for_id(fid)
686 }
687
688 pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
690 self.world().file_id_by_path(path)
691 }
692
693 pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
695 self.world().file(fid)
696 }
697
698 pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
700 self.world().source(fid)
701 }
702
703 pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
705 self.source_by_id(self.file_id_by_path(path)?)
706 }
707
708 pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
711 let node = LinkedNode::new(source.root()).find(span)?;
712 let cursor = node.offset() + 1;
713 classify_syntax(node, cursor)
714 }
715
716 pub fn classify_for_decl<'s>(
720 &self,
721 source: &'s Source,
722 position: LspPosition,
723 ) -> Option<SyntaxClass<'s>> {
724 let cursor = self.to_typst_pos_offset(source, position, 1)?;
725 let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
726
727 if cursor == node.offset() + 1 && is_mark(node.kind()) {
730 let prev_leaf = node.prev_leaf();
731 if let Some(prev_leaf) = prev_leaf
732 && prev_leaf.range().end == node.offset()
733 {
734 node = prev_leaf;
735 }
736 }
737
738 classify_syntax(node, cursor)
739 }
740
741 pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
743 self.world().font_resolver.describe_font(&font)
744 }
745
746 pub fn non_preview_packages(&self) -> EcoVec<PackageIndexEntry> {
749 #[cfg(feature = "local-registry")]
750 let it = || {
751 crate::package::list_package(
752 self.world(),
753 crate::package::PackageFilter::ExceptFor(EcoString::inline("preview")),
754 )
755 };
756 #[cfg(not(feature = "local-registry"))]
757 let it = || Default::default();
758 self.analysis.local_packages.lock().get_or_init(it).clone()
759 }
760
761 pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
762 Some(match rr {
763 ast::Expr::None(_) => Value::None,
764 ast::Expr::Auto(_) => Value::Auto,
765 ast::Expr::Bool(v) => Value::Bool(v.get()),
766 ast::Expr::Int(v) => Value::Int(v.get()),
767 ast::Expr::Float(v) => Value::Float(v.get()),
768 ast::Expr::Numeric(v) => Value::numeric(v.get()),
769 ast::Expr::Str(v) => Value::Str(v.get().into()),
770 _ => return None,
771 })
772 }
773
774 pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult<Module> {
776 let source = self.source_by_id(fid).at(Span::detached())?;
777 self.module_by_src(source)
778 }
779
780 pub fn module_by_str(&self, rr: String) -> Option<Module> {
782 let src = Source::new(*DETACHED_ENTRY, rr);
783 self.module_by_src(src).ok()
784 }
785
786 pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
788 eval_compat(&self.world(), &source)
789 }
790
791 pub fn module_by_syntax(self: &Arc<Self>, source: &SyntaxNode) -> Option<Value> {
793 self.module_term_by_syntax(source, true)
794 .and_then(|ty| ty.value())
795 }
796
797 pub fn module_term_by_syntax(self: &Arc<Self>, source: &SyntaxNode, value: bool) -> Option<Ty> {
800 let (src, scope) = self.analyze_import(source);
801 if let Some(scope) = scope {
802 return Some(match scope {
803 Value::Module(m) if m.file_id().is_some() => {
804 Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into()))
805 }
806 scope => Ty::Value(InsTy::new(scope)),
807 });
808 }
809
810 match src {
811 Some(Value::Str(s)) => {
812 let id = resolve_id_by_path(self.world(), source.span().id()?, s.as_str())?;
813
814 Some(if value {
815 Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?)))
816 } else {
817 Ty::Builtin(BuiltinTy::Module(Decl::module(id).into()))
818 })
819 }
820 _ => None,
821 }
822 }
823
824 pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
826 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
827 }
828
829 pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
831 let mut route = ExprRoute::default();
832 self.expr_stage_(source, &mut route)
833 }
834
835 pub(crate) fn expr_stage_(
837 self: &Arc<Self>,
838 source: &Source,
839 route: &mut ExprRoute,
840 ) -> ExprInfo {
841 use crate::syntax::expr_of;
842 let guard = self.query_stat(source.id(), "expr_stage");
843 self.slot.expr_stage.compute(hash128(&source), |prev| {
844 expr_of(self.clone(), source.clone(), route, guard, prev)
845 })
846 }
847
848 pub(crate) fn exports_of(
849 self: &Arc<Self>,
850 source: &Source,
851 route: &mut ExprRoute,
852 ) -> Option<Arc<LazyHash<LexicalScope>>> {
853 if let Some(s) = route.get(&source.id()) {
854 return s.clone();
855 }
856
857 Some(self.expr_stage_(source, route).exports.clone())
858 }
859
860 pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
862 let mut route = TypeEnv::default();
863 self.type_check_(source, &mut route)
864 }
865
866 pub(crate) fn type_check_(
868 self: &Arc<Self>,
869 source: &Source,
870 route: &mut TypeEnv,
871 ) -> Arc<TypeInfo> {
872 use crate::analysis::type_check;
873
874 let ei = self.expr_stage(source);
875 let guard = self.query_stat(source.id(), "type_check");
876 self.slot.type_check.compute(hash128(&ei), |prev| {
877 if let Some(cache_hint) = prev.filter(|prev| prev.revision == ei.revision) {
879 return cache_hint;
880 }
881
882 guard.miss();
883 type_check(self.clone(), ei, route)
884 })
885 }
886
887 #[typst_macros::time(span = source.root().span())]
889 pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
890 let ei = self.expr_stage(source);
891 let ti = self.type_check(source);
892 let guard = self.query_stat(source.id(), "lint");
893 self.slot.lint.compute(hash128(&(&ei, &ti, issues)), |_| {
894 guard.miss();
895 tinymist_lint::lint_file(self.world(), &ei, ti, issues.clone())
896 })
897 }
898
899 pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
900 crate::log_debug_ct!("convert runtime func {func:?}");
901 analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
902 }
903
904 pub(crate) fn type_of_value(self: &Arc<Self>, val: &Value) -> Ty {
905 crate::log_debug_ct!("convert runtime value {val:?}");
906
907 let cache_key = val;
909 let cached = self
910 .analysis
911 .caches
912 .terms
913 .m
914 .get(&hash128(&cache_key))
915 .and_then(|slot| (cache_key == &slot.1.0).then_some(slot.1.1.clone()));
916 if let Some(cached) = cached {
917 return cached;
918 }
919
920 let res = term_value(val);
921
922 self.analysis
923 .caches
924 .terms
925 .m
926 .entry(hash128(&cache_key))
927 .or_insert_with(|| (self.lifetime, (cache_key.clone(), res.clone())));
928
929 res
930 }
931
932 pub(crate) fn def_of_span(self: &Arc<Self>, source: &Source, span: Span) -> Option<Definition> {
934 let syntax = self.classify_span(source, span)?;
935 definition(self, source, syntax)
936 }
937
938 pub(crate) fn def_of_syntax(
943 self: &Arc<Self>,
944 source: &Source,
945 syntax: SyntaxClass,
946 ) -> Option<Definition> {
947 definition(self, source, syntax)
948 }
949
950 pub(crate) fn def_of_syntax_or_dyn(
958 self: &Arc<Self>,
959 source: &Source,
960 syntax: SyntaxClass,
961 ) -> Option<Definition> {
962 let def = self.def_of_syntax(source, syntax.clone());
963 match def.as_ref().map(|d| d.decl.kind()) {
964 Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
966 Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
967 }
968
969 let know_ty_well = def
971 .as_ref()
972 .and_then(|d| self.simplified_type_of_span(d.decl.span()))
973 .filter(|ty| !matches!(ty, Ty::Any))
974 .is_some();
975 if know_ty_well {
976 return def;
977 }
978
979 let def_ref = def.as_ref();
980 let def_name = || Some(def_ref?.name().clone());
981 let dyn_def = self
982 .analyze_expr(syntax.node())
983 .iter()
984 .find_map(|(value, _)| {
985 let def = Definition::from_value(value.clone(), def_name)?;
986 None.or_else(|| {
987 let source = self.source_by_id(def.decl.file_id()?).ok()?;
988 let node = LinkedNode::new(source.root()).find(def.decl.span())?;
989 let def_at_the_span = classify_def_loosely(node)?;
990 self.def_of_span(&source, def_at_the_span.name()?.span())
991 })
992 .or(Some(def))
993 });
994
995 dyn_def.or(def)
997 }
998
999 pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1000 let source = self.source_by_id(span.id()?).ok()?;
1001 let (ti, ty) = self.type_of_span_(&source, span)?;
1002 Some(ti.simplify(ty, false))
1003 }
1004
1005 pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1006 let source = self.source_by_id(span.id()?).ok()?;
1007 Some(self.type_of_span_(&source, span)?.1)
1008 }
1009
1010 pub(crate) fn type_of_span_(
1011 self: &Arc<Self>,
1012 source: &Source,
1013 span: Span,
1014 ) -> Option<(Arc<TypeInfo>, Ty)> {
1015 let ti = self.type_check(source);
1016 let ty = ti.type_of_span(span)?;
1017 Some((ti, ty))
1018 }
1019
1020 pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
1021 let id = node.span().id()?;
1022 let source = self.source_by_id(id).ok()?;
1023 let ty_chk = self.type_check(&source);
1024
1025 let ty = post_type_check(self.clone(), &ty_chk, node.clone())
1026 .or_else(|| ty_chk.type_of_span(node.span()))?;
1027 Some(ty_chk.simplify(ty, false))
1028 }
1029
1030 pub(crate) fn sig_of_def(self: &Arc<Self>, def: Definition) -> Option<Signature> {
1031 crate::log_debug_ct!("check definition func {def:?}");
1032 let source = def.decl.file_id().and_then(|id| self.source_by_id(id).ok());
1033 analyze_signature(self, SignatureTarget::Def(source, def))
1034 }
1035
1036 pub(crate) fn def_docs(self: &Arc<Self>, def: &Definition) -> Option<DefDocs> {
1037 match def.decl.kind() {
1038 DefKind::Function => {
1039 let sig = self.sig_of_def(def.clone())?;
1040 let docs = crate::docs::sig_docs(self, &sig)?;
1041 Some(DefDocs::Function(Box::new(docs)))
1042 }
1043 DefKind::Struct | DefKind::Constant | DefKind::Variable => {
1044 let docs = crate::docs::var_docs(self, def.decl.span())?;
1045 Some(DefDocs::Variable(docs))
1046 }
1047 DefKind::Module => {
1048 let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
1049 Some(DefDocs::Module(TidyModuleDocs {
1050 docs: ei.module_docstring.docs.clone().unwrap_or_default(),
1051 }))
1052 }
1053 DefKind::Reference => None,
1054 }
1055 }
1056
1057 pub(crate) fn sig_of_type(self: &Arc<Self>, ti: &TypeInfo, ty: Ty) -> Option<Signature> {
1058 super::sig_of_type(self, ti, ty)
1059 }
1060
1061 pub(crate) fn sig_of_type_or_dyn(
1062 self: &Arc<Self>,
1063 ti: &TypeInfo,
1064 callee_ty: Ty,
1065 callee: &SyntaxNode,
1066 ) -> Option<Signature> {
1067 self.sig_of_type(ti, callee_ty).or_else(|| {
1068 self.analyze_expr(callee).iter().find_map(|(value, _)| {
1069 let Value::Func(callee) = value else {
1070 return None;
1071 };
1072
1073 analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
1075 })
1076 })
1077 }
1078
1079 pub fn analyze_import(&self, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
1086 if let Some(v) = source.cast::<ast::Expr>().and_then(Self::const_eval) {
1087 return (Some(v), None);
1088 }
1089 let token = &self.analysis.workers.import;
1090 token.enter(|| analyze_import_(self.world(), source))
1091 }
1092
1093 pub fn analyze_expr(&self, source: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
1095 let token = &self.analysis.workers.expression;
1096 token.enter(|| analyze_expr_(self.world(), source))
1097 }
1098
1099 pub fn analyze_bib(&self, introspector: &dyn Introspector) -> Option<Arc<BibInfo>> {
1101 let world = self.world();
1102 let world = (world as &dyn World).track();
1103
1104 analyze_bib(world, introspector.track())
1105 }
1106
1107 pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
1113 let token = &self.analysis.workers.tooltip;
1114 token.enter(|| tooltip_(self.world(), source, cursor))
1115 }
1116
1117 pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1119 crate::package::get_manifest(self.world(), toml_id)
1120 }
1121
1122 pub fn compute_signature(
1124 self: &Arc<Self>,
1125 func: SignatureTarget,
1126 compute: impl FnOnce(&Arc<Self>) -> Option<Signature> + Send + Sync + 'static,
1127 ) -> Option<Signature> {
1128 let res = match func {
1129 SignatureTarget::Def(src, def) => self
1130 .analysis
1131 .caches
1132 .def_signatures
1133 .entry(hash128(&(src, def.clone())), self.lifetime),
1134 SignatureTarget::SyntaxFast(source, span) => {
1135 let cache_key = (source, span, true);
1136 self.analysis
1137 .caches
1138 .static_signatures
1139 .entry(hash128(&cache_key), self.lifetime)
1140 }
1141 SignatureTarget::Syntax(source, span) => {
1142 let cache_key = (source, span);
1143 self.analysis
1144 .caches
1145 .static_signatures
1146 .entry(hash128(&cache_key), self.lifetime)
1147 }
1148 SignatureTarget::Convert(rt) => self
1149 .analysis
1150 .caches
1151 .signatures
1152 .entry(hash128(&(&rt, true)), self.lifetime),
1153 SignatureTarget::Runtime(rt) => self
1154 .analysis
1155 .caches
1156 .signatures
1157 .entry(hash128(&rt), self.lifetime),
1158 };
1159 res.get_or_init(|| compute(self)).clone()
1160 }
1161
1162 pub(crate) fn compute_docstring(
1163 self: &Arc<Self>,
1164 fid: TypstFileId,
1165 docs: String,
1166 kind: DefKind,
1167 ) -> Option<Arc<DocString>> {
1168 let res = self
1169 .analysis
1170 .caches
1171 .docstrings
1172 .entry(hash128(&(fid, &docs, kind)), self.lifetime);
1173 res.get_or_init(|| {
1174 crate::syntax::docs::do_compute_docstring(self, fid, docs, kind).map(Arc::new)
1175 })
1176 .clone()
1177 }
1178
1179 pub fn remove_html(&self, markup: EcoString) -> EcoString {
1181 if !self.analysis.remove_html {
1182 return markup;
1183 }
1184
1185 static REMOVE_HTML_COMMENT_REGEX: LazyLock<regex::Regex> =
1186 LazyLock::new(|| regex::Regex::new(r#"<!--[\s\S]*?-->"#).unwrap());
1187 REMOVE_HTML_COMMENT_REGEX
1188 .replace_all(&markup, "")
1189 .trim()
1190 .into()
1191 }
1192
1193 fn query_stat(&self, id: TypstFileId, query: &'static str) -> QueryStatGuard {
1194 self.analysis.stats.stat(Some(id), query)
1195 }
1196
1197 pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1200 }
1210
1211 pub(crate) fn preload_expr_stages<I>(self: Arc<Self>, files: I)
1212 where
1213 I: IntoIterator<Item = TypstFileId>,
1214 {
1215 let files: Vec<_> = files.into_iter().collect();
1216 files.par_iter().for_each(|fid| {
1217 crate::log_debug_ct!("preload expr_stage {fid:?}");
1218 let Some(source) = self.source_by_id(*fid).ok() else {
1219 return;
1220 };
1221 self.expr_stage(&source);
1222 });
1223 }
1224
1225 pub(crate) fn preload_package(self: Arc<Self>, entry_point: TypstFileId) {
1226 crate::log_debug_ct!("preload package start {entry_point:?}");
1227
1228 #[derive(Clone)]
1229 struct Preloader {
1230 shared: Arc<SharedContext>,
1231 analyzed: Arc<Mutex<HashSet<TypstFileId>>>,
1232 }
1233
1234 impl Preloader {
1235 fn work(&self, fid: TypstFileId) {
1236 crate::log_debug_ct!("preload package {fid:?}");
1237 let Some(source) = self.shared.source_by_id(fid).ok() else {
1238 return;
1239 };
1240 let exprs = self.shared.expr_stage(&source);
1241 self.shared.type_check(&source);
1242 exprs.imports.iter().for_each(|(fid, _)| {
1243 if !self.analyzed.lock().insert(*fid) {
1244 return;
1245 }
1246 self.work(*fid);
1247 })
1248 }
1249 }
1250
1251 let preloader = Preloader {
1252 shared: self,
1253 analyzed: Arc::default(),
1254 };
1255
1256 preloader.work(entry_point);
1257 }
1258}
1259
1260type DeferredCompute<T> = Arc<OnceLock<T>>;
1262
1263#[derive(Clone)]
1264struct IncrCacheMap<K, V> {
1265 revision: usize,
1266 global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1267 prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1268 next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1269}
1270
1271impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
1272 fn default() -> Self {
1273 Self {
1274 revision: 0,
1275 global: Arc::default(),
1276 prev: Arc::default(),
1277 next: Arc::default(),
1278 }
1279 }
1280}
1281
1282impl<K, V> IncrCacheMap<K, V> {
1283 fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1284 where
1285 K: Clone + Eq + Hash,
1286 V: Clone,
1287 {
1288 let next = self.next.lock().entry(key.clone()).or_default().clone();
1289
1290 next.get_or_init(|| {
1291 let prev = self.prev.lock().get(&key).cloned();
1292 let prev = prev.and_then(|prev| prev.get().cloned());
1293 let prev = prev.or_else(|| {
1294 let global = self.global.lock();
1295 global.get(&key).map(|global| global.1.clone())
1296 });
1297
1298 let res = compute(prev);
1299
1300 let global = self.global.lock();
1301 let entry = global.entry(key.clone());
1302 use dashmap::mapref::entry::Entry;
1303 match entry {
1304 Entry::Occupied(mut entry) => {
1305 let (revision, _) = entry.get();
1306 if *revision < self.revision {
1307 entry.insert((self.revision, res.clone()));
1308 }
1309 }
1310 Entry::Vacant(entry) => {
1311 entry.insert((self.revision, res.clone()));
1312 }
1313 }
1314
1315 res
1316 })
1317 .clone()
1318 }
1319
1320 fn crawl(&self, revision: usize) -> Self {
1321 Self {
1322 revision,
1323 prev: self.next.clone(),
1324 global: self.global.clone(),
1325 next: Default::default(),
1326 }
1327 }
1328}
1329
1330#[derive(Clone)]
1331struct CacheMap<T> {
1332 m: Arc<FxDashMap<u128, (u64, T)>>,
1333 }
1335
1336impl<T> Default for CacheMap<T> {
1337 fn default() -> Self {
1338 Self {
1339 m: Default::default(),
1340 }
1342 }
1343}
1344
1345impl<T> CacheMap<T> {
1346 fn clear(&self) {
1347 self.m.clear();
1348 }
1349
1350 fn retain(&self, mut f: impl FnMut(&mut (u64, T)) -> bool) {
1351 self.m.retain(|_k, v| f(v));
1352 }
1353}
1354
1355impl<T: Default + Clone> CacheMap<T> {
1356 fn entry(&self, key: u128, lifetime: u64) -> T {
1357 let entry = self.m.entry(key);
1358 let entry = entry.or_insert_with(|| (lifetime, T::default()));
1359 entry.1.clone()
1360 }
1361}
1362
1363#[derive(Default)]
1365pub struct AnalysisGlobalWorkers {
1366 import: RateLimiter,
1368 expression: RateLimiter,
1370 tooltip: RateLimiter,
1372}
1373
1374#[derive(Default, Clone)]
1377pub struct AnalysisGlobalCaches {
1378 lifetime: Arc<AtomicU64>,
1379 clear_lifetime: Arc<AtomicU64>,
1380 def_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1381 static_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1382 signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1383 docstrings: CacheMap<DeferredCompute<Option<Arc<DocString>>>>,
1384 terms: CacheMap<(Value, Ty)>,
1385}
1386
1387#[derive(Default)]
1393pub struct AnalysisLocalCaches {
1394 modules: HashMap<TypstFileId, ModuleAnalysisLocalCache>,
1395 completion_files: OnceLock<Vec<TypstFileId>>,
1396 root_files: OnceLock<Vec<TypstFileId>>,
1397 module_deps: OnceLock<HashMap<TypstFileId, ModuleDependency>>,
1398}
1399
1400#[derive(Default)]
1405pub struct ModuleAnalysisLocalCache {
1406 expr_stage: OnceLock<ExprInfo>,
1407 type_check: OnceLock<Arc<TypeInfo>>,
1408}
1409
1410#[derive(Default)]
1413pub struct AnalysisRevCache {
1414 default_slot: AnalysisRevSlot,
1415 manager: RevisionManager<AnalysisRevSlot>,
1416}
1417
1418impl RevisionManagerLike for AnalysisRevCache {
1419 fn gc(&mut self, rev: usize) {
1420 self.manager.gc(rev);
1421
1422 {
1424 let mut max_ei = FxHashMap::default();
1425 let es = self.default_slot.expr_stage.global.lock();
1426 for r in es.iter() {
1427 let rev: &mut usize = max_ei.entry(r.1.fid).or_default();
1428 *rev = (*rev).max(r.1.revision);
1429 }
1430 es.retain(|_, r| r.1.revision == *max_ei.get(&r.1.fid).unwrap_or(&0));
1431 }
1432
1433 {
1434 let mut max_ti = FxHashMap::default();
1435 let ts = self.default_slot.type_check.global.lock();
1436 for r in ts.iter() {
1437 let rev: &mut usize = max_ti.entry(r.1.fid).or_default();
1438 *rev = (*rev).max(r.1.revision);
1439 }
1440 ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
1441 }
1442
1443 {
1444 let mut max_li = FxHashMap::default();
1445 let ts = self.default_slot.lint.global.lock();
1446 for r in ts.iter() {
1447 let rev: &mut usize = max_li.entry(r.1.fid).or_default();
1448 *rev = (*rev).max(r.1.revision);
1449 }
1450 ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
1451 }
1452 }
1453}
1454
1455impl AnalysisRevCache {
1456 fn clear(&mut self) {
1457 self.manager.clear();
1458 self.default_slot = Default::default();
1459 }
1460
1461 fn find_revision(
1463 &mut self,
1464 revision: NonZeroUsize,
1465 lg: &AnalysisRevLock,
1466 ) -> Arc<RevisionSlot<AnalysisRevSlot>> {
1467 lg.inner.access(revision);
1468 self.manager.find_revision(revision, |slot_base| {
1469 log::debug!("analysis revision {} is created", revision.get());
1470 slot_base
1471 .map(|slot| AnalysisRevSlot {
1472 revision: slot.revision,
1473 expr_stage: slot.data.expr_stage.crawl(revision.get()),
1474 type_check: slot.data.type_check.crawl(revision.get()),
1475 lint: slot.data.lint.crawl(revision.get()),
1476 })
1477 .unwrap_or_else(|| self.default_slot.clone())
1478 })
1479 }
1480}
1481
1482pub struct AnalysisRevLock {
1484 inner: RevisionLock,
1485 tokens: Option<SemanticTokenContext>,
1486 grid: Arc<Mutex<AnalysisRevCache>>,
1487}
1488
1489impl Drop for AnalysisRevLock {
1490 fn drop(&mut self) {
1491 let mut mu = self.grid.lock();
1492 let gc_revision = mu.manager.unlock(&mut self.inner);
1493
1494 if let Some(gc_revision) = gc_revision {
1495 let grid = self.grid.clone();
1496 rayon::spawn(move || {
1497 grid.lock().gc(gc_revision);
1498 });
1499 }
1500 }
1501}
1502
1503#[derive(Default, Clone)]
1504struct AnalysisRevSlot {
1505 revision: usize,
1506 expr_stage: IncrCacheMap<u128, ExprInfo>,
1507 type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
1508 lint: IncrCacheMap<u128, LintInfo>,
1509}
1510
1511impl Drop for AnalysisRevSlot {
1512 fn drop(&mut self) {
1513 log::debug!("analysis revision {} is dropped", self.revision);
1514 }
1515}
1516
1517fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
1518 while cursor < text.len() && !text.is_char_boundary(cursor) {
1520 cursor += 1;
1521 }
1522
1523 cursor.min(text.len())
1524}
1525
1526#[typst_macros::time]
1527#[comemo::memoize]
1528fn analyze_bib(
1529 world: Tracked<dyn World + '_>,
1530 introspector: Tracked<dyn Introspector + '_>,
1531) -> Option<Arc<BibInfo>> {
1532 let bib_elems = introspector.query(&BibliographyElem::ELEM.select());
1533 let bib_elem = bib_elems.iter().next()?.to_packed::<BibliographyElem>()?;
1534
1535 let csl_style = bib_elem.style.get_cloned(StyleChain::default()).derived;
1538
1539 let Value::Array(paths) = bib_elem.sources.clone().into_value() else {
1540 return None;
1541 };
1542 let elem_fid = bib_elem.span().id()?;
1543 let files = paths
1544 .into_iter()
1545 .flat_map(|path| path.cast().ok())
1546 .flat_map(|bib_path: EcoString| {
1547 let bib_fid = resolve_id_by_path(world.deref(), elem_fid, &bib_path)?;
1548 Some((bib_fid, world.file(bib_fid).ok()?))
1549 });
1550
1551 bib_info(csl_style, files)
1552}
1553
1554#[comemo::memoize]
1555fn loc_info(bytes: Bytes) -> Option<EcoVec<(usize, String)>> {
1556 let mut loc = EcoVec::new();
1557 let mut offset = 0;
1558 for line in bytes.split(|byte| *byte == b'\n') {
1559 loc.push((offset, String::from_utf8(line.to_owned()).ok()?));
1560 offset += line.len() + 1;
1561 }
1562 Some(loc)
1563}
1564
1565fn find_loc(
1566 len: usize,
1567 loc: &EcoVec<(usize, String)>,
1568 mut offset: usize,
1569 encoding: PositionEncoding,
1570) -> Option<LspPosition> {
1571 if offset > len {
1572 offset = len;
1573 }
1574
1575 let r = match loc.binary_search_by_key(&offset, |line| line.0) {
1576 Ok(i) => i,
1577 Err(i) => i - 1,
1578 };
1579
1580 let (start, s) = loc.get(r)?;
1581 let byte_offset = offset.saturating_sub(*start);
1582
1583 let column_prefix = if byte_offset <= s.len() {
1584 &s[..byte_offset]
1585 } else {
1586 let line = (r + 1) as u32;
1587 return Some(LspPosition { line, character: 0 });
1588 };
1589
1590 let line = r as u32;
1591 let character = match encoding {
1592 PositionEncoding::Utf8 => column_prefix.chars().count(),
1593 PositionEncoding::Utf16 => column_prefix.chars().map(|ch| ch.len_utf16()).sum(),
1594 } as u32;
1595
1596 Some(LspPosition { line, character })
1597}
1598
1599pub struct SearchCtx<'a> {
1601 pub ctx: &'a mut LocalContext,
1603 pub searched: HashSet<TypstFileId>,
1605 pub worklist: Vec<TypstFileId>,
1607}
1608
1609impl SearchCtx<'_> {
1610 pub fn push(&mut self, fid: TypstFileId) -> bool {
1612 if self.searched.insert(fid) {
1613 self.worklist.push(fid);
1614 true
1615 } else {
1616 false
1617 }
1618 }
1619
1620 pub fn push_dependents(&mut self, fid: TypstFileId) {
1622 let deps = self.ctx.module_dependencies().get(&fid);
1623 let dependents = deps.map(|dep| dep.dependents.clone()).into_iter().flatten();
1624 for dep in dependents {
1625 self.push(dep);
1626 }
1627 }
1628}
1629
1630#[derive(Default)]
1632pub struct RateLimiter {
1633 token: std::sync::Mutex<()>,
1634}
1635
1636impl RateLimiter {
1637 #[must_use]
1639 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1640 let _c = self.token.lock().unwrap();
1641 f()
1642 }
1643}