tinymist_project/
lsp.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
use std::path::Path;
use std::{borrow::Cow, sync::Arc};

use tinymist_std::error::prelude::*;
use tinymist_std::{bail, ImmutPath};
use tinymist_task::ExportTarget;
use tinymist_world::config::CompileFontOpts;
use tinymist_world::font::system::SystemFontSearcher;
use tinymist_world::package::{registry::HttpRegistry, RegistryPathMapper};
use tinymist_world::vfs::{system::SystemAccessModel, Vfs};
use tinymist_world::{args::*, WorldComputeGraph};
use tinymist_world::{
    CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState,
};
use typst::foundations::{Dict, Str, Value};
use typst::utils::LazyHash;
use typst::Features;

use crate::ProjectInput;

use crate::world::font::FontResolverImpl;
use crate::{CompiledArtifact, Interrupt};

/// Compiler feature for LSP universe and worlds without typst.ts to implement
/// more for tinymist. type trait of [`CompilerUniverse`].
#[derive(Debug, Clone, Copy)]
pub struct LspCompilerFeat;

impl CompilerFeat for LspCompilerFeat {
    /// Uses [`FontResolverImpl`] directly.
    type FontResolver = FontResolverImpl;
    /// It accesses a physical file system.
    type AccessModel = SystemAccessModel;
    /// It performs native HTTP requests for fetching package data.
    type Registry = HttpRegistry;
}

/// LSP universe that spawns LSP worlds.
pub type LspUniverse = CompilerUniverse<LspCompilerFeat>;
/// LSP world that holds compilation resources
pub type LspWorld = CompilerWorld<LspCompilerFeat>;
/// LSP compile snapshot.
pub type LspCompileSnapshot = CompileSnapshot<LspCompilerFeat>;
/// LSP compiled artifact.
pub type LspCompiledArtifact = CompiledArtifact<LspCompilerFeat>;
/// LSP compute graph.
pub type LspComputeGraph = Arc<WorldComputeGraph<LspCompilerFeat>>;
/// LSP interrupt.
pub type LspInterrupt = Interrupt<LspCompilerFeat>;
/// Immutable prehashed reference to dictionary.
pub type ImmutDict = Arc<LazyHash<Dict>>;

/// World provider for LSP universe and worlds.
pub trait WorldProvider {
    /// Get the entry options from the arguments.
    fn entry(&self) -> Result<EntryOpts>;
    /// Get a universe instance from the given arguments.
    fn resolve(&self) -> Result<LspUniverse>;
}

impl WorldProvider for CompileOnceArgs {
    fn resolve(&self) -> Result<LspUniverse> {
        let entry = self.entry()?.try_into()?;
        let inputs = self.resolve_inputs().unwrap_or_default();
        let fonts = Arc::new(LspUniverseBuilder::resolve_fonts(self.font.clone())?);
        let packages = LspUniverseBuilder::resolve_package(
            self.cert.as_deref().map(From::from),
            Some(&self.package),
        );

        // todo: more export targets
        Ok(LspUniverseBuilder::build(
            entry,
            ExportTarget::Paged,
            self.resolve_features(),
            inputs,
            packages,
            fonts,
        ))
    }

    fn entry(&self) -> Result<EntryOpts> {
        let mut cwd = None;
        let mut cwd = move || {
            cwd.get_or_insert_with(|| {
                std::env::current_dir().context("failed to get current directory")
            })
            .clone()
        };

        let main = {
            let input = self.input.as_ref().context("entry file must be provided")?;
            let input = Path::new(&input);
            if input.is_absolute() {
                input.to_owned()
            } else {
                cwd()?.join(input)
            }
        };

        let root = if let Some(root) = &self.root {
            if root.is_absolute() {
                root.clone()
            } else {
                cwd()?.join(root)
            }
        } else {
            main.parent()
                .context("entry file don't have a valid parent as root")?
                .to_owned()
        };

        let relative_main = match main.strip_prefix(&root) {
            Ok(relative_main) => relative_main,
            Err(_) => {
                log::error!("entry file must be inside the root, file: {main:?}, root: {root:?}");
                bail!("entry file must be inside the root, file: {main:?}, root: {root:?}");
            }
        };

        Ok(EntryOpts::new_rooted(
            root.clone(),
            Some(relative_main.to_owned()),
        ))
    }
}

