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