tinymist_query/analysis/
global.rs

1use std::num::NonZeroUsize;
2use std::ops::DerefMut;
3use std::sync::OnceLock;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::{collections::HashSet, ops::Deref};
6
7use comemo::{Track, Tracked};
8use lsp_types::Url;
9use parking_lot::Mutex;
10use rustc_hash::FxHashMap;
11use tinymist_analysis::docs::DocString;
12use tinymist_analysis::stats::AllocStats;
13use tinymist_analysis::syntax::classify_def_loosely;
14use tinymist_analysis::ty::{BuiltinTy, InsTy, term_value};
15use tinymist_analysis::{analyze_expr_, analyze_import_};
16use tinymist_lint::{KnownIssues, LintInfo};
17use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
18use tinymist_std::hash::{FxDashMap, hash128};
19use tinymist_std::typst::TypstDocument;
20use tinymist_world::debug_loc::DataSource;
21use tinymist_world::package::registry::PackageIndexEntry;
22use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
23use tinymist_world::{DETACHED_ENTRY, EntryReader};
24use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult};
25use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles};
26use typst::introspection::Introspector;
27use typst::layout::Position;
28use typst::model::BibliographyElem;
29use typst::syntax::package::PackageManifest;
30use typst::syntax::{Span, VirtualPath};
31use typst_shim::eval::{Eval, eval_compat};
32
33use super::{LspQuerySnapshot, TypeEnv};
34use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
35use crate::analysis::prelude::*;
36use crate::analysis::{
37    AnalysisStats, BibInfo, CompletionFeat, Definition, PathKind, QueryStatGuard,
38    SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
39    TypeInfo, analyze_signature, bib_info, definition, post_type_check,
40};
41use crate::docs::{DefDocs, TidyModuleDocs};
42use crate::syntax::{
43    Decl, DefKind, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, SyntaxClass,
44    classify_syntax, construct_module_dependencies, is_mark, resolve_id_by_path,
45    scan_workspace_files,
46};
47use crate::upstream::{Tooltip, tooltip_};
48use crate::{
49    ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
50};
51
52macro_rules! interned_str {
53    ($name:ident, $value:expr) => {
54        static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
55    };
56}
57
58/// The analysis data holds globally.
59#[derive(Default, Clone)]
60pub struct Analysis {
61    /// The position encoding for the workspace.
62    pub position_encoding: PositionEncoding,
63    /// Whether to allow overlapping semantic tokens.
64    pub allow_overlapping_token: bool,
65    /// Whether to allow multiline semantic tokens.
66    pub allow_multiline_token: bool,
67    /// Whether to remove html from markup content in responses.
68    pub remove_html: bool,
69    /// Whether to add client-side code lens.
70    pub support_client_codelens: bool,
71    /// Whether to utilize the extended `tinymist.resolveCodeAction` at client
72    /// side.
73    ///
74    /// The extended feature by `tinymist.resolveCodeAction`:
75    /// - supports Snippet edit.
76    ///
77    /// The example implementation can be found in the VS Code extension.
78    pub extended_code_action: bool,
79    /// Tinymist's completion features.
80    pub completion_feat: CompletionFeat,
81    /// The editor's color theme.
82    pub color_theme: ColorTheme,
83    /// When to trigger the lint.
84    pub lint: TaskWhen,
85    /// The periscope provider.
86    pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
87    /// The global worker resources for analysis.
88    pub workers: Arc<AnalysisGlobalWorkers>,
89    /// The local package cache.
90    pub local_packages: Arc<Mutex<OnceLock<EcoVec<PackageIndexEntry>>>>,
91    /// The semantic token cache.
92    pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
93    /// The global caches for analysis.
94    pub caches: AnalysisGlobalCaches,
95    /// The revision-managed cache for analysis.
96    pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
97    /// The statistics about the analyzers.
98    pub stats: Arc<AnalysisStats>,
99}
100
101impl Analysis {
102    /// Enters the analysis context.
103    pub fn enter(&self, g: LspComputeGraph) -> LocalContextGuard {
104        self.enter_(g, self.lock_revision(None))
105    }
106
107    /// Enters the analysis context.
108    pub(crate) fn enter_(&self, g: LspComputeGraph, mut lg: AnalysisRevLock) -> LocalContextGuard {
109        let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst);
110        let slot = self
111            .analysis_rev_cache
112            .lock()
113            .find_revision(g.world().revision(), &lg);
114        let tokens = lg.tokens.take();
115        LocalContextGuard {
116            _rev_lock: lg,
117            local: LocalContext {
118                tokens,
119                caches: AnalysisLocalCaches::default(),
120                shared: Arc::new(SharedContext {
121                    slot,
122                    lifetime,
123                    graph: g,
124                    analysis: self.clone(),
125                }),
126            },
127        }
128    }
129
130    /// Gets a snapshot for language queries.
131    pub fn query_snapshot(
132        self: Arc<Self>,
133        snap: LspComputeGraph,
134        req: Option<&CompilerQueryRequest>,
135    ) -> LspQuerySnapshot {
136        let rev_lock = self.lock_revision(req);
137        LspQuerySnapshot {
138            snap,
139            analysis: self,
140            rev_lock,
141        }
142    }
143
144    /// Locks the revision in *main thread*.
145    #[must_use]
146    pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock {
147        let mut grid = self.analysis_rev_cache.lock();
148
149        AnalysisRevLock {
150            tokens: match req {
151                Some(CompilerQueryRequest::SemanticTokensFull(req)) => Some(
152                    SemanticTokenCache::acquire(self.tokens_caches.clone(), &req.path, None),
153                ),
154                Some(CompilerQueryRequest::SemanticTokensDelta(req)) => {
155                    Some(SemanticTokenCache::acquire(
156                        self.tokens_caches.clone(),
157                        &req.path,
158                        Some(&req.previous_result_id),
159                    ))
160                }
161                _ => None,
162            },
163            inner: grid.manager.lock_estimated(),
164            grid: self.analysis_rev_cache.clone(),
165        }
166    }
167
168    /// Clear all cached resources.
169    pub fn clear_cache(&self) {
170        self.caches.signatures.clear();
171        self.caches.docstrings.clear();
172        self.caches.def_signatures.clear();
173        self.caches.static_signatures.clear();
174        self.caches.terms.clear();
175        *self.local_packages.lock() = OnceLock::default();
176        self.tokens_caches.lock().clear();
177        self.analysis_rev_cache.lock().clear();
178    }
179
180    /// Report the statistics of the analysis.
181    pub fn report_query_stats(&self) -> String {
182        self.stats.report()
183    }
184
185    /// Report the statistics of the allocation.
186    pub fn report_alloc_stats(&self) -> String {
187        AllocStats::report()
188    }
189
190    /// Get configured trigger suggest command.
191    pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
192        interned_str!(INTERNED, "editor.action.triggerSuggest");
193
194        (self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
195    }
196
197    /// Get configured trigger parameter hints command.
198    pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
199        interned_str!(INTERNED, "editor.action.triggerParameterHints");
200        (self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
201    }
202
203    /// Get configured trigger suggest after snippet command.
204    ///
205    /// > VS Code doesn't do that... Auto triggering suggestion only happens on
206    /// > typing (word starts or trigger characters). However, you can use
207    /// > editor.action.triggerSuggest as command on a suggestion to "manually"
208    /// > retrigger suggest after inserting one
209    pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
210        if !self.completion_feat.trigger_on_snippet_placeholders {
211            return None;
212        }
213
214        self.trigger_suggest(context)
215    }
216
217    /// Get configured trigger on positional parameter hints command.
218    pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
219        interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
220        if !self.completion_feat.trigger_on_snippet_placeholders {
221            return self.trigger_parameter_hints(context);
222        }
223
224        (self.completion_feat.trigger_suggest_and_parameter_hints && context)
225            .then(|| INTERNED.clone())
226    }
227}
228
229/// The periscope provider.
230pub trait PeriscopeProvider {
231    /// Resolve telescope image at the given position.
232    fn periscope_at(
233        &self,
234        _ctx: &mut LocalContext,
235        _doc: &TypstDocument,
236        _pos: Position,
237    ) -> Option<String> {
238        None
239    }
240}
241
242/// The local context guard that performs gc once dropped.
243pub struct LocalContextGuard {
244    /// The guarded local context
245    pub local: LocalContext,
246    /// The revision lock
247    _rev_lock: AnalysisRevLock,
248}
249
250impl Deref for LocalContextGuard {
251    type Target = LocalContext;
252
253    fn deref(&self) -> &Self::Target {
254        &self.local
255    }
256}
257
258impl DerefMut for LocalContextGuard {
259    fn deref_mut(&mut self) -> &mut Self::Target {
260        &mut self.local
261    }
262}
263
264// todo: gc in new thread
265impl Drop for LocalContextGuard {
266    fn drop(&mut self) {
267        self.gc();
268    }
269}
270
271impl LocalContextGuard {
272    fn gc(&self) {
273        let lifetime = self.lifetime;
274        loop {
275            let latest_clear_lifetime = self.analysis.caches.clear_lifetime.load(Ordering::Relaxed);
276            if latest_clear_lifetime >= lifetime {
277                return;
278            }
279
280            if self.analysis.caches.clear_lifetime.compare_exchange(
281                latest_clear_lifetime,
282                lifetime,
283                Ordering::SeqCst,
284                Ordering::SeqCst,
285            ) != Ok(latest_clear_lifetime)
286            {
287                continue;
288            }
289
290            break;
291        }
292
293        let retainer = |l: u64| lifetime.saturating_sub(l) < 60;
294        let caches = &self.analysis.caches;
295        caches.def_signatures.retain(|(l, _)| retainer(*l));
296        caches.static_signatures.retain(|(l, _)| retainer(*l));
297        caches.terms.retain(|(l, _)| retainer(*l));
298        caches.signatures.retain(|(l, _)| retainer(*l));
299        caches.docstrings.retain(|(l, _)| retainer(*l));
300    }
301}
302
303/// The local context for analyzers. In addition to the shared context, it also
304/// holds mutable local caches.
305pub struct LocalContext {
306    /// The created semantic token context.
307    pub(crate) tokens: Option<SemanticTokenContext>,
308    /// Local caches for analysis.
309    pub caches: AnalysisLocalCaches,
310    /// The shared context
311    pub shared: Arc<SharedContext>,
312}
313
314impl Deref for LocalContext {
315    type Target = Arc<SharedContext>;
316
317    fn deref(&self) -> &Self::Target {
318        &self.shared
319    }
320}
321
322impl DerefMut for LocalContext {
323    fn deref_mut(&mut self) -> &mut Self::Target {
324        &mut self.shared
325    }
326}
327
328impl LocalContext {
329    /// Set list of packages for LSP-based completion.
330    #[cfg(test)]
331    pub fn test_package_list(&mut self, f: impl FnOnce() -> Vec<PackageIndexEntry> + Clone) {
332        self.world().registry.test_package_list(f.clone());
333        self.analysis
334            .local_packages
335            .lock()
336            .get_or_init(|| f().into_iter().collect());
337    }
338
339    /// Set the files for LSP-based completion.
340    #[cfg(test)]
341    pub fn test_completion_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
342        self.caches.completion_files.get_or_init(f);
343    }
344
345    /// Set the files for analysis.
346    #[cfg(test)]
347    pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
348        self.caches.root_files.get_or_init(f);
349    }
350
351    /// Get all the source files in the workspace.
352    pub(crate) fn completion_files(&self, pref: &PathKind) -> impl Iterator<Item = &TypstFileId> {
353        let regexes = pref.ext_matcher();
354        self.caches
355            .completion_files
356            .get_or_init(|| {
357                if let Some(root) = self.world().entry_state().workspace_root() {
358                    scan_workspace_files(&root, PathKind::Special.ext_matcher(), |path| {
359                        WorkspaceResolver::workspace_file(Some(&root), VirtualPath::new(path))
360                    })
361                } else {
362                    vec![]
363                }
364            })
365            .iter()
366            .filter(move |fid| {
367                fid.vpath()
368                    .as_rooted_path()
369                    .extension()
370                    .and_then(|path| path.to_str())
371                    .is_some_and(|path| regexes.is_match(path))
372            })
373    }
374
375    /// Get all the source files in the workspace.
376    pub fn source_files(&self) -> &Vec<TypstFileId> {
377        self.caches.root_files.get_or_init(|| {
378            self.completion_files(&PathKind::Source {
379                allow_package: false,
380            })
381            .copied()
382            .collect()
383        })
384    }
385
386    /// Get the module dependencies of the workspace.
387    pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
388        if self.caches.module_deps.get().is_some() {
389            self.caches.module_deps.get().unwrap()
390        } else {
391            // may cause multiple times to calculate, but it is okay because we have mutable
392            // reference to self.
393            let deps = construct_module_dependencies(self);
394            self.caches.module_deps.get_or_init(|| deps)
395        }
396    }
397
398    /// Get all depended files in the workspace, inclusively.
399    pub fn depended_source_files(&self) -> EcoVec<TypstFileId> {
400        let mut ids = self.depended_files();
401        let preference = PathKind::Source {
402            allow_package: false,
403        };
404        ids.retain(|id| preference.is_match(id.vpath().as_rooted_path()));
405        ids
406    }
407
408    /// Get all depended file ids of a compilation, inclusively.
409    /// Note: must be called after compilation.
410    pub fn depended_files(&self) -> EcoVec<TypstFileId> {
411        self.world().depended_files()
412    }
413
414    /// Get the shared context.
415    pub fn shared(&self) -> &Arc<SharedContext> {
416        &self.shared
417    }
418
419    /// Get the shared context.
420    pub fn shared_(&self) -> Arc<SharedContext> {
421        self.shared.clone()
422    }
423
424    /// Fork a new context for searching in the workspace.
425    pub fn fork_for_search(&mut self) -> SearchCtx<'_> {
426        SearchCtx {
427            ctx: self,
428            searched: Default::default(),
429            worklist: Default::default(),
430        }
431    }
432
433    pub(crate) fn preload_package(&self, entry_point: TypstFileId) {
434        self.shared_().preload_package(entry_point);
435    }
436
437    pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst_shim::eval::Vm) -> T) -> T {
438        crate::upstream::with_vm((self.world() as &dyn World).track(), f)
439    }
440
441    pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
442        SharedContext::const_eval(rr)
443    }
444
445    pub(crate) fn mini_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
446        self.const_eval(rr)
447            .or_else(|| self.with_vm(|vm| rr.eval(vm).ok()))
448    }
449
450    pub(crate) fn cached_tokens(&mut self, source: &Source) -> (SemanticTokens, Option<String>) {
451        let tokens = crate::analysis::semantic_tokens::get_semantic_tokens(self, source);
452
453        let result_id = self.tokens.as_ref().map(|t| {
454            let id = t.next.revision;
455            t.next
456                .data
457                .set(tokens.clone())
458                .unwrap_or_else(|_| panic!("unexpected slot overwrite {id}"));
459            id.to_string()
460        });
461        (tokens, result_id)
462    }
463
464    /// Get the expression information of a source file.
465    pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
466        Some(self.expr_stage(&self.source_by_id(fid).ok()?))
467    }
468
469    /// Get the expression information of a source file.
470    pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo {
471        let id = source.id();
472        let cache = &self.caches.modules.entry(id).or_default().expr_stage;
473        cache.get_or_init(|| self.shared.expr_stage(source)).clone()
474    }
475
476    /// Get the type check information of a source file.
477    pub(crate) fn type_check(&mut self, source: &Source) -> Arc<TypeInfo> {
478        let id = source.id();
479        let cache = &self.caches.modules.entry(id).or_default().type_check;
480        cache.get_or_init(|| self.shared.type_check(source)).clone()
481    }
482
483    pub(crate) fn lint(
484        &mut self,
485        source: &Source,
486        known_issues: &KnownIssues,
487    ) -> EcoVec<SourceDiagnostic> {
488        self.shared.lint(source, known_issues).diagnostics
489    }
490
491    /// Get the type check information of a source file.
492    pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
493        let cache = &self.caches.modules.entry(id).or_default().type_check;
494        cache
495            .clone()
496            .get_or_init(|| {
497                let source = self.source_by_id(id).ok();
498                source
499                    .map(|s| self.shared.type_check(&s))
500                    .unwrap_or_default()
501            })
502            .clone()
503    }
504
505    pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
506        let scheme = self.type_check_by_id(s.id()?);
507        let ty = scheme.type_of_span(s)?;
508        Some(scheme.simplify(ty, false))
509    }
510
511    pub(crate) fn def_docs(&mut self, def: &Definition) -> Option<DefDocs> {
512        // let plain_docs = sym.head.docs.as_deref();
513        // let plain_docs = plain_docs.or(sym.head.oneliner.as_deref());
514        match def.decl.kind() {
515            DefKind::Function => {
516                let sig = self.sig_of_def(def.clone())?;
517                let docs = crate::docs::sig_docs(&sig)?;
518                Some(DefDocs::Function(Box::new(docs)))
519            }
520            DefKind::Struct | DefKind::Constant | DefKind::Variable => {
521                let docs = crate::docs::var_docs(self, def.decl.span())?;
522                Some(DefDocs::Variable(docs))
523            }
524            DefKind::Module => {
525                let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
526                Some(DefDocs::Module(TidyModuleDocs {
527                    docs: ei.module_docstring.docs.clone().unwrap_or_default(),
528                }))
529            }
530            DefKind::Reference => None,
531        }
532    }
533}
534
535/// The shared analysis context for analyzers.
536pub struct SharedContext {
537    /// The caches lifetime tick for analysis.
538    pub lifetime: u64,
539    // The world surface for Typst compiler.
540    // pub world: LspWorld,
541    /// The project snapshot with shared compute cache.
542    pub graph: LspComputeGraph,
543    /// The analysis data
544    pub analysis: Analysis,
545    /// The using analysis revision slot
546    slot: Arc<RevisionSlot<AnalysisRevSlot>>,
547}
548
549impl SharedContext {
550    /// Gets the revision of current analysis
551    pub fn revision(&self) -> usize {
552        self.slot.revision
553    }
554
555    /// Gets the position encoding during session.
556    pub(crate) fn position_encoding(&self) -> PositionEncoding {
557        self.analysis.position_encoding
558    }
559
560    /// Gets the world surface for Typst compiler.
561    pub fn world(&self) -> &LspWorld {
562        self.graph.world()
563    }
564
565    /// Gets the success document.
566    pub fn success_doc(&self) -> Option<&TypstDocument> {
567        self.graph.snap.success_doc.as_ref()
568    }
569
570    /// Converts an LSP position to a Typst position.
571    pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
572        crate::to_typst_position(position, self.analysis.position_encoding, src)
573    }
574
575    /// Converts an LSP position with some offset.
576    pub fn to_typst_pos_offset(
577        &self,
578        source: &Source,
579        position: LspPosition,
580        shift: usize,
581    ) -> Option<usize> {
582        let offset = self.to_typst_pos(position, source)?;
583        Some(ceil_char_boundary(source.text(), offset + shift))
584    }
585
586    /// Converts a Typst offset to an LSP position.
587    pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
588        crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src)
589    }
590
591    /// Converts an LSP range to a Typst range.
592    pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<Range<usize>> {
593        crate::to_typst_range(position, self.analysis.position_encoding, src)
594    }
595
596    /// Converts a Typst range to an LSP range.
597    pub fn to_lsp_range(&self, position: Range<usize>, src: &Source) -> LspRange {
598        crate::to_lsp_range(position, src, self.analysis.position_encoding)
599    }
600
601    /// Converts a Typst range to an LSP range.
602    pub fn to_lsp_range_(&self, position: Range<usize>, fid: TypstFileId) -> Option<LspRange> {
603        let ext = fid
604            .vpath()
605            .as_rootless_path()
606            .extension()
607            .and_then(|ext| ext.to_str());
608        // yaml/yml/bib
609        if matches!(ext, Some("yaml" | "yml" | "bib")) {
610            let bytes = self.file_by_id(fid).ok()?;
611            let bytes_len = bytes.len();
612            let loc = loc_info(bytes)?;
613            // binary search
614            let start = find_loc(bytes_len, &loc, position.start, self.position_encoding())?;
615            let end = find_loc(bytes_len, &loc, position.end, self.position_encoding())?;
616            return Some(LspRange { start, end });
617        }
618
619        let source = self.source_by_id(fid).ok()?;
620
621        Some(self.to_lsp_range(position, &source))
622    }
623
624    /// Resolves the real path for a file id.
625    pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
626        self.world().path_for_id(id)
627    }
628
629    /// Resolves the uri for a file id.
630    pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
631        self.world().uri_for_id(fid)
632    }
633
634    /// Gets file's id by its path
635    pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
636        self.world().file_id_by_path(path)
637    }
638
639    /// Gets the content of a file by file id.
640    pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
641        self.world().file(fid)
642    }
643
644    /// Gets the source of a file by file id.
645    pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
646        self.world().source(fid)
647    }
648
649    /// Gets the source of a file by file path.
650    pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
651        self.source_by_id(self.file_id_by_path(path)?)
652    }
653
654    /// Classifies the syntax under a span that can be operated on by IDE
655    /// functionality.
656    pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
657        let node = LinkedNode::new(source.root()).find(span)?;
658        let cursor = node.offset() + 1;
659        classify_syntax(node, cursor)
660    }
661
662    /// Classifies the syntax under position that can be operated on by IDE
663    /// functionality. It is preferred to select a decl if it is at the starts
664    /// of some mark.
665    pub fn classify_for_decl<'s>(
666        &self,
667        source: &'s Source,
668        position: LspPosition,
669    ) -> Option<SyntaxClass<'s>> {
670        let cursor = self.to_typst_pos_offset(source, position, 1)?;
671        let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
672
673        // In the case that the cursor is at the end of an identifier.
674        // e.g. `f(x|)`, we will select the `x`
675        if cursor == node.offset() + 1 && is_mark(node.kind()) {
676            let prev_leaf = node.prev_leaf();
677            if let Some(prev_leaf) = prev_leaf
678                && prev_leaf.range().end == node.offset()
679            {
680                node = prev_leaf;
681            }
682        }
683
684        classify_syntax(node, cursor)
685    }
686
687    /// Resolves extra font information.
688    pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
689        self.world().font_resolver.describe_font(&font)
690    }
691
692    /// Gets the packages other than that in the preview namespace and their
693    /// descriptions.
694    pub fn non_preview_packages(&self) -> EcoVec<PackageIndexEntry> {
695        #[cfg(feature = "local-registry")]
696        let it = || {
697            crate::package::list_package(
698                self.world(),
699                crate::package::PackageFilter::ExceptFor(EcoString::inline("preview")),
700            )
701        };
702        #[cfg(not(feature = "local-registry"))]
703        let it = || Default::default();
704        self.analysis.local_packages.lock().get_or_init(it).clone()
705    }
706
707    pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
708        Some(match rr {
709            ast::Expr::None(_) => Value::None,
710            ast::Expr::Auto(_) => Value::Auto,
711            ast::Expr::Bool(v) => Value::Bool(v.get()),
712            ast::Expr::Int(v) => Value::Int(v.get()),
713            ast::Expr::Float(v) => Value::Float(v.get()),
714            ast::Expr::Numeric(v) => Value::numeric(v.get()),
715            ast::Expr::Str(v) => Value::Str(v.get().into()),
716            _ => return None,
717        })
718    }
719
720    /// Gets a module by file id.
721    pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult<Module> {
722        let source = self.source_by_id(fid).at(Span::detached())?;
723        self.module_by_src(source)
724    }
725
726    /// Gets a module by string.
727    pub fn module_by_str(&self, rr: String) -> Option<Module> {
728        let src = Source::new(*DETACHED_ENTRY, rr);
729        self.module_by_src(src).ok()
730    }
731
732    /// Gets (Creates) a module by source.
733    pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
734        eval_compat(&self.world(), &source)
735    }
736
737    /// Gets a module value from a given source file.
738    pub fn module_by_syntax(self: &Arc<Self>, source: &SyntaxNode) -> Option<Value> {
739        self.module_term_by_syntax(source, true)
740            .and_then(|ty| ty.value())
741    }
742
743    /// Gets a module term from a given source file. If `value` is true, it will
744    /// prefer to get a value instead of a term.
745    pub fn module_term_by_syntax(self: &Arc<Self>, source: &SyntaxNode, value: bool) -> Option<Ty> {
746        let (src, scope) = self.analyze_import(source);
747        if let Some(scope) = scope {
748            return Some(match scope {
749                Value::Module(m) if m.file_id().is_some() => {
750                    Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into()))
751                }
752                scope => Ty::Value(InsTy::new(scope)),
753            });
754        }
755
756        match src {
757            Some(Value::Str(s)) => {
758                let id = resolve_id_by_path(self.world(), source.span().id()?, s.as_str())?;
759
760                Some(if value {
761                    Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?)))
762                } else {
763                    Ty::Builtin(BuiltinTy::Module(Decl::module(id).into()))
764                })
765            }
766            _ => None,
767        }
768    }
769
770    /// Gets the expression information of a source file.
771    pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
772        Some(self.expr_stage(&self.source_by_id(fid).ok()?))
773    }
774
775    /// Gets the expression information of a source file.
776    pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
777        let mut route = ExprRoute::default();
778        self.expr_stage_(source, &mut route)
779    }
780
781    /// Gets the expression information of a source file.
782    pub(crate) fn expr_stage_(
783        self: &Arc<Self>,
784        source: &Source,
785        route: &mut ExprRoute,
786    ) -> ExprInfo {
787        use crate::syntax::expr_of;
788        let guard = self.query_stat(source.id(), "expr_stage");
789        self.slot.expr_stage.compute(hash128(&source), |prev| {
790            expr_of(self.clone(), source.clone(), route, guard, prev)
791        })
792    }
793
794    pub(crate) fn exports_of(
795        self: &Arc<Self>,
796        source: &Source,
797        route: &mut ExprRoute,
798    ) -> Option<Arc<LazyHash<LexicalScope>>> {
799        if let Some(s) = route.get(&source.id()) {
800            return s.clone();
801        }
802
803        Some(self.expr_stage_(source, route).exports.clone())
804    }
805
806    /// Gets the type check information of a source file.
807    pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
808        let mut route = TypeEnv::default();
809        self.type_check_(source, &mut route)
810    }
811
812    /// Gets the type check information of a source file.
813    pub(crate) fn type_check_(
814        self: &Arc<Self>,
815        source: &Source,
816        route: &mut TypeEnv,
817    ) -> Arc<TypeInfo> {
818        use crate::analysis::type_check;
819
820        let ei = self.expr_stage(source);
821        let guard = self.query_stat(source.id(), "type_check");
822        self.slot.type_check.compute(hash128(&ei), |prev| {
823            // todo: recursively check changed scheme type
824            if let Some(cache_hint) = prev.filter(|prev| prev.revision == ei.revision) {
825                return cache_hint;
826            }
827
828            guard.miss();
829            type_check(self.clone(), ei, route)
830        })
831    }
832
833    /// Gets the lint result of a source file.
834    #[typst_macros::time(span = source.root().span())]
835    pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
836        let ei = self.expr_stage(source);
837        let ti = self.type_check(source);
838        let guard = self.query_stat(source.id(), "lint");
839        self.slot.lint.compute(hash128(&(&ei, &ti, issues)), |_| {
840            guard.miss();
841            tinymist_lint::lint_file(self.world(), &ei, ti, issues.clone())
842        })
843    }
844
845    pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
846        crate::log_debug_ct!("convert runtime func {func:?}");
847        analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
848    }
849
850    pub(crate) fn type_of_value(self: &Arc<Self>, val: &Value) -> Ty {
851        crate::log_debug_ct!("convert runtime value {val:?}");
852
853        // todo: check performance on peeking signature source frequently
854        let cache_key = val;
855        let cached = self
856            .analysis
857            .caches
858            .terms
859            .m
860            .get(&hash128(&cache_key))
861            .and_then(|slot| (cache_key == &slot.1.0).then_some(slot.1.1.clone()));
862        if let Some(cached) = cached {
863            return cached;
864        }
865
866        let res = term_value(val);
867
868        self.analysis
869            .caches
870            .terms
871            .m
872            .entry(hash128(&cache_key))
873            .or_insert_with(|| (self.lifetime, (cache_key.clone(), res.clone())));
874
875        res
876    }
877
878    /// Gets the definition from a source location.
879    pub(crate) fn def_of_span(self: &Arc<Self>, source: &Source, span: Span) -> Option<Definition> {
880        let syntax = self.classify_span(source, span)?;
881        definition(self, source, syntax)
882    }
883
884    /// Gets the definition from a declaration.
885    pub(crate) fn def_of_decl(&self, decl: &Interned<Decl>) -> Option<Definition> {
886        match decl.as_ref() {
887            Decl::Func(..) => Some(Definition::new(decl.clone(), None)),
888            Decl::Module(..) => None,
889            _ => None,
890        }
891    }
892
893    /// Gets the definition from static analysis.
894    ///
895    /// Passing a `doc` (compiled result) can help resolve dynamic things, e.g.
896    /// label definitions.
897    pub(crate) fn def_of_syntax(
898        self: &Arc<Self>,
899        source: &Source,
900        syntax: SyntaxClass,
901    ) -> Option<Definition> {
902        definition(self, source, syntax)
903    }
904
905    /// Gets the definition from static analysis or dynamic analysis.
906    ///
907    /// Note: while this has best quality in typst, it is expensive.
908    /// Use it if you know it is only called `O(1)` times to serve a user LSP
909    /// request, e.g. resolve a function definition for `completion`.
910    /// Otherwise, use `def_of_syntax`, e.g. resolves all definitions for
911    /// package docs.
912    pub(crate) fn def_of_syntax_or_dyn(
913        self: &Arc<Self>,
914        source: &Source,
915        syntax: SyntaxClass,
916    ) -> Option<Definition> {
917        let def = self.def_of_syntax(source, syntax.clone());
918        match def.as_ref().map(|d| d.decl.kind()) {
919            // todo: DefKind::Function
920            Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
921            Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
922        }
923
924        // Checks that we resolved a high-equality definition.
925        let know_ty_well = def
926            .as_ref()
927            .and_then(|d| self.simplified_type_of_span(d.decl.span()))
928            .filter(|ty| !matches!(ty, Ty::Any))
929            .is_some();
930        if know_ty_well {
931            return def;
932        }
933
934        let def_ref = def.as_ref();
935        let def_name = || Some(def_ref?.name().clone());
936        let dyn_def = self
937            .analyze_expr(syntax.node())
938            .iter()
939            .find_map(|(value, _)| {
940                let def = Definition::from_value(value.clone(), def_name)?;
941                None.or_else(|| {
942                    let source = self.source_by_id(def.decl.file_id()?).ok()?;
943                    let node = LinkedNode::new(source.root()).find(def.decl.span())?;
944                    let def_at_the_span = classify_def_loosely(node)?;
945                    self.def_of_span(&source, def_at_the_span.name()?.span())
946                })
947                .or(Some(def))
948            });
949
950        // Uses the dynamic definition or the fallback definition.
951        dyn_def.or(def)
952    }
953
954    pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
955        let source = self.source_by_id(span.id()?).ok()?;
956        let (ti, ty) = self.type_of_span_(&source, span)?;
957        Some(ti.simplify(ty, false))
958    }
959
960    pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
961        let source = self.source_by_id(span.id()?).ok()?;
962        Some(self.type_of_span_(&source, span)?.1)
963    }
964
965    pub(crate) fn type_of_span_(
966        self: &Arc<Self>,
967        source: &Source,
968        span: Span,
969    ) -> Option<(Arc<TypeInfo>, Ty)> {
970        let ti = self.type_check(source);
971        let ty = ti.type_of_span(span)?;
972        Some((ti, ty))
973    }
974
975    pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
976        let id = node.span().id()?;
977        let source = self.source_by_id(id).ok()?;
978        let ty_chk = self.type_check(&source);
979
980        let ty = post_type_check(self.clone(), &ty_chk, node.clone())
981            .or_else(|| ty_chk.type_of_span(node.span()))?;
982        Some(ty_chk.simplify(ty, false))
983    }
984
985    pub(crate) fn sig_of_def(self: &Arc<Self>, def: Definition) -> Option<Signature> {
986        crate::log_debug_ct!("check definition func {def:?}");
987        let source = def.decl.file_id().and_then(|id| self.source_by_id(id).ok());
988        analyze_signature(self, SignatureTarget::Def(source, def))
989    }
990
991    pub(crate) fn sig_of_type(self: &Arc<Self>, ti: &TypeInfo, ty: Ty) -> Option<Signature> {
992        super::sig_of_type(self, ti, ty)
993    }
994
995    pub(crate) fn sig_of_type_or_dyn(
996        self: &Arc<Self>,
997        ti: &TypeInfo,
998        callee_ty: Ty,
999        callee: &SyntaxNode,
1000    ) -> Option<Signature> {
1001        self.sig_of_type(ti, callee_ty).or_else(|| {
1002            self.analyze_expr(callee).iter().find_map(|(value, _)| {
1003                let Value::Func(callee) = value else {
1004                    return None;
1005                };
1006
1007                // Converts with cache
1008                analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
1009            })
1010        })
1011    }
1012
1013    /// Try to find imported target from the current source file.
1014    /// This function will try to resolves target statically.
1015    ///
1016    /// ## Returns
1017    /// The first value is the resolved source.
1018    /// The second value is the resolved scope.
1019    pub fn analyze_import(&self, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
1020        if let Some(v) = source.cast::<ast::Expr>().and_then(Self::const_eval) {
1021            return (Some(v), None);
1022        }
1023        let token = &self.analysis.workers.import;
1024        token.enter(|| analyze_import_(self.world(), source))
1025    }
1026
1027    /// Try to load a module from the current source file.
1028    pub fn analyze_expr(&self, source: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
1029        let token = &self.analysis.workers.expression;
1030        token.enter(|| analyze_expr_(self.world(), source))
1031    }
1032
1033    /// Get bib info of a source file.
1034    pub fn analyze_bib(&self, introspector: &Introspector) -> Option<Arc<BibInfo>> {
1035        let world = self.world();
1036        let world = (world as &dyn World).track();
1037
1038        analyze_bib(world, introspector.track())
1039    }
1040
1041    /// Describe the item under the cursor.
1042    ///
1043    /// Passing a `document` (from a previous compilation) is optional, but
1044    /// enhances the autocompletions. Label completions, for instance, are
1045    /// only generated when the document is available.
1046    pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
1047        let token = &self.analysis.workers.tooltip;
1048        token.enter(|| tooltip_(self.world(), source, cursor))
1049    }
1050
1051    /// Get the manifest of a package by file id.
1052    pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1053        crate::package::get_manifest(self.world(), toml_id)
1054    }
1055
1056    /// Compute the signature of a function.
1057    pub fn compute_signature(
1058        self: &Arc<Self>,
1059        func: SignatureTarget,
1060        compute: impl FnOnce(&Arc<Self>) -> Option<Signature> + Send + Sync + 'static,
1061    ) -> Option<Signature> {
1062        let res = match func {
1063            SignatureTarget::Def(src, def) => self
1064                .analysis
1065                .caches
1066                .def_signatures
1067                .entry(hash128(&(src, def.clone())), self.lifetime),
1068            SignatureTarget::SyntaxFast(source, span) => {
1069                let cache_key = (source, span, true);
1070                self.analysis
1071                    .caches
1072                    .static_signatures
1073                    .entry(hash128(&cache_key), self.lifetime)
1074            }
1075            SignatureTarget::Syntax(source, span) => {
1076                let cache_key = (source, span);
1077                self.analysis
1078                    .caches
1079                    .static_signatures
1080                    .entry(hash128(&cache_key), self.lifetime)
1081            }
1082            SignatureTarget::Convert(rt) => self
1083                .analysis
1084                .caches
1085                .signatures
1086                .entry(hash128(&(&rt, true)), self.lifetime),
1087            SignatureTarget::Runtime(rt) => self
1088                .analysis
1089                .caches
1090                .signatures
1091                .entry(hash128(&rt), self.lifetime),
1092        };
1093        res.get_or_init(|| compute(self)).clone()
1094    }
1095
1096    pub(crate) fn compute_docstring(
1097        self: &Arc<Self>,
1098        fid: TypstFileId,
1099        docs: String,
1100        kind: DefKind,
1101    ) -> Option<Arc<DocString>> {
1102        let res = self
1103            .analysis
1104            .caches
1105            .docstrings
1106            .entry(hash128(&(fid, &docs, kind)), self.lifetime);
1107        res.get_or_init(|| {
1108            crate::syntax::docs::do_compute_docstring(self, fid, docs, kind).map(Arc::new)
1109        })
1110        .clone()
1111    }
1112
1113    /// Remove html tags from markup content if necessary.
1114    pub fn remove_html(&self, markup: EcoString) -> EcoString {
1115        if !self.analysis.remove_html {
1116            return markup;
1117        }
1118
1119        static REMOVE_HTML_COMMENT_REGEX: LazyLock<regex::Regex> =
1120            LazyLock::new(|| regex::Regex::new(r#"<!--[\s\S]*?-->"#).unwrap());
1121        REMOVE_HTML_COMMENT_REGEX
1122            .replace_all(&markup, "")
1123            .trim()
1124            .into()
1125    }
1126
1127    fn query_stat(&self, id: TypstFileId, query: &'static str) -> QueryStatGuard {
1128        self.analysis.stats.stat(Some(id), query)
1129    }
1130
1131    /// Check on a module before really needing them. But we likely use them
1132    /// after a while.
1133    pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1134        // crate::log_debug_ct!("prefetch type check {fid:?}");
1135        // let this = self.clone();
1136        // rayon::spawn(move || {
1137        //     let Some(source) = this.world().source(fid).ok() else {
1138        //         return;
1139        //     };
1140        //     this.type_check(&source);
1141        //     // crate::log_debug_ct!("prefetch type check end {fid:?}");
1142        // });
1143    }
1144
1145    pub(crate) fn preload_package(self: Arc<Self>, entry_point: TypstFileId) {
1146        crate::log_debug_ct!("preload package start {entry_point:?}");
1147
1148        #[derive(Clone)]
1149        struct Preloader {
1150            shared: Arc<SharedContext>,
1151            analyzed: Arc<Mutex<HashSet<TypstFileId>>>,
1152        }
1153
1154        impl Preloader {
1155            fn work(&self, fid: TypstFileId) {
1156                crate::log_debug_ct!("preload package {fid:?}");
1157                let source = self.shared.source_by_id(fid).ok().unwrap();
1158                let exprs = self.shared.expr_stage(&source);
1159                self.shared.type_check(&source);
1160                exprs.imports.iter().for_each(|(fid, _)| {
1161                    if !self.analyzed.lock().insert(*fid) {
1162                        return;
1163                    }
1164                    self.work(*fid);
1165                })
1166            }
1167        }
1168
1169        let preloader = Preloader {
1170            shared: self,
1171            analyzed: Arc::default(),
1172        };
1173
1174        preloader.work(entry_point);
1175    }
1176}
1177
1178// Needed by recursive computation
1179type DeferredCompute<T> = Arc<OnceLock<T>>;
1180
1181#[derive(Clone)]
1182struct IncrCacheMap<K, V> {
1183    revision: usize,
1184    global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1185    prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1186    next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1187}
1188
1189impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
1190    fn default() -> Self {
1191        Self {
1192            revision: 0,
1193            global: Arc::default(),
1194            prev: Arc::default(),
1195            next: Arc::default(),
1196        }
1197    }
1198}
1199
1200impl<K, V> IncrCacheMap<K, V> {
1201    fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1202    where
1203        K: Clone + Eq + Hash,
1204        V: Clone,
1205    {
1206        let next = self.next.lock().entry(key.clone()).or_default().clone();
1207
1208        next.get_or_init(|| {
1209            let prev = self.prev.lock().get(&key).cloned();
1210            let prev = prev.and_then(|prev| prev.get().cloned());
1211            let prev = prev.or_else(|| {
1212                let global = self.global.lock();
1213                global.get(&key).map(|global| global.1.clone())
1214            });
1215
1216            let res = compute(prev);
1217
1218            let global = self.global.lock();
1219            let entry = global.entry(key.clone());
1220            use dashmap::mapref::entry::Entry;
1221            match entry {
1222                Entry::Occupied(mut entry) => {
1223                    let (revision, _) = entry.get();
1224                    if *revision < self.revision {
1225                        entry.insert((self.revision, res.clone()));
1226                    }
1227                }
1228                Entry::Vacant(entry) => {
1229                    entry.insert((self.revision, res.clone()));
1230                }
1231            }
1232
1233            res
1234        })
1235        .clone()
1236    }
1237
1238    fn crawl(&self, revision: usize) -> Self {
1239        Self {
1240            revision,
1241            prev: self.next.clone(),
1242            global: self.global.clone(),
1243            next: Default::default(),
1244        }
1245    }
1246}
1247
1248#[derive(Clone)]
1249struct CacheMap<T> {
1250    m: Arc<FxDashMap<u128, (u64, T)>>,
1251    // pub alloc: AllocStats,
1252}
1253
1254impl<T> Default for CacheMap<T> {
1255    fn default() -> Self {
1256        Self {
1257            m: Default::default(),
1258            // alloc: Default::default(),
1259        }
1260    }
1261}
1262
1263impl<T> CacheMap<T> {
1264    fn clear(&self) {
1265        self.m.clear();
1266    }
1267
1268    fn retain(&self, mut f: impl FnMut(&mut (u64, T)) -> bool) {
1269        self.m.retain(|_k, v| f(v));
1270    }
1271}
1272
1273impl<T: Default + Clone> CacheMap<T> {
1274    fn entry(&self, key: u128, lifetime: u64) -> T {
1275        let entry = self.m.entry(key);
1276        let entry = entry.or_insert_with(|| (lifetime, T::default()));
1277        entry.1.clone()
1278    }
1279}
1280
1281/// Shared workers to limit resource usage
1282#[derive(Default)]
1283pub struct AnalysisGlobalWorkers {
1284    /// A possible long running import dynamic analysis task
1285    import: RateLimiter,
1286    /// A possible long running expression dynamic analysis task
1287    expression: RateLimiter,
1288    /// A possible long running tooltip dynamic analysis task
1289    tooltip: RateLimiter,
1290}
1291
1292/// A global (compiler server spanned) cache for all level of analysis results
1293/// of a module.
1294#[derive(Default, Clone)]
1295pub struct AnalysisGlobalCaches {
1296    lifetime: Arc<AtomicU64>,
1297    clear_lifetime: Arc<AtomicU64>,
1298    def_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1299    static_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1300    signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1301    docstrings: CacheMap<DeferredCompute<Option<Arc<DocString>>>>,
1302    terms: CacheMap<(Value, Ty)>,
1303}
1304
1305/// A local (lsp request spanned) cache for all level of analysis results of a
1306/// module.
1307///
1308/// You should not hold it across requests, because input like source code may
1309/// change.
1310#[derive(Default)]
1311pub struct AnalysisLocalCaches {
1312    modules: HashMap<TypstFileId, ModuleAnalysisLocalCache>,
1313    completion_files: OnceLock<Vec<TypstFileId>>,
1314    root_files: OnceLock<Vec<TypstFileId>>,
1315    module_deps: OnceLock<HashMap<TypstFileId, ModuleDependency>>,
1316}
1317
1318/// A local cache for module-level analysis results of a module.
1319///
1320/// You should not hold it across requests, because input like source code may
1321/// change.
1322#[derive(Default)]
1323pub struct ModuleAnalysisLocalCache {
1324    expr_stage: OnceLock<ExprInfo>,
1325    type_check: OnceLock<Arc<TypeInfo>>,
1326}
1327
1328/// A revision-managed (per input change) cache for all level of analysis
1329/// results of a module.
1330#[derive(Default)]
1331pub struct AnalysisRevCache {
1332    default_slot: AnalysisRevSlot,
1333    manager: RevisionManager<AnalysisRevSlot>,
1334}
1335
1336impl RevisionManagerLike for AnalysisRevCache {
1337    fn gc(&mut self, rev: usize) {
1338        self.manager.gc(rev);
1339
1340        // todo: the following code are time consuming.
1341        {
1342            let mut max_ei = FxHashMap::default();
1343            let es = self.default_slot.expr_stage.global.lock();
1344            for r in es.iter() {
1345                let rev: &mut usize = max_ei.entry(r.1.fid).or_default();
1346                *rev = (*rev).max(r.1.revision);
1347            }
1348            es.retain(|_, r| r.1.revision == *max_ei.get(&r.1.fid).unwrap_or(&0));
1349        }
1350
1351        {
1352            let mut max_ti = FxHashMap::default();
1353            let ts = self.default_slot.type_check.global.lock();
1354            for r in ts.iter() {
1355                let rev: &mut usize = max_ti.entry(r.1.fid).or_default();
1356                *rev = (*rev).max(r.1.revision);
1357            }
1358            ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
1359        }
1360
1361        {
1362            let mut max_li = FxHashMap::default();
1363            let ts = self.default_slot.lint.global.lock();
1364            for r in ts.iter() {
1365                let rev: &mut usize = max_li.entry(r.1.fid).or_default();
1366                *rev = (*rev).max(r.1.revision);
1367            }
1368            ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
1369        }
1370    }
1371}
1372
1373impl AnalysisRevCache {
1374    fn clear(&mut self) {
1375        self.manager.clear();
1376        self.default_slot = Default::default();
1377    }
1378
1379    /// Find the last revision slot by revision number.
1380    fn find_revision(
1381        &mut self,
1382        revision: NonZeroUsize,
1383        lg: &AnalysisRevLock,
1384    ) -> Arc<RevisionSlot<AnalysisRevSlot>> {
1385        lg.inner.access(revision);
1386        self.manager.find_revision(revision, |slot_base| {
1387            log::debug!("analysis revision {} is created", revision.get());
1388            slot_base
1389                .map(|slot| AnalysisRevSlot {
1390                    revision: slot.revision,
1391                    expr_stage: slot.data.expr_stage.crawl(revision.get()),
1392                    type_check: slot.data.type_check.crawl(revision.get()),
1393                    lint: slot.data.lint.crawl(revision.get()),
1394                })
1395                .unwrap_or_else(|| self.default_slot.clone())
1396        })
1397    }
1398}
1399
1400/// A lock for revision.
1401pub struct AnalysisRevLock {
1402    inner: RevisionLock,
1403    tokens: Option<SemanticTokenContext>,
1404    grid: Arc<Mutex<AnalysisRevCache>>,
1405}
1406
1407impl Drop for AnalysisRevLock {
1408    fn drop(&mut self) {
1409        let mut mu = self.grid.lock();
1410        let gc_revision = mu.manager.unlock(&mut self.inner);
1411
1412        if let Some(gc_revision) = gc_revision {
1413            let grid = self.grid.clone();
1414            rayon::spawn(move || {
1415                grid.lock().gc(gc_revision);
1416            });
1417        }
1418    }
1419}
1420
1421#[derive(Default, Clone)]
1422struct AnalysisRevSlot {
1423    revision: usize,
1424    expr_stage: IncrCacheMap<u128, ExprInfo>,
1425    type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
1426    lint: IncrCacheMap<u128, LintInfo>,
1427}
1428
1429impl Drop for AnalysisRevSlot {
1430    fn drop(&mut self) {
1431        log::debug!("analysis revision {} is dropped", self.revision);
1432    }
1433}
1434
1435fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
1436    // while is not char boundary, move cursor to right
1437    while cursor < text.len() && !text.is_char_boundary(cursor) {
1438        cursor += 1;
1439    }
1440
1441    cursor.min(text.len())
1442}
1443
1444#[typst_macros::time]
1445#[comemo::memoize]
1446fn analyze_bib(
1447    world: Tracked<dyn World + '_>,
1448    introspector: Tracked<Introspector>,
1449) -> Option<Arc<BibInfo>> {
1450    let bib_elem = BibliographyElem::find(introspector).ok()?;
1451
1452    // todo: it doesn't respect the style chain which can be get from
1453    // `analyze_expr`
1454    let csl_style = bib_elem.style.get_cloned(StyleChain::default()).derived;
1455
1456    let Value::Array(paths) = bib_elem.sources.clone().into_value() else {
1457        return None;
1458    };
1459    let elem_fid = bib_elem.span().id()?;
1460    let files = paths
1461        .into_iter()
1462        .flat_map(|path| path.cast().ok())
1463        .flat_map(|bib_path: EcoString| {
1464            let bib_fid = resolve_id_by_path(world.deref(), elem_fid, &bib_path)?;
1465            Some((bib_fid, world.file(bib_fid).ok()?))
1466        });
1467
1468    bib_info(csl_style, files)
1469}
1470
1471#[comemo::memoize]
1472fn loc_info(bytes: Bytes) -> Option<EcoVec<(usize, String)>> {
1473    let mut loc = EcoVec::new();
1474    let mut offset = 0;
1475    for line in bytes.split(|byte| *byte == b'\n') {
1476        loc.push((offset, String::from_utf8(line.to_owned()).ok()?));
1477        offset += line.len() + 1;
1478    }
1479    Some(loc)
1480}
1481
1482fn find_loc(
1483    len: usize,
1484    loc: &EcoVec<(usize, String)>,
1485    mut offset: usize,
1486    encoding: PositionEncoding,
1487) -> Option<LspPosition> {
1488    if offset > len {
1489        offset = len;
1490    }
1491
1492    let r = match loc.binary_search_by_key(&offset, |line| line.0) {
1493        Ok(i) => i,
1494        Err(i) => i - 1,
1495    };
1496
1497    let (start, s) = loc.get(r)?;
1498    let byte_offset = offset.saturating_sub(*start);
1499
1500    let column_prefix = if byte_offset <= s.len() {
1501        &s[..byte_offset]
1502    } else {
1503        let line = (r + 1) as u32;
1504        return Some(LspPosition { line, character: 0 });
1505    };
1506
1507    let line = r as u32;
1508    let character = match encoding {
1509        PositionEncoding::Utf8 => column_prefix.chars().count(),
1510        PositionEncoding::Utf16 => column_prefix.chars().map(|ch| ch.len_utf16()).sum(),
1511    } as u32;
1512
1513    Some(LspPosition { line, character })
1514}
1515
1516/// The context for searching in the workspace.
1517pub struct SearchCtx<'a> {
1518    /// The inner analysis context.
1519    pub ctx: &'a mut LocalContext,
1520    /// The set of files that have been searched.
1521    pub searched: HashSet<TypstFileId>,
1522    /// The files that need to be searched.
1523    pub worklist: Vec<TypstFileId>,
1524}
1525
1526impl SearchCtx<'_> {
1527    /// Push a file to the worklist.
1528    pub fn push(&mut self, fid: TypstFileId) -> bool {
1529        if self.searched.insert(fid) {
1530            self.worklist.push(fid);
1531            true
1532        } else {
1533            false
1534        }
1535    }
1536
1537    /// Push the dependents of a file to the worklist.
1538    pub fn push_dependents(&mut self, fid: TypstFileId) {
1539        let deps = self.ctx.module_dependencies().get(&fid);
1540        let dependents = deps.map(|dep| dep.dependents.clone()).into_iter().flatten();
1541        for dep in dependents {
1542            self.push(dep);
1543        }
1544    }
1545}
1546
1547/// A rate limiter on some (cpu-heavy) action
1548#[derive(Default)]
1549pub struct RateLimiter {
1550    token: std::sync::Mutex<()>,
1551}
1552
1553impl RateLimiter {
1554    /// Executes some (cpu-heavy) action with rate limit
1555    #[must_use]
1556    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1557        let _c = self.token.lock().unwrap();
1558        f()
1559    }
1560}