// todo: merge me with the above impl
impl WorldProvider for (ProjectInput, ImmutPath) {
    fn resolve(&self) -> Result<LspUniverse> {
        let (proj, lock_dir) = self;
        let entry = self.entry()?.try_into()?;
        let inputs = proj
            .inputs
            .iter()
            .map(|(k, v)| (Str::from(k.as_str()), Value::Str(Str::from(v.as_str()))))
            .collect();
        let fonts = LspUniverseBuilder::resolve_fonts(CompileFontArgs {
            font_paths: {
                proj.font_paths
                    .iter()
                    .flat_map(|p| p.to_abs_path(lock_dir))
                    .collect::<Vec<_>>()
            },
            ignore_system_fonts: !proj.system_fonts,
        })?;
        let packages = LspUniverseBuilder::resolve_package(
            // todo: recover certificate path
            None,
            Some(&CompilePackageArgs {
                package_path: proj
                    .package_path
                    .as_ref()
                    .and_then(|p| p.to_abs_path(lock_dir)),
                package_cache_path: proj
                    .package_cache_path
                    .as_ref()
                    .and_then(|p| p.to_abs_path(lock_dir)),
            }),
        );

        // todo: more export targets
        Ok(LspUniverseBuilder::build(
            entry,
            ExportTarget::Paged,
            // todo: features
            Features::default(),
            Arc::new(LazyHash::new(inputs)),
            packages,
            Arc::new(fonts),
        ))
    }

    fn entry(&self) -> Result<EntryOpts> {
        let (proj, lock_dir) = self;

        let entry = proj
            .main
            .to_abs_path(lock_dir)
            .context("failed to resolve entry file")?;

        let root = if let Some(root) = &proj.root {
            root.to_abs_path(lock_dir)
                .context("failed to resolve root")?
        } else {
            lock_dir.as_ref().to_owned()
        };

        if !entry.starts_with(&root) {
            bail!("entry file must be in the root directory, {entry:?}, {root:?}");
        }

        let relative_entry = match entry.strip_prefix(&root) {
            Ok(relative_entry) => relative_entry,
            Err(_) => bail!("entry path must be inside the root: {}", entry.display()),
        };

        Ok(EntryOpts::new_rooted(
            root.clone(),
            Some(relative_entry.to_owned()),
        ))
    }
}

/// Builder for LSP universe.
pub struct LspUniverseBuilder;

impl LspUniverseBuilder {
    /// Create [`LspUniverse`] with the given options.
    /// See [`LspCompilerFeat`] for instantiation details.
    pub fn build(
        entry: EntryState,
        export_target: ExportTarget,
        features: Features,
        inputs: ImmutDict,
        package_registry: HttpRegistry,
        font_resolver: Arc<FontResolverImpl>,
    ) -> LspUniverse {
        let package_registry = Arc::new(package_registry);
        let resolver = Arc::new(RegistryPathMapper::new(package_registry.clone()));

        // todo: typst doesn't allow to merge features
        let features = if matches!(export_target, ExportTarget::Html) {
            Features::from_iter([typst::Feature::Html])
        } else {
            features
        };

        LspUniverse::new_raw(
            entry,
            features,
            Some(inputs),
            Vfs::new(resolver, SystemAccessModel {}),
            package_registry,
            font_resolver,
        )
    }

    /// Resolve fonts from given options.
    pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
        let mut searcher = SystemFontSearcher::new();
        searcher.resolve_opts(CompileFontOpts {
            font_paths: vec![],
            no_system_fonts: true,
            with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
        })?;
        Ok(searcher.build())
    }

    /// Resolve fonts from given options.
    pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
        let mut searcher = SystemFontSearcher::new();
        searcher.resolve_opts(CompileFontOpts {
            font_paths: args.font_paths,
            no_system_fonts: args.ignore_system_fonts,
            with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
        })?;
        Ok(searcher.build())
    }

    /// Resolve package registry from given options.
    pub fn resolve_package(
        cert_path: Option<ImmutPath>,
        args: Option<&CompilePackageArgs>,
    ) -> HttpRegistry {
        HttpRegistry::new(
            cert_path,
            args.and_then(|args| Some(args.package_path.clone()?.into())),
            args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
        )
    }
}