tinymist_query/analysis/
global.rs

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::{
38    AnalysisComponent, ComponentCoordinator, DependencyAdmission, ExprStage, LspQuerySnapshot,
39    type_check_component,
40};
41use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
42use crate::analysis::prelude::*;
43use crate::analysis::{
44    AnalysisStats, BibInfo, CompletionFeat, Definition, PathKind, QueryStatGuard,
45    SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
46    TypeInfo, analyze_signature, bib_info, definition, post_type_check,
47};
48use crate::docs::{DefDocs, TidyModuleDocs};
49use crate::syntax::{
50    Decl, DefKind, ExprInfo, LexicalScope, ModuleDependency, SyntaxClass, classify_syntax,
51    construct_module_dependencies, is_mark, resolve_id_by_path, scan_workspace_files,
52};
53use crate::upstream::{Tooltip, tooltip_};
54use crate::{
55    ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
56};
57
58macro_rules! interned_str {
59    ($name:ident, $value:expr) => {
60        static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
61    };
62}
63
64/// The analysis data holds globally.
65#[derive(Default, Clone)]
66pub struct Analysis {
67    /// The position encoding for the workspace.
68    pub position_encoding: PositionEncoding,
69    /// Whether to allow overlapping semantic tokens.
70    pub allow_overlapping_token: bool,
71    /// Whether to allow multiline semantic tokens.
72    pub allow_multiline_token: bool,
73    /// Whether to remove html from markup content in responses.
74    pub remove_html: bool,
75    /// Whether to add client-side code lens.
76    pub support_client_codelens: bool,
77    /// Whether to utilize the extended `tinymist.resolveCodeAction` at client
78    /// side.
79    ///
80    /// The extended feature by `tinymist.resolveCodeAction`:
81    /// - supports Snippet edit.
82    ///
83    /// The example implementation can be found in the VS Code extension.
84    pub extended_code_action: bool,
85    /// Tinymist's completion features.
86    pub completion_feat: CompletionFeat,
87    /// The editor's color theme.
88    pub color_theme: ColorTheme,
89    /// When to trigger the lint.
90    pub lint: TaskWhen,
91    /// The periscope provider.
92    pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
93    /// The global worker resources for analysis.
94    pub workers: Arc<AnalysisGlobalWorkers>,
95    /// The local package cache.
96    pub local_packages: Arc<Mutex<OnceLock<EcoVec<PackageIndexEntry>>>>,
97    /// The semantic token cache.
98    pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
99    /// The global caches for analysis.
100    pub caches: AnalysisGlobalCaches,
101    /// The revision-managed cache for analysis.
102    pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
103    /// The statistics about the analyzers.
104    pub stats: Arc<AnalysisStats>,
105}
106
107impl Analysis {
108    /// Enters the analysis context.
109    pub fn enter(&self, g: LspComputeGraph) -> LocalContextGuard {
110        self.enter_(g, self.lock_revision(None))
111    }
112
113    /// Enters the analysis context.
114    pub(crate) fn enter_(&self, g: LspComputeGraph, mut lg: AnalysisRevLock) -> LocalContextGuard {
115        let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst);
116        let slot = self
117            .analysis_rev_cache
118            .lock()
119            .find_revision(g.world().revision(), &lg);
120        let tokens = lg.tokens.take();
121        LocalContextGuard {
122            _rev_lock: lg,
123            local: LocalContext {
124                tokens,
125                caches: AnalysisLocalCaches::default(),
126                shared: Arc::new(SharedContext {
127                    slot,
128                    lifetime,
129                    graph: g,
130                    analysis: self.clone(),
131                }),
132            },
133        }
134    }
135
136    /// Gets a snapshot for language queries.
137    pub fn query_snapshot(
138        self: Arc<Self>,
139        snap: LspComputeGraph,
140        req: Option<&CompilerQueryRequest>,
141    ) -> LspQuerySnapshot {
142        let rev_lock = self.lock_revision(req);
143        LspQuerySnapshot {
144            snap,
145            analysis: self,
146            rev_lock,
147        }
148    }
149
150    /// Locks the revision in *main thread*.
151    #[must_use]
152    pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock {
153        let mut grid = self.analysis_rev_cache.lock();
154
155        AnalysisRevLock {
156            tokens: match req {
157                Some(CompilerQueryRequest::SemanticTokensFull(req)) => Some(
158                    SemanticTokenCache::acquire(self.tokens_caches.clone(), &req.path, None),
159                ),
160                Some(CompilerQueryRequest::SemanticTokensDelta(req)) => {
161                    Some(SemanticTokenCache::acquire(
162                        self.tokens_caches.clone(),
163                        &req.path,
164                        Some(&req.previous_result_id),
165                    ))
166                }
167                _ => None,
168            },
169            inner: grid.manager.lock_estimated(),
170            grid: self.analysis_rev_cache.clone(),
171        }
172    }
173
174    /// Clear all cached resources.
175    pub fn clear_cache(&self) {
176        self.caches.signatures.clear();
177        self.caches.docstrings.clear();
178        self.caches.def_signatures.clear();
179        self.caches.static_signatures.clear();
180        self.caches.terms.clear();
181        *self.local_packages.lock() = OnceLock::default();
182        self.tokens_caches.lock().clear();
183        self.analysis_rev_cache.lock().clear();
184    }
185
186    /// Report the statistics of the analysis.
187    pub fn report_query_stats(&self) -> String {
188        self.stats.report()
189    }
190
191    /// Report the structured statistics of the analysis.
192    pub fn report_query_stats_json(&self) -> Vec<QueryStatReportEntry> {
193        self.stats.report_json()
194    }
195
196    /// Report the statistics of the allocation.
197    pub fn report_alloc_stats(&self) -> String {
198        AllocStats::report()
199    }
200
201    /// Get configured trigger suggest command.
202    pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
203        interned_str!(INTERNED, "editor.action.triggerSuggest");
204
205        (self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
206    }
207
208    /// Get configured trigger parameter hints command.
209    pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
210        interned_str!(INTERNED, "editor.action.triggerParameterHints");
211        (self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
212    }
213
214    /// Get configured trigger suggest after snippet command.
215    ///
216    /// > VS Code doesn't do that... Auto triggering suggestion only happens on
217    /// > typing (word starts or trigger characters). However, you can use
218    /// > editor.action.triggerSuggest as command on a suggestion to "manually"
219    /// > retrigger suggest after inserting one
220    pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
221        if !self.completion_feat.trigger_on_snippet_placeholders {
222            return None;
223        }
224
225        self.trigger_suggest(context)
226    }
227
228    /// Get configured trigger on positional parameter hints command.
229    pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
230        interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
231        if !self.completion_feat.trigger_on_snippet_placeholders {
232            return self.trigger_parameter_hints(context);
233        }
234
235        (self.completion_feat.trigger_suggest_and_parameter_hints && context)
236            .then(|| INTERNED.clone())
237    }
238}
239
240/// The periscope provider.
241pub trait PeriscopeProvider {
242    /// Resolve telescope image at the given position.
243    fn periscope_at(
244        &self,
245        _ctx: &mut LocalContext,
246        _doc: &TypstDocument,
247        _pos: Position,
248    ) -> Option<String> {
249        None
250    }
251}
252
253/// The local context guard that performs gc once dropped.
254pub struct LocalContextGuard {
255    /// The guarded local context
256    pub local: LocalContext,
257    /// The revision lock
258    _rev_lock: AnalysisRevLock,
259}
260
261impl Deref for LocalContextGuard {
262    type Target = LocalContext;
263
264    fn deref(&self) -> &Self::Target {
265        &self.local
266    }
267}
268
269impl DerefMut for LocalContextGuard {
270    fn deref_mut(&mut self) -> &mut Self::Target {
271        &mut self.local
272    }
273}
274
275// todo: gc in new thread
276impl Drop for LocalContextGuard {
277    fn drop(&mut self) {
278        self.gc();
279    }
280}
281
282impl LocalContextGuard {
283    fn gc(&self) {
284        let lifetime = self.lifetime;
285        loop {
286            let latest_clear_lifetime = self.analysis.caches.clear_lifetime.load(Ordering::Relaxed);
287            if latest_clear_lifetime >= lifetime {
288                return;
289            }
290
291            if self.analysis.caches.clear_lifetime.compare_exchange(
292                latest_clear_lifetime,
293                lifetime,
294                Ordering::SeqCst,
295                Ordering::SeqCst,
296            ) != Ok(latest_clear_lifetime)
297            {
298                continue;
299            }
300
301            break;
302        }
303
304        let retainer = |l: u64| lifetime.saturating_sub(l) < 60;
305        let caches = &self.analysis.caches;
306        caches.def_signatures.retain(|(l, _)| retainer(*l));
307        caches.static_signatures.retain(|(l, _)| retainer(*l));
308        caches.terms.retain(|(l, _)| retainer(*l));
309        caches.signatures.retain(|(l, _)| retainer(*l));
310        caches.docstrings.retain(|(l, _)| retainer(*l));
311    }
312}
313
314/// The local context for analyzers. In addition to the shared context, it also
315/// holds mutable local caches.
316pub struct LocalContext {
317    /// The created semantic token context.
318    pub(crate) tokens: Option<SemanticTokenContext>,
319    /// Local caches for analysis.
320    pub caches: AnalysisLocalCaches,
321    /// The shared context
322    pub shared: Arc<SharedContext>,
323}
324
325impl Deref for LocalContext {
326    type Target = Arc<SharedContext>;
327
328    fn deref(&self) -> &Self::Target {
329        &self.shared
330    }
331}
332
333impl DerefMut for LocalContext {
334    fn deref_mut(&mut self) -> &mut Self::Target {
335        &mut self.shared
336    }
337}
338
339impl LocalContext {
340    /// Set list of packages for LSP-based completion.
341    #[cfg(test)]
342    pub fn test_package_list(&mut self, f: impl FnOnce() -> Vec<PackageIndexEntry> + Clone) {
343        self.world().registry.test_package_list(f.clone());
344        self.analysis
345            .local_packages
346            .lock()
347            .get_or_init(|| f().into_iter().collect());
348    }
349
350    /// Set the files for LSP-based completion.
351    #[cfg(test)]
352    pub fn test_completion_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
353        self.caches.completion_files.get_or_init(f);
354    }
355
356    /// Set the files for analysis.
357    #[cfg(test)]
358    pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
359        self.caches.root_files.get_or_init(f);
360    }
361
362    /// Get all the source files in the workspace.
363    pub(crate) fn completion_files(&self, pref: &PathKind) -> impl Iterator<Item = &TypstFileId> {
364        let regexes = pref.ext_matcher();
365        self.caches
366            .completion_files
367            .get_or_init(|| {
368                if let Some(root) = self.world().entry_state().workspace_root() {
369                    scan_workspace_files(&root, PathKind::Special.ext_matcher(), |path| {
370                        VirtualPath::virtualize(&root, &root.join(path))
371                            .ok()
372                            .map(|path| WorkspaceResolver::workspace_file(Some(&root), path))
373                    })
374                    .into_iter()
375                    .flatten()
376                    .collect()
377                } else {
378                    vec![]
379                }
380            })
381            .iter()
382            .filter(move |fid| {
383                fid.vpath()
384                    .as_rooted_path_compat()
385                    .extension()
386                    .and_then(|path| path.to_str())
387                    .is_some_and(|path| regexes.is_match(path))
388            })
389    }
390
391    /// Get all the source files in the workspace.
392    pub fn source_files(&self) -> &Vec<TypstFileId> {
393        self.caches.root_files.get_or_init(|| {
394            self.completion_files(&PathKind::Source {
395                allow_package: false,
396            })
397            .copied()
398            .collect()
399        })
400    }
401
402    /// Get the module dependencies of the workspace.
403    pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
404        if self.caches.module_deps.get().is_some() {
405            self.caches.module_deps.get().unwrap()
406        } else {
407            // may cause multiple times to calculate, but it is okay because we have mutable
408            // reference to self.
409            let deps = construct_module_dependencies(self);
410            self.caches.module_deps.get_or_init(|| deps)
411        }
412    }
413
414    /// Get all depended files in the workspace, inclusively.
415    pub fn depended_source_files(&self) -> EcoVec<TypstFileId> {
416        let mut ids = self.depended_files();
417        let preference = PathKind::Source {
418            allow_package: false,
419        };
420        ids.retain(|id| preference.is_match(id.vpath().as_rooted_path_compat()));
421        ids
422    }
423
424    /// Get all depended file ids of a compilation, inclusively.
425    /// Note: must be called after compilation.
426    pub fn depended_files(&self) -> EcoVec<TypstFileId> {
427        self.world().depended_files()
428    }
429
430    /// Get the shared context.
431    pub fn shared(&self) -> &Arc<SharedContext> {
432        &self.shared
433    }
434
435    /// Get the shared context.
436    pub fn shared_(&self) -> Arc<SharedContext> {
437        self.shared.clone()
438    }
439
440    /// Fork a new context for searching in the workspace.
441    pub fn fork_for_search(&mut self) -> SearchCtx<'_> {
442        SearchCtx {
443            ctx: self,
444            searched: Default::default(),
445            worklist: Default::default(),
446        }
447    }
448
449    pub(crate) fn preload_package(&self, entry_point: TypstFileId) {
450        self.shared_().preload_package(entry_point);
451    }
452
453    pub(crate) fn preload_expr_stages<I>(&self, files: I)
454    where
455        I: IntoIterator<Item = TypstFileId>,
456    {
457        self.shared_().preload_expr_stages(files);
458    }
459
460    pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst_shim::eval::Vm) -> T) -> T {
461        crate::upstream::with_vm((self.world() as &dyn World).track(), f)
462    }
463
464    pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
465        SharedContext::const_eval(rr)
466    }
467
468    pub(crate) fn mini_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
469        self.const_eval(rr)
470            .or_else(|| self.with_vm(|vm| rr.eval(vm).ok()))
471    }
472
473    pub(crate) fn cached_tokens(&mut self, source: &Source) -> (SemanticTokens, Option<String>) {
474        let tokens = crate::analysis::semantic_tokens::get_semantic_tokens(self.shared(), source);
475
476        let result_id = self.tokens.as_ref().map(|t| {
477            let id = t.next.revision;
478            t.next
479                .data
480                .set(tokens.clone())
481                .unwrap_or_else(|_| panic!("unexpected slot overwrite {id}"));
482            id.to_string()
483        });
484        (tokens, result_id)
485    }
486
487    /// Get the expression information of a source file.
488    pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
489        Some(self.expr_stage(&self.source_by_id(fid).ok()?))
490    }
491
492    /// Get the expression information of a source file.
493    pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo {
494        let id = source.id();
495        let cache = &self.caches.modules.entry(id).or_default().expr_stage;
496        cache.get_or_init(|| self.shared.expr_stage(source)).clone()
497    }
498
499    /// Get the type check information of a source file.
500    pub(crate) fn type_check(&mut self, source: &Source) -> Arc<TypeInfo> {
501        let id = source.id();
502        let cache = &self.caches.modules.entry(id).or_default().type_check;
503        cache.get_or_init(|| self.shared.type_check(source)).clone()
504    }
505
506    pub(crate) fn lint(
507        &mut self,
508        source: &Source,
509        known_issues: &KnownIssues,
510    ) -> EcoVec<SourceDiagnostic> {
511        self.shared.lint(source, known_issues).diagnostics
512    }
513
514    /// Get the type check information of a source file.
515    pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
516        let cache = &self.caches.modules.entry(id).or_default().type_check;
517        cache
518            .clone()
519            .get_or_init(|| {
520                let source = self.source_by_id(id).ok();
521                source
522                    .map(|s| self.shared.type_check(&s))
523                    .unwrap_or_default()
524            })
525            .clone()
526    }
527
528    pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
529        let scheme = self.type_check_by_id(s.id()?);
530        let ty = scheme.type_of_span(s)?;
531        Some(scheme.simplify(ty, false))
532    }
533
534    pub(crate) fn def_docs(&mut self, def: &Definition) -> Option<DefDocs> {
535        // let plain_docs = sym.head.docs.as_deref();
536        // let plain_docs = plain_docs.or(sym.head.oneliner.as_deref());
537        match def.decl.kind() {
538            DefKind::Function => {
539                let sig = self.sig_of_def(def.clone())?;
540                let docs = crate::docs::sig_docs(self.shared(), &sig)?;
541                Some(DefDocs::Function(Box::new(docs)))
542            }
543            DefKind::Struct | DefKind::Constant | DefKind::Variable => {
544                let docs = crate::docs::var_docs(self.shared(), def.decl.span())?;
545                Some(DefDocs::Variable(docs))
546            }
547            DefKind::Module => {
548                let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
549                Some(DefDocs::Module(TidyModuleDocs {
550                    docs: ei.module_docstring.docs.clone().unwrap_or_default(),
551                }))
552            }
553            DefKind::Reference => None,
554        }
555    }
556}
557
558/// A concurrent per-request cache for expensive shared computations.
559#[derive(Clone)]
560pub struct SharedQueryCache<K, V> {
561    slots: Arc<FxDashMap<K, Arc<OnceLock<V>>>>,
562}
563
564impl<K, V> Default for SharedQueryCache<K, V>
565where
566    K: Eq + Hash,
567{
568    fn default() -> Self {
569        Self {
570            slots: Arc::new(FxDashMap::default()),
571        }
572    }
573}
574
575impl<K, V> SharedQueryCache<K, V>
576where
577    K: Eq + Hash,
578    V: Clone,
579{
580    /// Gets a cached value for `key`, initializing it once if absent.
581    pub fn get_or_init(&self, key: K, init: impl FnOnce() -> V) -> V {
582        let slot = self
583            .slots
584            .entry(key)
585            .or_insert_with(|| Arc::new(OnceLock::new()))
586            .clone();
587        slot.get_or_init(init).clone()
588    }
589}
590
591/// The shared analysis context for analyzers.
592pub struct SharedContext {
593    /// The caches lifetime tick for analysis.
594    pub lifetime: u64,
595    // The world surface for Typst compiler.
596    // pub world: LspWorld,
597    /// The project snapshot with shared compute cache.
598    pub graph: LspComputeGraph,
599    /// The analysis data
600    pub analysis: Analysis,
601    /// The using analysis revision slot
602    slot: Arc<RevisionSlot<AnalysisRevSlot>>,
603}
604
605/// Extracts one member's result from a complete component batch.
606fn component_member<T: Clone>(
607    kind: &str,
608    component: &AnalysisComponent,
609    result: &FxHashMap<TypstFileId, T>,
610    fid: TypstFileId,
611) -> T {
612    result
613        .get(&fid)
614        .unwrap_or_else(|| {
615            panic!(
616                "{kind} component {:?} is missing requested file {fid:?}",
617                component.members
618            )
619        })
620        .clone()
621}
622
623impl SharedContext {
624    pub(super) fn components(&self) -> &ComponentCoordinator {
625        &self.slot.components
626    }
627
628    /// Gets the revision of current analysis
629    pub fn revision(&self) -> usize {
630        self.slot.revision
631    }
632
633    /// Gets the position encoding during session.
634    pub(crate) fn position_encoding(&self) -> PositionEncoding {
635        self.analysis.position_encoding
636    }
637
638    /// Gets the world surface for Typst compiler.
639    pub fn world(&self) -> &LspWorld {
640        self.graph.world()
641    }
642
643    /// Gets the success document.
644    pub fn success_doc(&self) -> Option<&TypstDocument> {
645        self.graph.snap.success_doc.as_ref()
646    }
647
648    /// Converts an LSP position to a Typst position.
649    pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
650        crate::to_typst_position(position, self.analysis.position_encoding, src)
651    }
652
653    /// Converts an LSP position with some offset.
654    pub fn to_typst_pos_offset(
655        &self,
656        source: &Source,
657        position: LspPosition,
658        shift: usize,
659    ) -> Option<usize> {
660        let offset = self.to_typst_pos(position, source)?;
661        Some(ceil_char_boundary(source.text(), offset + shift))
662    }
663
664    /// Converts a Typst offset to an LSP position.
665    pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
666        crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src)
667    }
668
669    /// Converts an LSP range to a Typst range.
670    pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<Range<usize>> {
671        crate::to_typst_range(position, self.analysis.position_encoding, src)
672    }
673
674    /// Converts a Typst range to an LSP range.
675    pub fn to_lsp_range(&self, position: Range<usize>, src: &Source) -> LspRange {
676        crate::to_lsp_range(position, src, self.analysis.position_encoding)
677    }
678
679    /// Converts a Typst range to an LSP range.
680    pub fn to_lsp_range_(&self, position: Range<usize>, fid: TypstFileId) -> Option<LspRange> {
681        let ext = fid
682            .vpath()
683            .as_rootless_path_compat()
684            .extension()
685            .and_then(|ext| ext.to_str());
686        // yaml/yml/bib
687        if matches!(ext, Some("yaml" | "yml" | "bib")) {
688            let bytes = self.file_by_id(fid).ok()?;
689            let bytes_len = bytes.len();
690            let loc = loc_info(bytes)?;
691            // binary search
692            let start = find_loc(bytes_len, &loc, position.start, self.position_encoding())?;
693            let end = find_loc(bytes_len, &loc, position.end, self.position_encoding())?;
694            return Some(LspRange { start, end });
695        }
696
697        let source = self.source_by_id(fid).ok()?;
698
699        Some(self.to_lsp_range(position, &source))
700    }
701
702    /// Resolves the real path for a file id.
703    pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
704        self.world().path_for_id(id)
705    }
706
707    /// Resolves the uri for a file id.
708    pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
709        self.world().uri_for_id(fid)
710    }
711
712    /// Gets file's id by its path
713    pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
714        self.world().file_id_by_path(path)
715    }
716
717    /// Gets the content of a file by file id.
718    pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
719        self.world().file(fid)
720    }
721
722    /// Gets the source of a file by file id.
723    pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
724        self.world().source(fid)
725    }
726
727    /// Gets the source of a file by file path.
728    pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
729        self.source_by_id(self.file_id_by_path(path)?)
730    }
731
732    /// Classifies the syntax under a span that can be operated on by IDE
733    /// functionality.
734    pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
735        let node = LinkedNode::new(source.root()).find(span)?;
736        let cursor = node.offset() + 1;
737        classify_syntax(node, cursor)
738    }
739
740    /// Classifies the syntax under position that can be operated on by IDE
741    /// functionality. It is preferred to select a decl if it is at the starts
742    /// of some mark.
743    pub fn classify_for_decl<'s>(
744        &self,
745        source: &'s Source,
746        position: LspPosition,
747    ) -> Option<SyntaxClass<'s>> {
748        let cursor = self.to_typst_pos_offset(source, position, 1)?;
749        let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
750
751        // In the case that the cursor is at the end of an identifier.
752        // e.g. `f(x|)`, we will select the `x`
753        if cursor == node.offset() + 1 && is_mark(node.kind()) {
754            let prev_leaf = node.prev_leaf();
755            if let Some(prev_leaf) = prev_leaf
756                && prev_leaf.range().end == node.offset()
757            {
758                node = prev_leaf;
759            }
760        }
761
762        classify_syntax(node, cursor)
763    }
764
765    /// Resolves extra font information.
766    pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
767        self.world().font_resolver.describe_font(&font)
768    }
769
770    /// Gets the packages other than that in the preview namespace and their
771    /// descriptions.
772    pub fn non_preview_packages(&self) -> EcoVec<PackageIndexEntry> {
773        #[cfg(feature = "local-registry")]
774        let it = || {
775            crate::package::list_package(
776                self.world(),
777                crate::package::PackageFilter::ExceptFor(EcoString::inline("preview")),
778            )
779        };
780        #[cfg(not(feature = "local-registry"))]
781        let it = || Default::default();
782        self.analysis.local_packages.lock().get_or_init(it).clone()
783    }
784
785    pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
786        Some(match rr {
787            ast::Expr::None(_) => Value::None,
788            ast::Expr::Auto(_) => Value::Auto,
789            ast::Expr::Bool(v) => Value::Bool(v.get()),
790            ast::Expr::Int(v) => Value::Int(v.get()),
791            ast::Expr::Float(v) => Value::Float(v.get()),
792            ast::Expr::Numeric(v) => Value::numeric(v.get()),
793            ast::Expr::Str(v) => Value::Str(v.get().into()),
794            _ => return None,
795        })
796    }
797
798    /// Gets a module by file id.
799    pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult<Module> {
800        let source = self.source_by_id(fid).at(Span::detached())?;
801        self.module_by_src(source)
802    }
803
804    /// Gets a module by string.
805    pub fn module_by_str(&self, rr: String) -> Option<Module> {
806        let src = Source::new(*DETACHED_ENTRY, rr);
807        self.module_by_src(src).ok()
808    }
809
810    /// Gets (Creates) a module by source.
811    pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
812        eval_compat(&self.world(), &source)
813    }
814
815    /// Gets a module value from a given source file.
816    pub fn module_by_syntax(self: &Arc<Self>, source: &SyntaxNode) -> Option<Value> {
817        self.module_term_by_syntax(source, true)
818            .and_then(|ty| ty.value())
819    }
820
821    /// Gets a module term from a given source file. If `value` is true, it will
822    /// prefer to get a value instead of a term.
823    pub fn module_term_by_syntax(self: &Arc<Self>, source: &SyntaxNode, value: bool) -> Option<Ty> {
824        let (src, scope) = self.analyze_import(source);
825        if let Some(scope) = scope {
826            return Some(match scope {
827                Value::Module(m) if m.file_id().is_some() => {
828                    Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into()))
829                }
830                scope => Ty::Value(InsTy::new(scope)),
831            });
832        }
833
834        match src {
835            Some(Value::Str(s)) => {
836                let id = resolve_id_by_path(self.world(), source.span().id()?, s.as_str())?;
837
838                Some(if value {
839                    Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?)))
840                } else {
841                    Ty::Builtin(BuiltinTy::Module(Decl::module(id).into()))
842                })
843            }
844            _ => None,
845        }
846    }
847
848    /// Gets the expression information of a source file.
849    pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
850        Some(self.expr_stage(&self.source_by_id(fid).ok()?))
851    }
852
853    fn previous_expr_component(
854        self: &Arc<Self>,
855        component: &AnalysisComponent,
856        sources: &[Source],
857    ) -> Option<FxHashMap<TypstFileId, ExprInfo>> {
858        if sources.len() != component.members.len()
859            || sources
860                .iter()
861                .zip(component.members.iter())
862                .any(|(source, member)| source.id() != *member)
863        {
864            return None;
865        }
866        // An unresolved site can select a different target even when its
867        // source text is unchanged. Every member of an SCC has the same
868        // condensation-DAG closure, so one query covers the complete batch.
869        if self
870            .slot
871            .components
872            .has_unresolved_dependencies(component.members[0])
873        {
874            return None;
875        }
876
877        // One consistent snapshot of the dependency identities; None means a
878        // concurrent merge retracted this owner, and expr_stage retries from
879        // the fresh component.
880        let deps = self.slot.components.member_dependencies(component)?;
881
882        let mut previous = FxHashMap::default();
883        for source in sources {
884            let key = Self::expr_history_key(source, component, &deps);
885            let info = self.slot.expr_stage.previous(&key)?;
886            if info.fid != source.id()
887                || info.source.lines().len_bytes() != source.lines().len_bytes()
888                || hash128(&info.source) != hash128(source)
889            {
890                return None;
891            }
892            previous.insert(source.id(), info);
893        }
894
895        // Internal imports are valid because every member source was accepted
896        // as one batch above. Only external completed exports need comparison.
897        for (&importer, info) in &previous {
898            for (&target, old_exports) in &info.imports {
899                match self.dependency_admission(importer, target) {
900                    DependencyAdmission::SameComponent => {
901                        if !previous.contains_key(&target) {
902                            return None;
903                        }
904                    }
905                    DependencyAdmission::Reachable => {
906                        let current = self.expr_stage_by_id(target)?.exports.clone();
907                        if old_exports.size() != current.size()
908                            || hash128(old_exports) != hash128(&current)
909                        {
910                            return None;
911                        }
912                    }
913                    DependencyAdmission::Unresolved | DependencyAdmission::Rejected => {
914                        return None;
915                    }
916                }
917            }
918        }
919
920        Some(previous)
921    }
922
923    /// Builds one member's expression history key from a dependency snapshot
924    /// taken while `component` owned its group.
925    fn expr_history_key(
926        source: &Source,
927        component: &AnalysisComponent,
928        deps: &FxHashMap<TypstFileId, Vec<TypstFileId>>,
929    ) -> u128 {
930        hash128(&(source, component.members.as_ref(), &deps[&source.id()]))
931    }
932
933    /// Hashes every current expression input that can affect a component's
934    /// type results. The component membership is included separately from its
935    /// transitive condensation-DAG closure, so a changed SCC partition also
936    /// invalidates the batch.
937    fn type_component_fingerprint(
938        self: &Arc<Self>,
939        component: &AnalysisComponent,
940        exprs: &FxHashMap<TypstFileId, ExprInfo>,
941    ) -> u128 {
942        let inputs: Vec<_> = self
943            .slot
944            .components
945            .reachable_files(component.members[0])
946            .into_iter()
947            .map(|fid| {
948                let hash = exprs
949                    .get(&fid)
950                    .cloned()
951                    .or_else(|| self.expr_stage_by_id(fid))
952                    .map(|info| hash128(&info));
953                (fid, hash)
954            })
955            .collect();
956
957        hash128(&(component.members.as_ref(), inputs))
958    }
959
960    fn expr_component(
961        self: &Arc<Self>,
962        component: &Arc<AnalysisComponent>,
963    ) -> Arc<FxHashMap<TypstFileId, ExprInfo>> {
964        let result = component
965            .expr_stage
966            .get_or_init(|| {
967                let sources: Vec<_> = component
968                    .members
969                    .iter()
970                    .map(|&fid| {
971                        self.source_by_id(fid).unwrap_or_else(|err| {
972                            panic!(
973                                "sealed expression component contains unreadable {fid:?}: {err:?}"
974                            )
975                        })
976                    })
977                    .collect();
978                if let Some(previous) = self.previous_expr_component(component, &sources) {
979                    return Arc::new(previous);
980                }
981
982                let mut route = ExprStage::new(self.clone(), sources.iter().cloned());
983                let mut result = FxHashMap::default();
984
985                for source in sources {
986                    let info = route.analyze(self.clone(), source.clone());
987                    result.insert(source.id(), info);
988                }
989
990                for (&fid, info) in &result {
991                    for imported in info.imports.keys() {
992                        match self.slot.components.record_dependency(fid, *imported) {
993                            DependencyAdmission::SameComponent
994                            | DependencyAdmission::Reachable => {}
995                            DependencyAdmission::Unresolved => {
996                                crate::log_debug_ct!(
997                                    "expression analysis kept unresolved import {fid:?} -> {imported:?} as no-wait"
998                                );
999                            }
1000                            DependencyAdmission::Rejected => {
1001                                log::warn!(
1002                                    "dependency admission missed import {fid:?} -> {imported:?}; keeping it as no-wait"
1003                                );
1004                            }
1005                        }
1006                    }
1007                }
1008
1009                Arc::new(result)
1010            })
1011            .clone();
1012
1013        // A late dynamic edge can redirect this owner to a fresh SCC while the
1014        // initializer is running. The old batch is complete, but obsolete —
1015        // its same-component imports used no-wait fallbacks — so it must
1016        // never enter revision history. The ownership check, the dependency
1017        // identity reads, and the batch publication run in one critical
1018        // section so no merge can interleave between them.
1019        self.slot.components.commit_current(component, |deps| {
1020            for info in result.values() {
1021                let key = Self::expr_history_key(&info.source, component, deps);
1022                self.slot.expr_stage.publish(key, info.clone());
1023            }
1024        });
1025
1026        result
1027    }
1028
1029    /// Gets the expression information of a source file.
1030    pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
1031        loop {
1032            let component = self.analysis_component(source.id());
1033            let result = component
1034                .expr_stage
1035                .get()
1036                .cloned()
1037                .unwrap_or_else(|| self.expr_component(&component));
1038            if !component.is_current() {
1039                continue;
1040            }
1041
1042            return component_member("expression", &component, &result, source.id());
1043        }
1044    }
1045
1046    pub(crate) fn external_exports_of(
1047        self: &Arc<Self>,
1048        importer: TypstFileId,
1049        source: &Source,
1050    ) -> Option<Arc<LazyHash<LexicalScope>>> {
1051        // Commit the resolved edge before acquiring another component's result
1052        // slot. A cycle redirects this route to a fresh component, so this
1053        // obsolete pass uses a no-wait fallback and is retried by expr_stage.
1054        match self
1055            .slot
1056            .components
1057            .record_dependency(importer, source.id())
1058        {
1059            DependencyAdmission::Reachable => Some(self.expr_stage(source).exports.clone()),
1060            DependencyAdmission::SameComponent => {
1061                // A dependency discovered by the full pass can merge the route
1062                // currently being computed. Finish this obsolete batch without
1063                // reading another owner; expr_stage will retry from the fresh
1064                // component before publishing or returning it.
1065                None
1066            }
1067            DependencyAdmission::Unresolved => None,
1068            DependencyAdmission::Rejected => {
1069                log::warn!(
1070                    "rejected expression dependency {importer:?} -> {:?}; using empty exports",
1071                    source.id()
1072                );
1073                None
1074            }
1075        }
1076    }
1077
1078    /// Gets the type check information of a source file.
1079    pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
1080        loop {
1081            let component = self.analysis_component(source.id());
1082            if let Some(result) = component.type_check.get() {
1083                if !component.is_current() {
1084                    continue;
1085                }
1086                return component_member("type", &component, result, source.id());
1087            }
1088
1089            let exprs = self.expr_component(&component);
1090            if !component.is_current() {
1091                continue;
1092            }
1093
1094            // Compute the dependency fingerprint before taking this component's
1095            // type slot. Fingerprinting may initialize expression batches along
1096            // the condensation DAG, but never waits on another type slot.
1097            let fingerprint = self.type_component_fingerprint(&component, &exprs);
1098            let result = component
1099                .type_check
1100                .get_or_init(|| {
1101                    let mut previous = FxHashMap::default();
1102                    for &fid in component.members.iter() {
1103                        let ei = exprs.get(&fid).unwrap_or_else(|| {
1104                            panic!("component expression batch is missing {fid:?}")
1105                        });
1106                        if let Some(cache_hint) = self
1107                            .slot
1108                            .type_check
1109                            .previous(&hash128(&(fid, fingerprint)))
1110                            .filter(|prev| prev.fid == Some(fid) && prev.revision == ei.revision)
1111                        {
1112                            previous.insert(fid, cache_hint);
1113                        }
1114                    }
1115                    if previous.len() != component.members.len() {
1116                        // SCC members are checked in one environment and can
1117                        // depend on each other's completed result. Reuse the
1118                        // previous batch only when every member is reusable.
1119                        previous.clear();
1120                    }
1121
1122                    let result = type_check_component(
1123                        self.clone(),
1124                        &component.members,
1125                        exprs.clone(),
1126                        previous,
1127                    );
1128
1129                    Arc::new(result)
1130                })
1131                .clone();
1132
1133            // Publish history only after the complete current batch is ready.
1134            // The ownership check and the publication run in one critical
1135            // section so a merged-away owner never publishes an obsolete
1136            // batch.
1137            let published = self.slot.components.commit_current(&component, |_deps| {
1138                for (&fid, info) in result.iter() {
1139                    self.slot
1140                        .type_check
1141                        .publish(hash128(&(fid, fingerprint)), info.clone());
1142                }
1143            });
1144            if published.is_none() {
1145                continue;
1146            }
1147
1148            return component_member("type", &component, &result, source.id());
1149        }
1150    }
1151
1152    /// Gets the lint result of a source file.
1153    #[typst_macros::time(span = source.root().span())]
1154    pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
1155        let ei = self.expr_stage(source);
1156        let guard = self.query_stat(source.id(), "lint");
1157        self.slot.lint.compute(hash128(&(&ei, issues)), |_| {
1158            guard.miss();
1159            tinymist_lint::lint_file(self.world(), &ei, issues.clone())
1160        })
1161    }
1162
1163    pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
1164        crate::log_debug_ct!("convert runtime func {func:?}");
1165        analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
1166    }
1167
1168    pub(crate) fn type_of_value(self: &Arc<Self>, val: &Value) -> Ty {
1169        crate::log_debug_ct!("convert runtime value {val:?}");
1170
1171        // todo: check performance on peeking signature source frequently
1172        let cache_key = val;
1173        let cached = self
1174            .analysis
1175            .caches
1176            .terms
1177            .m
1178            .get(&hash128(&cache_key))
1179            .and_then(|slot| (cache_key == &slot.1.0).then_some(slot.1.1.clone()));
1180        if let Some(cached) = cached {
1181            return cached;
1182        }
1183
1184        let res = term_value(val);
1185
1186        self.analysis
1187            .caches
1188            .terms
1189            .m
1190            .entry(hash128(&cache_key))
1191            .or_insert_with(|| (self.lifetime, (cache_key.clone(), res.clone())));
1192
1193        res
1194    }
1195
1196    /// Gets the definition from a source location.
1197    pub(crate) fn def_of_span(self: &Arc<Self>, source: &Source, span: Span) -> Option<Definition> {
1198        let syntax = self.classify_span(source, span)?;
1199        definition(self, source, syntax)
1200    }
1201
1202    /// Gets the definition from static analysis.
1203    ///
1204    /// Passing a `doc` (compiled result) can help resolve dynamic things, e.g.
1205    /// label definitions.
1206    pub(crate) fn def_of_syntax(
1207        self: &Arc<Self>,
1208        source: &Source,
1209        syntax: SyntaxClass,
1210    ) -> Option<Definition> {
1211        definition(self, source, syntax)
1212    }
1213
1214    /// Gets the definition from static analysis or dynamic analysis.
1215    ///
1216    /// Note: while this has best quality in typst, it is expensive.
1217    /// Use it if you know it is only called `O(1)` times to serve a user LSP
1218    /// request, e.g. resolve a function definition for `completion`.
1219    /// Otherwise, use `def_of_syntax`, e.g. resolves all definitions for
1220    /// package docs.
1221    pub(crate) fn def_of_syntax_or_dyn(
1222        self: &Arc<Self>,
1223        source: &Source,
1224        syntax: SyntaxClass,
1225    ) -> Option<Definition> {
1226        let def = self.def_of_syntax(source, syntax.clone());
1227        match def.as_ref().map(|d| d.decl.kind()) {
1228            // todo: DefKind::Function
1229            Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
1230            Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
1231        }
1232
1233        // Checks that we resolved a high-equality definition.
1234        let know_ty_well = def
1235            .as_ref()
1236            .and_then(|d| self.simplified_type_of_span(d.decl.span()))
1237            .filter(|ty| !matches!(ty, Ty::Any))
1238            .is_some();
1239        if know_ty_well {
1240            return def;
1241        }
1242
1243        let def_ref = def.as_ref();
1244        let def_name = || Some(def_ref?.name().clone());
1245        let dyn_def = self
1246            .analyze_expr(syntax.node())
1247            .iter()
1248            .find_map(|(value, _)| {
1249                let def = Definition::from_value(value.clone(), def_name)?;
1250                None.or_else(|| {
1251                    let source = self.source_by_id(def.decl.file_id()?).ok()?;
1252                    let node = LinkedNode::new(source.root()).find(def.decl.span())?;
1253                    let def_at_the_span = classify_def_loosely(node)?;
1254                    self.def_of_span(&source, def_at_the_span.name()?.span())
1255                })
1256                .or(Some(def))
1257            });
1258
1259        // Uses the dynamic definition or the fallback definition.
1260        dyn_def.or(def)
1261    }
1262
1263    pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1264        let source = self.source_by_id(span.id()?).ok()?;
1265        let (ti, ty) = self.type_of_span_(&source, span)?;
1266        Some(ti.simplify(ty, false))
1267    }
1268
1269    pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1270        let source = self.source_by_id(span.id()?).ok()?;
1271        Some(self.type_of_span_(&source, span)?.1)
1272    }
1273
1274    pub(crate) fn type_of_span_(
1275        self: &Arc<Self>,
1276        source: &Source,
1277        span: Span,
1278    ) -> Option<(Arc<TypeInfo>, Ty)> {
1279        let ti = self.type_check(source);
1280        let ty = ti.type_of_span(span)?;
1281        Some((ti, ty))
1282    }
1283
1284    pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
1285        let id = node.span().id()?;
1286        let source = self.source_by_id(id).ok()?;
1287        let ty_chk = self.type_check(&source);
1288
1289        let ty = post_type_check(self.clone(), &ty_chk, node.clone())
1290            .or_else(|| ty_chk.type_of_span(node.span()))?;
1291        Some(ty_chk.simplify(ty, false))
1292    }
1293
1294    pub(crate) fn sig_of_def(self: &Arc<Self>, def: Definition) -> Option<Signature> {
1295        crate::log_debug_ct!("check definition func {def:?}");
1296        let source = def.decl.file_id().and_then(|id| self.source_by_id(id).ok());
1297        analyze_signature(self, SignatureTarget::Def(source, def))
1298    }
1299
1300    pub(crate) fn def_docs(self: &Arc<Self>, def: &Definition) -> Option<DefDocs> {
1301        match def.decl.kind() {
1302            DefKind::Function => {
1303                let sig = self.sig_of_def(def.clone())?;
1304                let docs = crate::docs::sig_docs(self, &sig)?;
1305                Some(DefDocs::Function(Box::new(docs)))
1306            }
1307            DefKind::Struct | DefKind::Constant | DefKind::Variable => {
1308                let docs = crate::docs::var_docs(self, def.decl.span())?;
1309                Some(DefDocs::Variable(docs))
1310            }
1311            DefKind::Module => {
1312                let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
1313                Some(DefDocs::Module(TidyModuleDocs {
1314                    docs: ei.module_docstring.docs.clone().unwrap_or_default(),
1315                }))
1316            }
1317            DefKind::Reference => None,
1318        }
1319    }
1320
1321    pub(crate) fn sig_of_type(self: &Arc<Self>, ti: &TypeInfo, ty: Ty) -> Option<Signature> {
1322        super::sig_of_type(self, ti, ty)
1323    }
1324
1325    pub(crate) fn sig_of_type_or_dyn(
1326        self: &Arc<Self>,
1327        ti: &TypeInfo,
1328        callee_ty: Ty,
1329        callee: &SyntaxNode,
1330    ) -> Option<Signature> {
1331        self.sig_of_type(ti, callee_ty).or_else(|| {
1332            self.analyze_expr(callee).iter().find_map(|(value, _)| {
1333                let Value::Func(callee) = value else {
1334                    return None;
1335                };
1336
1337                // Converts with cache
1338                analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
1339            })
1340        })
1341    }
1342
1343    /// Try to find imported target from the current source file.
1344    /// This function will try to resolves target statically.
1345    ///
1346    /// ## Returns
1347    /// The first value is the resolved source.
1348    /// The second value is the resolved scope.
1349    pub fn analyze_import(&self, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
1350        if let Some(v) = source.cast::<ast::Expr>().and_then(Self::const_eval) {
1351            return (Some(v), None);
1352        }
1353        let token = &self.analysis.workers.import;
1354        token.enter(|| analyze_import_(self.world(), source))
1355    }
1356
1357    /// Try to load a module from the current source file.
1358    pub fn analyze_expr(&self, source: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
1359        let token = &self.analysis.workers.expression;
1360        token.enter(|| analyze_expr_(self.world(), source))
1361    }
1362
1363    /// Get bib info of a source file.
1364    pub fn analyze_bib(&self, introspector: &dyn Introspector) -> Option<Arc<BibInfo>> {
1365        let world = self.world();
1366        let world = (world as &dyn World).track();
1367
1368        analyze_bib(world, introspector.track())
1369    }
1370
1371    /// Describe the item under the cursor.
1372    ///
1373    /// Passing a `document` (from a previous compilation) is optional, but
1374    /// enhances the autocompletions. Label completions, for instance, are
1375    /// only generated when the document is available.
1376    pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
1377        let token = &self.analysis.workers.tooltip;
1378        token.enter(|| tooltip_(self.world(), source, cursor))
1379    }
1380
1381    /// Get the manifest of a package by file id.
1382    pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1383        crate::package::get_manifest(self.world(), toml_id)
1384    }
1385
1386    /// Compute the signature of a function.
1387    pub fn compute_signature(
1388        self: &Arc<Self>,
1389        func: SignatureTarget,
1390        compute: impl FnOnce(&Arc<Self>) -> Option<Signature> + Send + Sync + 'static,
1391    ) -> Option<Signature> {
1392        let res = match func {
1393            SignatureTarget::Def(src, def) => self
1394                .analysis
1395                .caches
1396                .def_signatures
1397                .entry(hash128(&(src, def.clone())), self.lifetime),
1398            SignatureTarget::SyntaxFast(source, span) => {
1399                let cache_key = (source, span, true);
1400                self.analysis
1401                    .caches
1402                    .static_signatures
1403                    .entry(hash128(&cache_key), self.lifetime)
1404            }
1405            SignatureTarget::Syntax(source, span) => {
1406                let cache_key = (source, span);
1407                self.analysis
1408                    .caches
1409                    .static_signatures
1410                    .entry(hash128(&cache_key), self.lifetime)
1411            }
1412            SignatureTarget::Convert(rt) => self
1413                .analysis
1414                .caches
1415                .signatures
1416                .entry(hash128(&(&rt, true)), self.lifetime),
1417            SignatureTarget::Runtime(rt) => self
1418                .analysis
1419                .caches
1420                .signatures
1421                .entry(hash128(&rt), self.lifetime),
1422        };
1423        res.get_or_init(|| compute(self)).clone()
1424    }
1425
1426    pub(crate) fn compute_docstring(
1427        self: &Arc<Self>,
1428        fid: TypstFileId,
1429        docs: String,
1430        kind: DefKind,
1431    ) -> Option<Arc<DocString>> {
1432        let res = self
1433            .analysis
1434            .caches
1435            .docstrings
1436            .entry(hash128(&(fid, &docs, kind)), self.lifetime);
1437        res.get_or_init(|| {
1438            crate::syntax::docs::do_compute_docstring(self, fid, docs, kind).map(Arc::new)
1439        })
1440        .clone()
1441    }
1442
1443    /// Remove html tags from markup content if necessary.
1444    pub fn remove_html(&self, markup: EcoString) -> EcoString {
1445        if !self.analysis.remove_html {
1446            return markup;
1447        }
1448
1449        static REMOVE_HTML_COMMENT_REGEX: LazyLock<regex::Regex> =
1450            LazyLock::new(|| regex::Regex::new(r#"<!--[\s\S]*?-->"#).unwrap());
1451        REMOVE_HTML_COMMENT_REGEX
1452            .replace_all(&markup, "")
1453            .trim()
1454            .into()
1455    }
1456
1457    pub(super) fn query_stat(&self, id: TypstFileId, query: &'static str) -> QueryStatGuard {
1458        self.analysis.stats.stat(Some(id), query)
1459    }
1460
1461    /// Starts statistics collection for a full expression analysis.
1462    pub(super) fn expr_stage_stat(&self, id: TypstFileId) -> QueryStatGuard {
1463        self.query_stat(id, "expr_stage")
1464    }
1465
1466    /// Check on a module before really needing them. But we likely use them
1467    /// after a while.
1468    pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1469        // crate::log_debug_ct!("prefetch type check {fid:?}");
1470        // let this = self.clone();
1471        // rayon::spawn(move || {
1472        //     let Some(source) = this.world().source(fid).ok() else {
1473        //         return;
1474        //     };
1475        //     this.type_check(&source);
1476        //     // crate::log_debug_ct!("prefetch type check end {fid:?}");
1477        // });
1478    }
1479
1480    pub(crate) fn preload_expr_stages<I>(self: Arc<Self>, files: I)
1481    where
1482        I: IntoIterator<Item = TypstFileId>,
1483    {
1484        let files: Vec<_> = files.into_iter().collect();
1485        files.par_iter().for_each(|fid| {
1486            crate::log_debug_ct!("preload expr_stage {fid:?}");
1487            let Some(source) = self.source_by_id(*fid).ok() else {
1488                return;
1489            };
1490            self.expr_stage(&source);
1491        });
1492    }
1493
1494    pub(crate) fn preload_package(self: Arc<Self>, entry_point: TypstFileId) {
1495        crate::log_debug_ct!("preload package start {entry_point:?}");
1496
1497        #[derive(Clone)]
1498        struct Preloader {
1499            shared: Arc<SharedContext>,
1500            analyzed: Arc<Mutex<HashSet<TypstFileId>>>,
1501        }
1502
1503        impl Preloader {
1504            fn work(&self, fid: TypstFileId) {
1505                crate::log_debug_ct!("preload package {fid:?}");
1506                let Some(source) = self.shared.source_by_id(fid).ok() else {
1507                    return;
1508                };
1509                let exprs = self.shared.expr_stage(&source);
1510                self.shared.type_check(&source);
1511                exprs.imports.iter().for_each(|(fid, _)| {
1512                    if !self.analyzed.lock().insert(*fid) {
1513                        return;
1514                    }
1515                    self.work(*fid);
1516                })
1517            }
1518        }
1519
1520        let preloader = Preloader {
1521            shared: self,
1522            analyzed: Arc::new(Mutex::new(HashSet::from([entry_point]))),
1523        };
1524
1525        preloader.work(entry_point);
1526    }
1527}
1528
1529// Needed by recursive computation
1530type DeferredCompute<T> = Arc<OnceLock<T>>;
1531
1532#[derive(Clone)]
1533struct IncrCacheMap<K, V> {
1534    revision: usize,
1535    global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1536    prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1537    next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1538}
1539
1540impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
1541    fn default() -> Self {
1542        Self {
1543            revision: 0,
1544            global: Arc::default(),
1545            prev: Arc::default(),
1546            next: Arc::default(),
1547        }
1548    }
1549}
1550
1551impl<K, V> IncrCacheMap<K, V> {
1552    /// Returns only a completed value from an earlier revision.
1553    fn previous(&self, key: &K) -> Option<V>
1554    where
1555        K: Clone + Eq + Hash,
1556        V: Clone,
1557    {
1558        let prev = self.prev.lock().get(key).cloned();
1559        let prev = prev.and_then(|prev| prev.get().cloned());
1560        prev.or_else(|| {
1561            let global = self.global.lock();
1562            global
1563                .get(key)
1564                .filter(|global| global.0 <= self.revision)
1565                .map(|global| global.1.clone())
1566        })
1567    }
1568
1569    /// Publishes a completed value for this revision.
1570    fn publish(&self, key: K, value: V) -> V
1571    where
1572        K: Clone + Eq + Hash,
1573        V: Clone,
1574    {
1575        let next = self.next.lock().entry(key.clone()).or_default().clone();
1576        next.get_or_init(|| {
1577            self.publish_global(key, value.clone());
1578            value
1579        })
1580        .clone()
1581    }
1582
1583    fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1584    where
1585        K: Clone + Eq + Hash,
1586        V: Clone,
1587    {
1588        let next = self.next.lock().entry(key.clone()).or_default().clone();
1589
1590        next.get_or_init(|| self.compute_and_publish(key, compute))
1591            .clone()
1592    }
1593
1594    fn compute_and_publish(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1595    where
1596        K: Clone + Eq + Hash,
1597        V: Clone,
1598    {
1599        let res = compute(self.previous(&key));
1600        self.publish_global(key, res.clone());
1601        res
1602    }
1603
1604    fn publish_global(&self, key: K, value: V)
1605    where
1606        K: Clone + Eq + Hash,
1607        V: Clone,
1608    {
1609        let global = self.global.lock();
1610        let entry = global.entry(key);
1611        use dashmap::mapref::entry::Entry;
1612        match entry {
1613            Entry::Occupied(mut entry) => {
1614                let (revision, _) = entry.get();
1615                if *revision < self.revision {
1616                    entry.insert((self.revision, value));
1617                }
1618            }
1619            Entry::Vacant(entry) => {
1620                entry.insert((self.revision, value));
1621            }
1622        }
1623    }
1624
1625    fn crawl(&self, revision: usize) -> Self {
1626        Self {
1627            revision,
1628            prev: self.next.clone(),
1629            global: self.global.clone(),
1630            next: Default::default(),
1631        }
1632    }
1633}
1634
1635#[cfg(test)]
1636mod incr_cache_tests {
1637    use super::IncrCacheMap;
1638
1639    #[test]
1640    fn panicked_initializer_leaves_slot_empty() {
1641        let cache = IncrCacheMap::<u8, u8>::default();
1642        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1643            cache.compute(1, |_| panic!("test panic"))
1644        }));
1645
1646        assert!(result.is_err());
1647        assert_eq!(cache.compute(1, |_| 2), 2);
1648    }
1649
1650    #[test]
1651    fn older_revision_does_not_read_newer_global_value() {
1652        let cache = IncrCacheMap::<u8, u8>::default();
1653        let newer = cache.crawl(2);
1654        assert_eq!(newer.compute(1, |_| 20), 20);
1655
1656        let older = cache.crawl(1);
1657        assert_eq!(
1658            older.compute(1, |prev| {
1659                assert_eq!(prev, None);
1660                10
1661            }),
1662            10
1663        );
1664        assert_eq!(
1665            older.compute(1, |_| panic!("completed value must be reused")),
1666            10
1667        );
1668    }
1669}
1670
1671#[derive(Clone)]
1672struct CacheMap<T> {
1673    m: Arc<FxDashMap<u128, (u64, T)>>,
1674    // pub alloc: AllocStats,
1675}
1676
1677impl<T> Default for CacheMap<T> {
1678    fn default() -> Self {
1679        Self {
1680            m: Default::default(),
1681            // alloc: Default::default(),
1682        }
1683    }
1684}
1685
1686impl<T> CacheMap<T> {
1687    fn clear(&self) {
1688        self.m.clear();
1689    }
1690
1691    fn retain(&self, mut f: impl FnMut(&mut (u64, T)) -> bool) {
1692        self.m.retain(|_k, v| f(v));
1693    }
1694}
1695
1696impl<T: Default + Clone> CacheMap<T> {
1697    fn entry(&self, key: u128, lifetime: u64) -> T {
1698        let entry = self.m.entry(key);
1699        let entry = entry.or_insert_with(|| (lifetime, T::default()));
1700        entry.1.clone()
1701    }
1702}
1703
1704/// Shared workers to limit resource usage
1705#[derive(Default)]
1706pub struct AnalysisGlobalWorkers {
1707    /// A possible long running import dynamic analysis task
1708    import: RateLimiter,
1709    /// A possible long running expression dynamic analysis task
1710    expression: RateLimiter,
1711    /// A possible long running tooltip dynamic analysis task
1712    tooltip: RateLimiter,
1713}
1714
1715/// A global (compiler server spanned) cache for all level of analysis results
1716/// of a module.
1717#[derive(Default, Clone)]
1718pub struct AnalysisGlobalCaches {
1719    lifetime: Arc<AtomicU64>,
1720    clear_lifetime: Arc<AtomicU64>,
1721    def_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1722    static_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1723    signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1724    docstrings: CacheMap<DeferredCompute<Option<Arc<DocString>>>>,
1725    terms: CacheMap<(Value, Ty)>,
1726}
1727
1728/// A local (lsp request spanned) cache for all level of analysis results of a
1729/// module.
1730///
1731/// You should not hold it across requests, because input like source code may
1732/// change.
1733#[derive(Default)]
1734pub struct AnalysisLocalCaches {
1735    modules: HashMap<TypstFileId, ModuleAnalysisLocalCache>,
1736    completion_files: OnceLock<Vec<TypstFileId>>,
1737    root_files: OnceLock<Vec<TypstFileId>>,
1738    module_deps: OnceLock<HashMap<TypstFileId, ModuleDependency>>,
1739}
1740
1741/// A local cache for module-level analysis results of a module.
1742///
1743/// You should not hold it across requests, because input like source code may
1744/// change.
1745#[derive(Default)]
1746pub struct ModuleAnalysisLocalCache {
1747    expr_stage: OnceLock<ExprInfo>,
1748    type_check: OnceLock<Arc<TypeInfo>>,
1749}
1750
1751/// A revision-managed (per input change) cache for all level of analysis
1752/// results of a module.
1753#[derive(Default)]
1754pub struct AnalysisRevCache {
1755    default_slot: AnalysisRevSlot,
1756    manager: RevisionManager<AnalysisRevSlot>,
1757}
1758
1759impl RevisionManagerLike for AnalysisRevCache {
1760    fn gc(&mut self, rev: usize) {
1761        self.manager.gc(rev);
1762
1763        // todo: the following code are time consuming.
1764        {
1765            let mut max_ei = FxHashMap::default();
1766            let es = self.default_slot.expr_stage.global.lock();
1767            for r in es.iter() {
1768                let rev: &mut usize = max_ei.entry(r.1.fid).or_default();
1769                *rev = (*rev).max(r.1.revision);
1770            }
1771            es.retain(|_, r| r.1.revision == *max_ei.get(&r.1.fid).unwrap_or(&0));
1772        }
1773
1774        {
1775            let mut max_ti = FxHashMap::default();
1776            let ts = self.default_slot.type_check.global.lock();
1777            for r in ts.iter() {
1778                let rev: &mut usize = max_ti.entry(r.1.fid).or_default();
1779                *rev = (*rev).max(r.1.revision);
1780            }
1781            ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
1782        }
1783
1784        {
1785            let mut max_li = FxHashMap::default();
1786            let ts = self.default_slot.lint.global.lock();
1787            for r in ts.iter() {
1788                let rev: &mut usize = max_li.entry(r.1.fid).or_default();
1789                *rev = (*rev).max(r.1.revision);
1790            }
1791            ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
1792        }
1793    }
1794}
1795
1796impl AnalysisRevCache {
1797    fn clear(&mut self) {
1798        self.manager.clear();
1799        self.default_slot = Default::default();
1800    }
1801
1802    /// Find the last revision slot by revision number.
1803    fn find_revision(
1804        &mut self,
1805        revision: NonZeroUsize,
1806        lg: &AnalysisRevLock,
1807    ) -> Arc<RevisionSlot<AnalysisRevSlot>> {
1808        lg.inner.access(revision);
1809        self.manager.find_revision(revision, |slot_base| {
1810            log::debug!("analysis revision {} is created", revision.get());
1811            slot_base
1812                .map(|slot| slot.data.crawl(revision.get()))
1813                .unwrap_or_else(|| self.default_slot.crawl(revision.get()))
1814        })
1815    }
1816}
1817
1818/// A lock for revision.
1819pub struct AnalysisRevLock {
1820    inner: RevisionLock,
1821    tokens: Option<SemanticTokenContext>,
1822    grid: Arc<Mutex<AnalysisRevCache>>,
1823}
1824
1825impl Drop for AnalysisRevLock {
1826    fn drop(&mut self) {
1827        let mut mu = self.grid.lock();
1828        let gc_revision = mu.manager.unlock(&mut self.inner);
1829
1830        if let Some(gc_revision) = gc_revision {
1831            let grid = self.grid.clone();
1832            rayon::spawn(move || {
1833                grid.lock().gc(gc_revision);
1834            });
1835        }
1836    }
1837}
1838
1839#[derive(Default)]
1840struct AnalysisRevSlot {
1841    revision: usize,
1842    components: ComponentCoordinator,
1843    expr_stage: IncrCacheMap<u128, ExprInfo>,
1844    type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
1845    lint: IncrCacheMap<u128, LintInfo>,
1846}
1847
1848impl AnalysisRevSlot {
1849    fn crawl(&self, revision: usize) -> Self {
1850        Self {
1851            revision,
1852            // Import membership and all in-flight ownership are revision-local.
1853            components: ComponentCoordinator::default(),
1854            expr_stage: self.expr_stage.crawl(revision),
1855            type_check: self.type_check.crawl(revision),
1856            lint: self.lint.crawl(revision),
1857        }
1858    }
1859}
1860
1861impl Drop for AnalysisRevSlot {
1862    fn drop(&mut self) {
1863        log::debug!("analysis revision {} is dropped", self.revision);
1864    }
1865}
1866
1867fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
1868    // while is not char boundary, move cursor to right
1869    while cursor < text.len() && !text.is_char_boundary(cursor) {
1870        cursor += 1;
1871    }
1872
1873    cursor.min(text.len())
1874}
1875
1876#[typst_macros::time]
1877#[comemo::memoize]
1878fn analyze_bib(
1879    world: Tracked<dyn World + '_>,
1880    introspector: Tracked<dyn Introspector + '_>,
1881) -> Option<Arc<BibInfo>> {
1882    let bib_elems = introspector.query(&BibliographyElem::ELEM.select());
1883    let bib_elem = bib_elems.iter().next()?.to_packed::<BibliographyElem>()?;
1884
1885    // todo: it doesn't respect the style chain which can be get from
1886    // `analyze_expr`
1887    let csl_style = bib_elem.style.get_cloned(StyleChain::default()).derived;
1888
1889    let Value::Array(paths) = bib_elem.sources.clone().into_value() else {
1890        return None;
1891    };
1892    let elem_fid = bib_elem.span().id()?;
1893    let files = paths
1894        .into_iter()
1895        .flat_map(|path| path.cast().ok())
1896        .flat_map(|bib_path: EcoString| {
1897            let bib_fid = resolve_id_by_path(world.deref(), elem_fid, &bib_path)?;
1898            Some((bib_fid, world.file(bib_fid).ok()?))
1899        });
1900
1901    bib_info(csl_style, files)
1902}
1903
1904#[comemo::memoize]
1905fn loc_info(bytes: Bytes) -> Option<EcoVec<(usize, String)>> {
1906    let mut loc = EcoVec::new();
1907    let mut offset = 0;
1908    for line in bytes.split(|byte| *byte == b'\n') {
1909        loc.push((offset, String::from_utf8(line.to_owned()).ok()?));
1910        offset += line.len() + 1;
1911    }
1912    Some(loc)
1913}
1914
1915fn find_loc(
1916    len: usize,
1917    loc: &EcoVec<(usize, String)>,
1918    mut offset: usize,
1919    encoding: PositionEncoding,
1920) -> Option<LspPosition> {
1921    if offset > len {
1922        offset = len;
1923    }
1924
1925    let r = match loc.binary_search_by_key(&offset, |line| line.0) {
1926        Ok(i) => i,
1927        Err(i) => i - 1,
1928    };
1929
1930    let (start, s) = loc.get(r)?;
1931    let byte_offset = offset.saturating_sub(*start);
1932
1933    let column_prefix = if byte_offset <= s.len() {
1934        &s[..byte_offset]
1935    } else {
1936        let line = (r + 1) as u32;
1937        return Some(LspPosition { line, character: 0 });
1938    };
1939
1940    let line = r as u32;
1941    let character = match encoding {
1942        PositionEncoding::Utf8 => column_prefix.chars().count(),
1943        PositionEncoding::Utf16 => column_prefix.chars().map(|ch| ch.len_utf16()).sum(),
1944    } as u32;
1945
1946    Some(LspPosition { line, character })
1947}
1948
1949/// The context for searching in the workspace.
1950pub struct SearchCtx<'a> {
1951    /// The inner analysis context.
1952    pub ctx: &'a mut LocalContext,
1953    /// The set of files that have been searched.
1954    pub searched: HashSet<TypstFileId>,
1955    /// The files that need to be searched.
1956    pub worklist: Vec<TypstFileId>,
1957}
1958
1959impl SearchCtx<'_> {
1960    /// Push a file to the worklist.
1961    pub fn push(&mut self, fid: TypstFileId) -> bool {
1962        if self.searched.insert(fid) {
1963            self.worklist.push(fid);
1964            true
1965        } else {
1966            false
1967        }
1968    }
1969
1970    /// Push the dependents of a file to the worklist.
1971    pub fn push_dependents(&mut self, fid: TypstFileId) {
1972        let deps = self.ctx.module_dependencies().get(&fid);
1973        let dependents = deps.map(|dep| dep.dependents.clone()).into_iter().flatten();
1974        for dep in dependents {
1975            self.push(dep);
1976        }
1977    }
1978}
1979
1980/// A rate limiter on some (cpu-heavy) action
1981#[derive(Default)]
1982pub struct RateLimiter {
1983    token: std::sync::Mutex<()>,
1984}
1985
1986impl RateLimiter {
1987    /// Executes some (cpu-heavy) action with rate limit
1988    #[must_use]
1989    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1990        let _c = self.token.lock().unwrap();
1991        f()
1992    }
1993}