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::{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/// The analysis data holds globally.
63#[derive(Default, Clone)]
64pub struct Analysis {
65    /// The position encoding for the workspace.
66    pub position_encoding: PositionEncoding,
67    /// Whether to allow overlapping semantic tokens.
68    pub allow_overlapping_token: bool,
69    /// Whether to allow multiline semantic tokens.
70    pub allow_multiline_token: bool,
71    /// Whether to remove html from markup content in responses.
72    pub remove_html: bool,
73    /// Whether to add client-side code lens.
74    pub support_client_codelens: bool,
75    /// Whether to utilize the extended `tinymist.resolveCodeAction` at client
76    /// side.
77    ///
78    /// The extended feature by `tinymist.resolveCodeAction`:
79    /// - supports Snippet edit.
80    ///
81    /// The example implementation can be found in the VS Code extension.
82    pub extended_code_action: bool,
83    /// Tinymist's completion features.
84    pub completion_feat: CompletionFeat,
85    /// The editor's color theme.
86    pub color_theme: ColorTheme,
87    /// When to trigger the lint.
88    pub lint: TaskWhen,
89    /// The periscope provider.
90    pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
91    /// The global worker resources for analysis.
92    pub workers: Arc<AnalysisGlobalWorkers>,
93    /// The local package cache.
94    pub local_packages: Arc<Mutex<OnceLock<EcoVec<PackageIndexEntry>>>>,
95    /// The semantic token cache.
96    pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
97    /// The global caches for analysis.
98    pub caches: AnalysisGlobalCaches,
99    /// The revision-managed cache for analysis.
100    pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
101    /// The statistics about the analyzers.
102    pub stats: Arc<AnalysisStats>,
103}
104
105impl Analysis {
106    /// Enters the analysis context.
107    pub fn enter(&self, g: LspComputeGraph) -> LocalContextGuard {
108        self.enter_(g, self.lock_revision(None))
109    }
110
111    /// Enters the analysis context.
112    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    /// Gets a snapshot for language queries.
135    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    /// Locks the revision in *main thread*.
149    #[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    /// Clear all cached resources.
173    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    /// Report the statistics of the analysis.
185    pub fn report_query_stats(&self) -> String {
186        self.stats.report()
187    }
188
189    /// Report the structured statistics of the analysis.
190    pub fn report_query_stats_json(&self) -> Vec<QueryStatReportEntry> {
191        self.stats.report_json()
192    }
193
194    /// Report the statistics of the allocation.
195    pub fn report_alloc_stats(&self) -> String {
196        AllocStats::report()
197    }
198
199    /// Get configured trigger suggest command.
200    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    /// Get configured trigger parameter hints command.
207    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    /// Get configured trigger suggest after snippet command.
213    ///
214    /// > VS Code doesn't do that... Auto triggering suggestion only happens on
215    /// > typing (word starts or trigger characters). However, you can use
216    /// > editor.action.triggerSuggest as command on a suggestion to "manually"
217    /// > retrigger suggest after inserting one
218    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    /// Get configured trigger on positional parameter hints command.
227    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
238/// The periscope provider.
239pub trait PeriscopeProvider {
240    /// Resolve telescope image at the given position.
241    fn periscope_at(
242        &self,
243        _ctx: &mut LocalContext,
244        _doc: &TypstDocument,
245        _pos: Position,
246    ) -> Option<String> {
247        None
248    }
249}
250
251/// The local context guard that performs gc once dropped.
252pub struct LocalContextGuard {
253    /// The guarded local context
254    pub local: LocalContext,
255    /// The revision lock
256    _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
273// todo: gc in new thread
274impl 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
312/// The local context for analyzers. In addition to the shared context, it also
313/// holds mutable local caches.
314pub struct LocalContext {
315    /// The created semantic token context.
316    pub(crate) tokens: Option<SemanticTokenContext>,
317    /// Local caches for analysis.
318    pub caches: AnalysisLocalCaches,
319    /// The shared context
320    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    /// Set list of packages for LSP-based completion.
339    #[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    /// Set the files for LSP-based completion.
349    #[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    /// Set the files for analysis.
355    #[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    /// Get all the source files in the workspace.
361    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    /// Get all the source files in the workspace.
390    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    /// Get the module dependencies of the workspace.
401    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            // may cause multiple times to calculate, but it is okay because we have mutable
406            // reference to self.
407            let deps = construct_module_dependencies(self);
408            self.caches.module_deps.get_or_init(|| deps)
409        }
410    }
411
412    /// Get all depended files in the workspace, inclusively.
413    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    /// Get all depended file ids of a compilation, inclusively.
423    /// Note: must be called after compilation.
424    pub fn depended_files(&self) -> EcoVec<TypstFileId> {
425        self.world().depended_files()
426    }
427
428    /// Get the shared context.
429    pub fn shared(&self) -> &Arc<SharedContext> {
430        &self.shared
431    }
432
433    /// Get the shared context.
434    pub fn shared_(&self) -> Arc<SharedContext> {
435        self.shared.clone()
436    }
437
438    /// Fork a new context for searching in the workspace.
439    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    /// Get the expression information of a source file.
486    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    /// Get the expression information of a source file.
491    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    /// Get the type check information of a source file.
498    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    /// Get the type check information of a source file.
513    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        // let plain_docs = sym.head.docs.as_deref();
534        // let plain_docs = plain_docs.or(sym.head.oneliner.as_deref());
535        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/// A concurrent per-request cache for expensive shared computations.
557#[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    /// Gets a cached value for `key`, initializing it once if absent.
579    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
589/// The shared analysis context for analyzers.
590pub struct SharedContext {
591    /// The caches lifetime tick for analysis.
592    pub lifetime: u64,
593    // The world surface for Typst compiler.
594    // pub world: LspWorld,
595    /// The project snapshot with shared compute cache.
596    pub graph: LspComputeGraph,
597    /// The analysis data
598    pub analysis: Analysis,
599    /// The using analysis revision slot
600    slot: Arc<RevisionSlot<AnalysisRevSlot>>,
601}
602
603impl SharedContext {
604    /// Gets the revision of current analysis
605    pub fn revision(&self) -> usize {
606        self.slot.revision
607    }
608
609    /// Gets the position encoding during session.
610    pub(crate) fn position_encoding(&self) -> PositionEncoding {
611        self.analysis.position_encoding
612    }
613
614    /// Gets the world surface for Typst compiler.
615    pub fn world(&self) -> &LspWorld {
616        self.graph.world()
617    }
618
619    /// Gets the success document.
620    pub fn success_doc(&self) -> Option<&TypstDocument> {
621        self.graph.snap.success_doc.as_ref()
622    }
623
624    /// Converts an LSP position to a Typst position.
625    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    /// Converts an LSP position with some offset.
630    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    /// Converts a Typst offset to an LSP position.
641    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    /// Converts an LSP range to a Typst range.
646    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    /// Converts a Typst range to an LSP range.
651    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    /// Converts a Typst range to an LSP range.
656    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        // yaml/yml/bib
663        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            // binary search
668            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    /// Resolves the real path for a file id.
679    pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
680        self.world().path_for_id(id)
681    }
682
683    /// Resolves the uri for a file id.
684    pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
685        self.world().uri_for_id(fid)
686    }
687
688    /// Gets file's id by its path
689    pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
690        self.world().file_id_by_path(path)
691    }
692
693    /// Gets the content of a file by file id.
694    pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
695        self.world().file(fid)
696    }
697
698    /// Gets the source of a file by file id.
699    pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
700        self.world().source(fid)
701    }
702
703    /// Gets the source of a file by file path.
704    pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
705        self.source_by_id(self.file_id_by_path(path)?)
706    }
707
708    /// Classifies the syntax under a span that can be operated on by IDE
709    /// functionality.
710    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    /// Classifies the syntax under position that can be operated on by IDE
717    /// functionality. It is preferred to select a decl if it is at the starts
718    /// of some mark.
719    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        // In the case that the cursor is at the end of an identifier.
728        // e.g. `f(x|)`, we will select the `x`
729        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    /// Resolves extra font information.
742    pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
743        self.world().font_resolver.describe_font(&font)
744    }
745
746    /// Gets the packages other than that in the preview namespace and their
747    /// descriptions.
748    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    /// Gets a module by file id.
775    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    /// Gets a module by string.
781    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    /// Gets (Creates) a module by source.
787    pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
788        eval_compat(&self.world(), &source)
789    }
790
791    /// Gets a module value from a given source file.
792    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    /// Gets a module term from a given source file. If `value` is true, it will
798    /// prefer to get a value instead of a term.
799    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    /// Gets the expression information of a source file.
825    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    /// Gets the expression information of a source file.
830    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    /// Gets the expression information of a source file.
836    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    /// Gets the type check information of a source file.
861    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    /// Gets the type check information of a source file.
867    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            // todo: recursively check changed scheme type
878            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    /// Gets the lint result of a source file.
888    #[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        // todo: check performance on peeking signature source frequently
908        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    /// Gets the definition from a source location.
933    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    /// Gets the definition from static analysis.
939    ///
940    /// Passing a `doc` (compiled result) can help resolve dynamic things, e.g.
941    /// label definitions.
942    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    /// Gets the definition from static analysis or dynamic analysis.
951    ///
952    /// Note: while this has best quality in typst, it is expensive.
953    /// Use it if you know it is only called `O(1)` times to serve a user LSP
954    /// request, e.g. resolve a function definition for `completion`.
955    /// Otherwise, use `def_of_syntax`, e.g. resolves all definitions for
956    /// package docs.
957    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            // todo: DefKind::Function
965            Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
966            Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
967        }
968
969        // Checks that we resolved a high-equality definition.
970        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        // Uses the dynamic definition or the fallback definition.
996        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                // Converts with cache
1074                analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
1075            })
1076        })
1077    }
1078
1079    /// Try to find imported target from the current source file.
1080    /// This function will try to resolves target statically.
1081    ///
1082    /// ## Returns
1083    /// The first value is the resolved source.
1084    /// The second value is the resolved scope.
1085    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    /// Try to load a module from the current source file.
1094    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    /// Get bib info of a source file.
1100    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    /// Describe the item under the cursor.
1108    ///
1109    /// Passing a `document` (from a previous compilation) is optional, but
1110    /// enhances the autocompletions. Label completions, for instance, are
1111    /// only generated when the document is available.
1112    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    /// Get the manifest of a package by file id.
1118    pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1119        crate::package::get_manifest(self.world(), toml_id)
1120    }
1121
1122    /// Compute the signature of a function.
1123    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    /// Remove html tags from markup content if necessary.
1180    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    /// Check on a module before really needing them. But we likely use them
1198    /// after a while.
1199    pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1200        // crate::log_debug_ct!("prefetch type check {fid:?}");
1201        // let this = self.clone();
1202        // rayon::spawn(move || {
1203        //     let Some(source) = this.world().source(fid).ok() else {
1204        //         return;
1205        //     };
1206        //     this.type_check(&source);
1207        //     // crate::log_debug_ct!("prefetch type check end {fid:?}");
1208        // });
1209    }
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
1260// Needed by recursive computation
1261type 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    // pub alloc: AllocStats,
1334}
1335
1336impl<T> Default for CacheMap<T> {
1337    fn default() -> Self {
1338        Self {
1339            m: Default::default(),
1340            // alloc: Default::default(),
1341        }
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/// Shared workers to limit resource usage
1364#[derive(Default)]
1365pub struct AnalysisGlobalWorkers {
1366    /// A possible long running import dynamic analysis task
1367    import: RateLimiter,
1368    /// A possible long running expression dynamic analysis task
1369    expression: RateLimiter,
1370    /// A possible long running tooltip dynamic analysis task
1371    tooltip: RateLimiter,
1372}
1373
1374/// A global (compiler server spanned) cache for all level of analysis results
1375/// of a module.
1376#[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/// A local (lsp request spanned) cache for all level of analysis results of a
1388/// module.
1389///
1390/// You should not hold it across requests, because input like source code may
1391/// change.
1392#[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/// A local cache for module-level analysis results of a module.
1401///
1402/// You should not hold it across requests, because input like source code may
1403/// change.
1404#[derive(Default)]
1405pub struct ModuleAnalysisLocalCache {
1406    expr_stage: OnceLock<ExprInfo>,
1407    type_check: OnceLock<Arc<TypeInfo>>,
1408}
1409
1410/// A revision-managed (per input change) cache for all level of analysis
1411/// results of a module.
1412#[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        // todo: the following code are time consuming.
1423        {
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    /// Find the last revision slot by revision number.
1462    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
1482/// A lock for revision.
1483pub 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 is not char boundary, move cursor to right
1519    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    // todo: it doesn't respect the style chain which can be get from
1536    // `analyze_expr`
1537    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
1599/// The context for searching in the workspace.
1600pub struct SearchCtx<'a> {
1601    /// The inner analysis context.
1602    pub ctx: &'a mut LocalContext,
1603    /// The set of files that have been searched.
1604    pub searched: HashSet<TypstFileId>,
1605    /// The files that need to be searched.
1606    pub worklist: Vec<TypstFileId>,
1607}
1608
1609impl SearchCtx<'_> {
1610    /// Push a file to the worklist.
1611    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    /// Push the dependents of a file to the worklist.
1621    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/// A rate limiter on some (cpu-heavy) action
1631#[derive(Default)]
1632pub struct RateLimiter {
1633    token: std::sync::Mutex<()>,
1634}
1635
1636impl RateLimiter {
1637    /// Executes some (cpu-heavy) action with rate limit
1638    #[must_use]
1639    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1640        let _c = self.token.lock().unwrap();
1641        f()
1642    }
1643}