mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: allow running server with loading font once and on rootless files (#94)
* dev: change system config * docs: update config doc * dev: clean up tightly coupled world * dev: load font once * docs: add more comments to tinymist-query * dev: merge compiler layers * feat: allow run lsp on rootless files * build: bump ts * fix: workspace dep * build: bump preview * dev: correctly check inactive state * fix: weird cargo default features
This commit is contained in:
parent
f0a9c3e880
commit
76b4e91046
46 changed files with 974 additions and 638 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -2702,9 +2702,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reflexo"
|
name = "reflexo"
|
||||||
version = "0.4.2-rc9"
|
version = "0.5.0-rc2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3bf3d6c80be65ec408823031e560758c1de427601d61a82fdea0d0ecb7794f4"
|
checksum = "247ea8050cb5c88b41a68b3269f5a2eb7ebff55851a564d96b035643418346e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
@ -4033,9 +4033,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-preview"
|
name = "typst-preview"
|
||||||
version = "0.11.1"
|
version = "0.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b8eebe95602e6723ce6e3c06e4c32000ab1bde2d0e775248b74ee2373aeff87"
|
checksum = "fc1323cc2de067d19919891a1ee3f43af5c96d91decce44044347137d3e06fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"await-tree",
|
"await-tree",
|
||||||
|
@ -4095,9 +4095,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-ts-compiler"
|
name = "typst-ts-compiler"
|
||||||
version = "0.4.2-rc9"
|
version = "0.5.0-rc2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "478dbcd334466a939420fa9e9429e0ecaa8ec8c5a943e74862b2b46b364e2938"
|
checksum = "c18cf7d96c0c558901b3f7e3f5200ecb7e3d7d3dcc5a1222e94bc875237ff352"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"append-only-vec",
|
"append-only-vec",
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
|
@ -4134,9 +4134,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-ts-core"
|
name = "typst-ts-core"
|
||||||
version = "0.4.2-rc9"
|
version = "0.5.0-rc2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d109fb49a9ffa1d9f7126b317313d29257ec0d34ae7a34c65d16ed1fea2b448"
|
checksum = "a69135c380eb60efa4aeabd986d27d82ecd1b4c843fd3393992b449409317847"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
"base64-serde",
|
"base64-serde",
|
||||||
|
@ -4172,9 +4172,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-ts-svg-exporter"
|
name = "typst-ts-svg-exporter"
|
||||||
version = "0.4.2-rc9"
|
version = "0.5.0-rc2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f909564a69478f3e244ddc4380dc337f74edfa5d4ce7551ddfedc6f5d32f48b6"
|
checksum = "b6063f63c8e3ba3d4d7f4cb1a8fd96b096e8e713f24783278fea98dac0746966"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
"comemo",
|
"comemo",
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -44,12 +44,12 @@ typst = "0.11.0"
|
||||||
typst-ide = "0.11.0"
|
typst-ide = "0.11.0"
|
||||||
typst-pdf = "0.11.0"
|
typst-pdf = "0.11.0"
|
||||||
typst-assets = "0.11.0"
|
typst-assets = "0.11.0"
|
||||||
reflexo = { version = "0.4.2-rc9", default-features = false, features = [
|
reflexo = { version = "0.5.0-rc2", default-features = false, features = [
|
||||||
"flat-vector",
|
"flat-vector",
|
||||||
] }
|
] }
|
||||||
typst-ts-core = { version = "0.4.2-rc9" }
|
typst-ts-core = { version = "0.5.0-rc2", default-features = false }
|
||||||
typst-ts-compiler = { version = "0.4.2-rc9" }
|
typst-ts-compiler = { version = "0.5.0-rc2" }
|
||||||
typst-preview = { version = "0.11.0" }
|
typst-preview = { version = "0.11.3" }
|
||||||
|
|
||||||
lsp-server = "0.7.6"
|
lsp-server = "0.7.6"
|
||||||
lsp-types = { version = "=0.95.0", features = ["proposed"] }
|
lsp-types = { version = "=0.95.0", features = ["proposed"] }
|
||||||
|
@ -108,7 +108,7 @@ undocumented_unsafe_blocks = "warn"
|
||||||
typst = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
typst = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
||||||
typst-ide = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
typst-ide = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
||||||
typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
||||||
typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
# typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
|
||||||
|
|
||||||
# typst = { path = "../typst/crates/typst" }
|
# typst = { path = "../typst/crates/typst" }
|
||||||
# typst-ide = { path = "../typst/crates/typst-ide" }
|
# typst-ide = { path = "../typst/crates/typst-ide" }
|
||||||
|
|
|
@ -34,12 +34,6 @@ typst.workspace = true
|
||||||
typst-ide.workspace = true
|
typst-ide.workspace = true
|
||||||
|
|
||||||
reflexo.workspace = true
|
reflexo.workspace = true
|
||||||
typst-ts-compiler.workspace = true
|
|
||||||
typst-ts-core = { version = "0.4.2-rc8", default-features = false, features = [
|
|
||||||
"flat-vector",
|
|
||||||
"vector-bbox",
|
|
||||||
"no-content-hint",
|
|
||||||
] }
|
|
||||||
|
|
||||||
lsp-types.workspace = true
|
lsp-types.workspace = true
|
||||||
if_chain = "1"
|
if_chain = "1"
|
||||||
|
@ -49,8 +43,14 @@ once_cell.workspace = true
|
||||||
insta.workspace = true
|
insta.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
typst-ts-core = { workspace = true, default-features = false, features = [
|
||||||
|
"flat-vector",
|
||||||
|
"vector-bbox",
|
||||||
|
"no-content-hint",
|
||||||
|
] }
|
||||||
|
typst-ts-compiler.workspace = true
|
||||||
sha2 = { version = "0.10" }
|
sha2 = { version = "0.10" }
|
||||||
hex = { version = "0.4" }
|
hex = { version = "0.4" }
|
||||||
|
|
||||||
# [lints]
|
[lints]
|
||||||
# workspace = true
|
workspace = true
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Semantic static and dynamic analysis of the source code.
|
||||||
|
|
||||||
pub mod def_use;
|
pub mod def_use;
|
||||||
pub use def_use::*;
|
pub use def_use::*;
|
||||||
pub mod track_values;
|
pub mod track_values;
|
||||||
|
|
|
@ -252,7 +252,7 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
ModSrc::Expr(_) => {}
|
ModSrc::Expr(_) => {}
|
||||||
ModSrc::Path(p) => {
|
ModSrc::Path(p) => {
|
||||||
let src = find_source_by_import_path(
|
let src = find_source_by_import_path(
|
||||||
self.ctx.ctx.world,
|
self.ctx.ctx.world(),
|
||||||
self.current_id,
|
self.current_id,
|
||||||
p.deref(),
|
p.deref(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,15 +8,10 @@ use once_cell::sync::OnceCell;
|
||||||
use reflexo::{cow_mut::CowMut, ImmutPath};
|
use reflexo::{cow_mut::CowMut, ImmutPath};
|
||||||
use typst::syntax::FileId as TypstFileId;
|
use typst::syntax::FileId as TypstFileId;
|
||||||
use typst::{
|
use typst::{
|
||||||
diag::{eco_format, FileError, FileResult},
|
diag::{eco_format, FileError, FileResult, PackageError},
|
||||||
syntax::{Source, VirtualPath},
|
syntax::{package::PackageSpec, Source, VirtualPath},
|
||||||
World,
|
World,
|
||||||
};
|
};
|
||||||
use typst_ts_compiler::package::Registry;
|
|
||||||
use typst_ts_compiler::TypstSystemWorld;
|
|
||||||
// use typst_ts_compiler::TypstSystemWorld;
|
|
||||||
// use typst_ts_compiler::{service::WorkspaceProvider, TypstSystemWorld};
|
|
||||||
// use typst_ts_core::{cow_mut::CowMut, ImmutPath};
|
|
||||||
|
|
||||||
use super::{get_def_use_inner, DefUseInfo};
|
use super::{get_def_use_inner, DefUseInfo};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -37,7 +32,7 @@ impl ModuleAnalysisCache {
|
||||||
/// Get the source of a file.
|
/// Get the source of a file.
|
||||||
pub fn source(&self, ctx: &AnalysisContext, file_id: TypstFileId) -> FileResult<Source> {
|
pub fn source(&self, ctx: &AnalysisContext, file_id: TypstFileId) -> FileResult<Source> {
|
||||||
self.source
|
self.source
|
||||||
.get_or_init(|| ctx.world.source(file_id))
|
.get_or_init(|| ctx.world().source(file_id))
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +68,22 @@ pub struct AnalysisCaches {
|
||||||
module_deps: OnceCell<HashMap<TypstFileId, ModuleDependency>>,
|
module_deps: OnceCell<HashMap<TypstFileId, ModuleDependency>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The resources for analysis.
|
||||||
|
pub trait AnaylsisResources {
|
||||||
|
/// Get the world surface for Typst compiler.
|
||||||
|
fn world(&self) -> &dyn World;
|
||||||
|
|
||||||
|
/// Resolve the real path for a package spec.
|
||||||
|
fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError>;
|
||||||
|
|
||||||
|
/// Get all the files in the workspace.
|
||||||
|
fn iter_dependencies(&self, f: &mut dyn FnMut(&ImmutPath, std::time::SystemTime));
|
||||||
|
}
|
||||||
|
|
||||||
/// The context for analyzers.
|
/// The context for analyzers.
|
||||||
pub struct AnalysisContext<'a> {
|
pub struct AnalysisContext<'a> {
|
||||||
/// The world surface for Typst compiler
|
/// The world surface for Typst compiler
|
||||||
pub world: &'a TypstSystemWorld,
|
pub resources: &'a dyn AnaylsisResources,
|
||||||
/// The analysis data
|
/// The analysis data
|
||||||
pub analysis: CowMut<'a, Analysis>,
|
pub analysis: CowMut<'a, Analysis>,
|
||||||
caches: AnalysisCaches,
|
caches: AnalysisCaches,
|
||||||
|
@ -84,14 +91,19 @@ pub struct AnalysisContext<'a> {
|
||||||
|
|
||||||
impl<'w> AnalysisContext<'w> {
|
impl<'w> AnalysisContext<'w> {
|
||||||
/// Create a new analysis context.
|
/// Create a new analysis context.
|
||||||
pub fn new(world: &'w TypstSystemWorld, a: Analysis) -> Self {
|
pub fn new(world: &'w dyn AnaylsisResources, a: Analysis) -> Self {
|
||||||
Self {
|
Self {
|
||||||
world,
|
resources: world,
|
||||||
analysis: CowMut::Owned(a),
|
analysis: CowMut::Owned(a),
|
||||||
caches: AnalysisCaches::default(),
|
caches: AnalysisCaches::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the world surface for Typst compiler.
|
||||||
|
pub fn world(&self) -> &dyn World {
|
||||||
|
self.resources.world()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) -> &Vec<TypstFileId> {
|
pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) -> &Vec<TypstFileId> {
|
||||||
self.caches.root_files.get_or_init(f)
|
self.caches.root_files.get_or_init(f)
|
||||||
|
@ -125,7 +137,7 @@ impl<'w> AnalysisContext<'w> {
|
||||||
// Determine the root path relative to which the file path
|
// Determine the root path relative to which the file path
|
||||||
// will be resolved.
|
// will be resolved.
|
||||||
let root = match id.package() {
|
let root = match id.package() {
|
||||||
Some(spec) => self.world.registry.resolve(spec)?,
|
Some(spec) => self.resources.resolve(spec)?,
|
||||||
None => self.analysis.root.clone(),
|
None => self.analysis.root.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,24 +194,29 @@ impl<'w> AnalysisContext<'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the position encoding during session.
|
||||||
|
pub(crate) fn position_encoding(&self) -> PositionEncoding {
|
||||||
|
self.analysis.position_encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a LSP position to a Typst position.
|
||||||
pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
|
pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
|
||||||
lsp_to_typst::position(position, self.analysis.position_encoding, src)
|
lsp_to_typst::position(position, self.analysis.position_encoding, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<TypstRange> {
|
/// Convert a Typst offset to a LSP position.
|
||||||
lsp_to_typst::range(position, self.analysis.position_encoding, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
|
pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
|
||||||
typst_to_lsp::offset_to_position(typst_offset, self.analysis.position_encoding, src)
|
typst_to_lsp::offset_to_position(typst_offset, self.analysis.position_encoding, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_lsp_range(&self, position: TypstRange, src: &Source) -> LspRange {
|
/// Convert a LSP range to a Typst range.
|
||||||
typst_to_lsp::range(position, src, self.analysis.position_encoding)
|
pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<TypstRange> {
|
||||||
|
lsp_to_typst::range(position, self.analysis.position_encoding, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn position_encoding(&self) -> PositionEncoding {
|
/// Convert a Typst range to a LSP range.
|
||||||
self.analysis.position_encoding
|
pub fn to_lsp_range(&self, position: TypstRange, src: &Source) -> LspRange {
|
||||||
|
typst_to_lsp::range(position, src, self.analysis.position_encoding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use lsp_types::Command;
|
use lsp_types::Command;
|
||||||
|
|
||||||
use crate::{prelude::*, SyntaxRequest};
|
use crate::{prelude::*, SemanticRequest};
|
||||||
|
|
||||||
/// The [`textDocument/codeLens`] request is sent from the client to the server
|
/// The [`textDocument/codeLens`] request is sent from the client to the server
|
||||||
/// to compute code lenses for a given text document.
|
/// to compute code lenses for a given text document.
|
||||||
|
@ -12,7 +12,7 @@ pub struct CodeLensRequest {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for CodeLensRequest {
|
impl SemanticRequest for CodeLensRequest {
|
||||||
type Response = Vec<CodeLens>;
|
type Response = Vec<CodeLens>;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
|
|
@ -1,9 +1,32 @@
|
||||||
use crate::{prelude::*, StatefulRequest};
|
use crate::{prelude::*, StatefulRequest};
|
||||||
|
|
||||||
|
/// The [`textDocument/completion`] request is sent from the client to the
|
||||||
|
/// server to compute completion items at a given cursor position.
|
||||||
|
///
|
||||||
|
/// If computing full completion items is expensive, servers can additionally
|
||||||
|
/// provide a handler for the completion item resolve request
|
||||||
|
/// (`completionItem/resolve`). This request is sent when a completion item is
|
||||||
|
/// selected in the user interface.
|
||||||
|
///
|
||||||
|
/// [`textDocument/completion`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// Since 3.16.0, the client can signal that it can resolve more properties
|
||||||
|
/// lazily. This is done using the `completion_item.resolve_support` client
|
||||||
|
/// capability which lists all properties that can be filled in during a
|
||||||
|
/// `completionItem/resolve` request.
|
||||||
|
///
|
||||||
|
/// All other properties (usually `sort_text`, `filter_text`, `insert_text`, and
|
||||||
|
/// `text_edit`) must be provided in the `textDocument/completion` response and
|
||||||
|
/// must not be changed during resolve.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CompletionRequest {
|
pub struct CompletionRequest {
|
||||||
|
/// The path of the document to compute completions.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The position in the document at which to compute completions.
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
|
/// Whether the completion is triggered explicitly.
|
||||||
pub explicit: bool,
|
pub explicit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +58,7 @@ impl StatefulRequest for CompletionRequest {
|
||||||
let explicit = false;
|
let explicit = false;
|
||||||
|
|
||||||
let (offset, completions) =
|
let (offset, completions) =
|
||||||
typst_ide::autocomplete(ctx.world, doc, &source, cursor, explicit)?;
|
typst_ide::autocomplete(ctx.world(), doc, &source, cursor, explicit)?;
|
||||||
|
|
||||||
let lsp_start_position = ctx.to_lsp_pos(offset, &source);
|
let lsp_start_position = ctx.to_lsp_pos(offset, &source);
|
||||||
let replace_range = LspRange::new(lsp_start_position, self.position);
|
let replace_range = LspRange::new(lsp_start_position, self.position);
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub type DiagnosticsMap = HashMap<Url, Vec<LspDiagnostic>>;
|
||||||
|
|
||||||
/// Converts a list of Typst diagnostics to LSP diagnostics.
|
/// Converts a list of Typst diagnostics to LSP diagnostics.
|
||||||
pub fn convert_diagnostics<'a>(
|
pub fn convert_diagnostics<'a>(
|
||||||
project: &TypstSystemWorld,
|
project: &AnalysisContext,
|
||||||
errors: impl IntoIterator<Item = &'a TypstDiagnostic>,
|
errors: impl IntoIterator<Item = &'a TypstDiagnostic>,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> DiagnosticsMap {
|
) -> DiagnosticsMap {
|
||||||
|
@ -23,7 +23,7 @@ pub fn convert_diagnostics<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_diagnostic(
|
fn convert_diagnostic(
|
||||||
project: &TypstSystemWorld,
|
project: &AnalysisContext,
|
||||||
typst_diagnostic: &TypstDiagnostic,
|
typst_diagnostic: &TypstDiagnostic,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> anyhow::Result<(Url, LspDiagnostic)> {
|
) -> anyhow::Result<(Url, LspDiagnostic)> {
|
||||||
|
@ -31,10 +31,10 @@ fn convert_diagnostic(
|
||||||
let lsp_range;
|
let lsp_range;
|
||||||
if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) {
|
if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) {
|
||||||
uri = Url::from_file_path(project.path_for_id(id)?).unwrap();
|
uri = Url::from_file_path(project.path_for_id(id)?).unwrap();
|
||||||
let source = project.source(id)?;
|
let source = project.world().source(id)?;
|
||||||
lsp_range = diagnostic_range(&source, span, position_encoding);
|
lsp_range = diagnostic_range(&source, span, position_encoding);
|
||||||
} else {
|
} else {
|
||||||
uri = Url::from_file_path(project.root.clone()).unwrap();
|
uri = Url::from_file_path(project.analysis.root.clone()).unwrap();
|
||||||
lsp_range = LspRange::default();
|
lsp_range = LspRange::default();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,13 +59,13 @@ fn convert_diagnostic(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tracepoint_to_relatedinformation(
|
fn tracepoint_to_relatedinformation(
|
||||||
project: &TypstSystemWorld,
|
project: &AnalysisContext,
|
||||||
tracepoint: &Spanned<Tracepoint>,
|
tracepoint: &Spanned<Tracepoint>,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> anyhow::Result<Option<DiagnosticRelatedInformation>> {
|
) -> anyhow::Result<Option<DiagnosticRelatedInformation>> {
|
||||||
if let Some(id) = tracepoint.span.id() {
|
if let Some(id) = tracepoint.span.id() {
|
||||||
let uri = Url::from_file_path(project.path_for_id(id)?).unwrap();
|
let uri = Url::from_file_path(project.path_for_id(id)?).unwrap();
|
||||||
let source = project.source(id)?;
|
let source = project.world().source(id)?;
|
||||||
|
|
||||||
if let Some(typst_range) = source.range(tracepoint.span) {
|
if let Some(typst_range) = source.range(tracepoint.span) {
|
||||||
let lsp_range = typst_to_lsp::range(typst_range, &source, position_encoding);
|
let lsp_range = typst_to_lsp::range(typst_range, &source, position_encoding);
|
||||||
|
@ -84,7 +84,7 @@ fn tracepoint_to_relatedinformation(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diagnostic_related_information(
|
fn diagnostic_related_information(
|
||||||
project: &TypstSystemWorld,
|
project: &AnalysisContext,
|
||||||
typst_diagnostic: &TypstDiagnostic,
|
typst_diagnostic: &TypstDiagnostic,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> anyhow::Result<Vec<DiagnosticRelatedInformation>> {
|
) -> anyhow::Result<Vec<DiagnosticRelatedInformation>> {
|
||||||
|
|
|
@ -1,22 +1,38 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||||
|
SyntaxRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The [`textDocument/documentSymbol`] request is sent from the client to the
|
||||||
|
/// server to retrieve all symbols found in a given text document.
|
||||||
|
///
|
||||||
|
/// [`textDocument/documentSymbol`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
|
||||||
|
///
|
||||||
|
/// The returned result is either:
|
||||||
|
///
|
||||||
|
/// * [`DocumentSymbolResponse::Flat`] which is a flat list of all symbols found
|
||||||
|
/// in a given text document. Then neither the symbol’s location range nor the
|
||||||
|
/// symbol’s container name should be used to infer a hierarchy.
|
||||||
|
/// * [`DocumentSymbolResponse::Nested`] which is a hierarchy of symbols found
|
||||||
|
/// in a given text document.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DocumentSymbolRequest {
|
pub struct DocumentSymbolRequest {
|
||||||
|
/// The path of the document to retrieve symbols from.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentSymbolRequest {
|
impl SyntaxRequest for DocumentSymbolRequest {
|
||||||
pub fn request(
|
type Response = DocumentSymbolResponse;
|
||||||
|
|
||||||
|
fn request(
|
||||||
self,
|
self,
|
||||||
source: Source,
|
source: &Source,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<DocumentSymbolResponse> {
|
) -> Option<Self::Response> {
|
||||||
let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol)?;
|
let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol)?;
|
||||||
|
|
||||||
let symbols = filter_document_symbols(&symbols, &source, position_encoding);
|
let symbols = filter_document_symbols(&symbols, source, position_encoding);
|
||||||
Some(DocumentSymbolResponse::Nested(symbols))
|
Some(DocumentSymbolResponse::Nested(symbols))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +78,7 @@ mod tests {
|
||||||
|
|
||||||
let source = ctx.source_by_path(&path).unwrap();
|
let source = ctx.source_by_path(&path).unwrap();
|
||||||
|
|
||||||
let result = request.request(source, PositionEncoding::Utf16);
|
let result = request.request(&source, PositionEncoding::Utf16);
|
||||||
assert_snapshot!(JsonRepr::new_redacted(result.unwrap(), &REDACT_LOC));
|
assert_snapshot!(JsonRepr::new_redacted(result.unwrap(), &REDACT_LOC));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,46 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalKind, LexicalScopeKind},
|
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalKind, LexicalScopeKind},
|
||||||
|
SyntaxRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The [`textDocument/foldingRange`] request is sent from the client to the
|
||||||
|
/// server to return all folding ranges found in a given text document.
|
||||||
|
///
|
||||||
|
/// [`textDocument/foldingRange`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.10.0.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FoldingRangeRequest {
|
pub struct FoldingRangeRequest {
|
||||||
|
/// The path of the document to get folding ranges for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// If set, the client can only provide folding ranges that consist of whole
|
||||||
|
/// lines.
|
||||||
pub line_folding_only: bool,
|
pub line_folding_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoldingRangeRequest {
|
impl SyntaxRequest for FoldingRangeRequest {
|
||||||
pub fn request(
|
type Response = Vec<FoldingRange>;
|
||||||
|
|
||||||
|
fn request(
|
||||||
self,
|
self,
|
||||||
source: Source,
|
source: &Source,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<Vec<FoldingRange>> {
|
) -> Option<Self::Response> {
|
||||||
let line_folding_only = self.line_folding_only;
|
let line_folding_only = self.line_folding_only;
|
||||||
|
|
||||||
let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Braced)?;
|
let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Braced)?;
|
||||||
|
|
||||||
let mut results = vec![];
|
let mut results = vec![];
|
||||||
let LspPosition { line, character } =
|
let LspPosition { line, character } =
|
||||||
typst_to_lsp::offset_to_position(source.text().len(), position_encoding, &source);
|
typst_to_lsp::offset_to_position(source.text().len(), position_encoding, source);
|
||||||
let loc = (line, Some(character));
|
let loc = (line, Some(character));
|
||||||
|
|
||||||
calc_folding_range(
|
calc_folding_range(
|
||||||
&symbols,
|
&symbols,
|
||||||
&source,
|
source,
|
||||||
position_encoding,
|
position_encoding,
|
||||||
line_folding_only,
|
line_folding_only,
|
||||||
loc,
|
loc,
|
||||||
|
@ -126,7 +140,7 @@ mod tests {
|
||||||
|
|
||||||
let source = world.source_by_path(&path).unwrap();
|
let source = world.source_by_path(&path).unwrap();
|
||||||
|
|
||||||
let result = request.request(source, PositionEncoding::Utf16);
|
let result = request.request(&source, PositionEncoding::Utf16);
|
||||||
assert_snapshot!(JsonRepr::new_pure(result.unwrap()));
|
assert_snapshot!(JsonRepr::new_pure(result.unwrap()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,35 @@ use lsp_types::LocationLink;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
syntax::{get_deref_target, DerefTarget},
|
syntax::{get_deref_target, DerefTarget},
|
||||||
SyntaxRequest,
|
SemanticRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The [`textDocument/declaration`] request asks the server for the declaration
|
||||||
|
/// location of a symbol at a given text document position.
|
||||||
|
///
|
||||||
|
/// [`textDocument/declaration`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.14.0.
|
||||||
|
///
|
||||||
|
/// The [`GotoDeclarationResponse::Link`](lsp_types::GotoDefinitionResponse::Link) return value
|
||||||
|
/// was introduced in specification version 3.14.0 and requires client-side
|
||||||
|
/// support in order to be used. It can be returned if the client set the
|
||||||
|
/// following field to `true` in the [`initialize`](Self::initialize) method:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// InitializeParams::capabilities::text_document::declaration::link_support
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GotoDeclarationRequest {
|
pub struct GotoDeclarationRequest {
|
||||||
|
/// The path of the document to get the declaration location for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The position of the symbol to get the declaration location for.
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for GotoDeclarationRequest {
|
impl SemanticRequest for GotoDeclarationRequest {
|
||||||
type Response = GotoDeclarationResponse;
|
type Response = GotoDeclarationResponse;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
find_source_by_import, get_deref_target, DerefTarget, IdentRef, LexicalKind,
|
find_source_by_import, get_deref_target, DerefTarget, IdentRef, LexicalKind,
|
||||||
LexicalModKind, LexicalVarKind,
|
LexicalModKind, LexicalVarKind,
|
||||||
},
|
},
|
||||||
SyntaxRequest,
|
SemanticRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The [`textDocument/definition`] request asks the server for the definition
|
/// The [`textDocument/definition`] request asks the server for the definition
|
||||||
|
@ -36,7 +36,7 @@ pub struct GotoDefinitionRequest {
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for GotoDefinitionRequest {
|
impl SemanticRequest for GotoDefinitionRequest {
|
||||||
type Response = GotoDefinitionResponse;
|
type Response = GotoDefinitionResponse;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
@ -96,7 +96,7 @@ pub(crate) fn find_definition(
|
||||||
let parent = path.parent()?;
|
let parent = path.parent()?;
|
||||||
let def_fid = parent.span().id()?;
|
let def_fid = parent.span().id()?;
|
||||||
let e = parent.cast::<ast::ModuleImport>()?;
|
let e = parent.cast::<ast::ModuleImport>()?;
|
||||||
let source = find_source_by_import(ctx.world, def_fid, e)?;
|
let source = find_source_by_import(ctx.world(), def_fid, e)?;
|
||||||
return Some(DefinitionLink {
|
return Some(DefinitionLink {
|
||||||
kind: LexicalKind::Mod(LexicalModKind::PathVar),
|
kind: LexicalKind::Mod(LexicalModKind::PathVar),
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
|
@ -132,7 +132,7 @@ pub(crate) fn find_definition(
|
||||||
let def_id = def_id.or_else(|| Some(def_use.get_def(source_id, &ident_ref)?.0));
|
let def_id = def_id.or_else(|| Some(def_use.get_def(source_id, &ident_ref)?.0));
|
||||||
let def_info = def_id.and_then(|def_id| def_use.get_def_by_id(def_id));
|
let def_info = def_id.and_then(|def_id| def_use.get_def_by_id(def_id));
|
||||||
|
|
||||||
let values = analyze_expr(ctx.world, &use_site);
|
let values = analyze_expr(ctx.world(), &use_site);
|
||||||
for v in values {
|
for v in values {
|
||||||
// mostly builtin functions
|
// mostly builtin functions
|
||||||
if let Value::Func(f) = v.0 {
|
if let Value::Func(f) = v.0 {
|
||||||
|
@ -193,7 +193,7 @@ pub(crate) fn find_definition(
|
||||||
let root = LinkedNode::new(def_source.root());
|
let root = LinkedNode::new(def_source.root());
|
||||||
let def_name = root.leaf_at(def.range.start + 1)?;
|
let def_name = root.leaf_at(def.range.start + 1)?;
|
||||||
log::info!("def_name for function: {def_name:?}", def_name = def_name);
|
log::info!("def_name for function: {def_name:?}", def_name = def_name);
|
||||||
let values = analyze_expr(ctx.world, &def_name);
|
let values = analyze_expr(ctx.world(), &def_name);
|
||||||
let func = values.into_iter().find(|v| matches!(v.0, Value::Func(..)));
|
let func = values.into_iter().find(|v| matches!(v.0, Value::Func(..)));
|
||||||
log::info!("okay for function: {func:?}");
|
log::info!("okay for function: {func:?}");
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,18 @@ use crate::{
|
||||||
DefinitionLink, LspHoverContents, StatefulRequest,
|
DefinitionLink, LspHoverContents, StatefulRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The [`textDocument/hover`] request asks the server for hover information at
|
||||||
|
/// a given text document position.
|
||||||
|
///
|
||||||
|
/// [`textDocument/hover`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
|
||||||
|
///
|
||||||
|
/// Such hover information typically includes type signature information and
|
||||||
|
/// inline documentation for the symbol at the given text document position.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HoverRequest {
|
pub struct HoverRequest {
|
||||||
|
/// The path of the document to get hover information for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The position of the symbol to get hover information for.
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +40,10 @@ impl StatefulRequest for HoverRequest {
|
||||||
|
|
||||||
let contents = def_tooltip(ctx, &source, cursor).or_else(|| {
|
let contents = def_tooltip(ctx, &source, cursor).or_else(|| {
|
||||||
Some(typst_to_lsp::tooltip(&tooltip(
|
Some(typst_to_lsp::tooltip(&tooltip(
|
||||||
ctx.world, doc, &source, cursor,
|
ctx.world(),
|
||||||
|
doc,
|
||||||
|
&source,
|
||||||
|
cursor,
|
||||||
)?))
|
)?))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -76,10 +88,10 @@ let {name}({params});
|
||||||
crate::syntax::LexicalKind::Var(LexicalVarKind::Variable) => {
|
crate::syntax::LexicalKind::Var(LexicalVarKind::Variable) => {
|
||||||
let deref_node = deref_target.node();
|
let deref_node = deref_target.node();
|
||||||
// todo: check sensible length, value highlighting
|
// todo: check sensible length, value highlighting
|
||||||
let values = expr_tooltip(ctx.world, deref_node)
|
let values = expr_tooltip(ctx.world(), deref_node)
|
||||||
.map(|t| match t {
|
.map(|t| match t {
|
||||||
Tooltip::Text(s) => format!("// Values: {}", s),
|
Tooltip::Text(s) => format!("// Values: {s}"),
|
||||||
Tooltip::Code(s) => format!("// Values: {}", s),
|
Tooltip::Code(s) => format!("// Values: {s}"),
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Some(LspHoverContents::Scalar(lsp_types::MarkedString::String(
|
Some(LspHoverContents::Scalar(lsp_types::MarkedString::String(
|
||||||
|
|
|
@ -10,23 +10,30 @@ use typst::{
|
||||||
util::LazyHash,
|
util::LazyHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{prelude::*, SyntaxRequest};
|
use crate::{prelude::*, SemanticRequest};
|
||||||
|
|
||||||
|
/// Configuration for inlay hints.
|
||||||
pub struct InlayHintConfig {
|
pub struct InlayHintConfig {
|
||||||
// positional arguments group
|
// positional arguments group
|
||||||
|
/// Show inlay hints for positional arguments.
|
||||||
pub on_pos_args: bool,
|
pub on_pos_args: bool,
|
||||||
|
/// Disable inlay hints for single positional arguments.
|
||||||
pub off_single_pos_arg: bool,
|
pub off_single_pos_arg: bool,
|
||||||
|
|
||||||
// variadic arguments group
|
// variadic arguments group
|
||||||
|
/// Show inlay hints for variadic arguments.
|
||||||
pub on_variadic_args: bool,
|
pub on_variadic_args: bool,
|
||||||
|
/// Disable inlay hints for all variadic arguments but the first variadic
|
||||||
|
/// argument.
|
||||||
pub only_first_variadic_args: bool,
|
pub only_first_variadic_args: bool,
|
||||||
|
|
||||||
// todo
|
|
||||||
// The typst sugar grammar
|
// The typst sugar grammar
|
||||||
|
/// Show inlay hints for content block arguments.
|
||||||
pub on_content_block_args: bool,
|
pub on_content_block_args: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlayHintConfig {
|
impl InlayHintConfig {
|
||||||
|
/// A smart configuration that enables most useful inlay hints.
|
||||||
pub const fn smart() -> Self {
|
pub const fn smart() -> Self {
|
||||||
Self {
|
Self {
|
||||||
on_pos_args: true,
|
on_pos_args: true,
|
||||||
|
@ -40,20 +47,31 @@ impl InlayHintConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The [`textDocument/inlayHint`] request is sent from the client to the server
|
||||||
|
/// to compute inlay hints for a given `(text document, range)` tuple that may
|
||||||
|
/// be rendered in the editor in place with other text.
|
||||||
|
///
|
||||||
|
/// [`textDocument/inlayHint`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.17.0
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InlayHintRequest {
|
pub struct InlayHintRequest {
|
||||||
|
/// The path of the document to get inlay hints for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The range of the document to get inlay hints for.
|
||||||
pub range: LspRange,
|
pub range: LspRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for InlayHintRequest {
|
impl SemanticRequest for InlayHintRequest {
|
||||||
type Response = Vec<InlayHint>;
|
type Response = Vec<InlayHint>;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
let source = ctx.source_by_path(&self.path).ok()?;
|
let source = ctx.source_by_path(&self.path).ok()?;
|
||||||
let range = ctx.to_typst_range(self.range, &source)?;
|
let range = ctx.to_typst_range(self.range, &source)?;
|
||||||
|
|
||||||
let hints = inlay_hint(ctx.world, &source, range, ctx.position_encoding()).ok()?;
|
let hints = inlay_hint(ctx.world(), &source, range, ctx.position_encoding()).ok()?;
|
||||||
debug!(
|
debug!(
|
||||||
"got inlay hints on {source:?} => {hints:?}",
|
"got inlay hints on {source:?} => {hints:?}",
|
||||||
source = source.id(),
|
source = source.id(),
|
||||||
|
@ -516,7 +534,7 @@ impl ParamSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Signature {
|
pub(crate) struct Signature {
|
||||||
pub pos: Vec<Arc<ParamSpec>>,
|
pub pos: Vec<Arc<ParamSpec>>,
|
||||||
pub named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
|
pub named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
|
||||||
has_fill_or_size_or_stroke: bool,
|
has_fill_or_size_or_stroke: bool,
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
//! # tinymist-query
|
||||||
|
//!
|
||||||
|
//! **Note: this crate is under development. it currently doesn't ensure stable
|
||||||
|
//! APIs, and heavily depending on some unstable crates.**
|
||||||
|
//!
|
||||||
|
//! This crate provides a set of APIs to query the information about the source
|
||||||
|
//! code. Currently it provides:
|
||||||
|
//! + language queries defined by the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
|
||||||
|
|
||||||
mod adt;
|
mod adt;
|
||||||
pub mod analysis;
|
pub mod analysis;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
@ -8,7 +17,7 @@ pub(crate) mod diagnostics;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use analysis::AnalysisContext;
|
pub use analysis::AnalysisContext;
|
||||||
use typst::model::Document as TypstDocument;
|
use typst::{model::Document as TypstDocument, syntax::Source};
|
||||||
|
|
||||||
pub use diagnostics::*;
|
pub use diagnostics::*;
|
||||||
pub(crate) mod code_lens;
|
pub(crate) mod code_lens;
|
||||||
|
@ -48,31 +57,57 @@ pub use references::*;
|
||||||
|
|
||||||
pub mod lsp_typst_boundary;
|
pub mod lsp_typst_boundary;
|
||||||
pub use lsp_typst_boundary::*;
|
pub use lsp_typst_boundary::*;
|
||||||
|
pub(crate) mod lsp_features;
|
||||||
|
pub use lsp_features::*;
|
||||||
|
|
||||||
mod prelude;
|
mod prelude;
|
||||||
|
|
||||||
|
/// A compiled document with an self-incremented logical version.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct VersionedDocument {
|
pub struct VersionedDocument {
|
||||||
|
/// The version of the document.
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
|
/// The compiled document.
|
||||||
pub document: Arc<TypstDocument>,
|
pub document: Arc<TypstDocument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A request handler with given syntax information.
|
||||||
pub trait SyntaxRequest {
|
pub trait SyntaxRequest {
|
||||||
|
/// The response type of the request.
|
||||||
type Response;
|
type Response;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response>;
|
/// Request the information from the given source.
|
||||||
}
|
|
||||||
|
|
||||||
pub trait StatefulRequest {
|
|
||||||
type Response;
|
|
||||||
|
|
||||||
fn request(
|
fn request(
|
||||||
self,
|
self,
|
||||||
ctx: &mut AnalysisContext,
|
source: &Source,
|
||||||
v: Option<VersionedDocument>,
|
positing_encoding: PositionEncoding,
|
||||||
) -> Option<Self::Response>;
|
) -> Option<Self::Response>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A request handler with given (semantic) analysis context.
|
||||||
|
pub trait SemanticRequest {
|
||||||
|
/// The response type of the request.
|
||||||
|
type Response;
|
||||||
|
|
||||||
|
/// Request the information from the given context.
|
||||||
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A request handler with given (semantic) analysis context and a versioned
|
||||||
|
/// document.
|
||||||
|
pub trait StatefulRequest {
|
||||||
|
/// The response type of the request.
|
||||||
|
type Response;
|
||||||
|
|
||||||
|
/// Request the information from the given context.
|
||||||
|
fn request(
|
||||||
|
self,
|
||||||
|
ctx: &mut AnalysisContext,
|
||||||
|
doc: Option<VersionedDocument>,
|
||||||
|
) -> Option<Self::Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
mod polymorphic {
|
mod polymorphic {
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
49
crates/tinymist-query/src/lsp_features.rs
Normal file
49
crates/tinymist-query/src/lsp_features.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// todo: remove this
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use lsp_types::{
|
||||||
|
Registration, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
|
||||||
|
Unregistration,
|
||||||
|
};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::{Modifier, TokenType};
|
||||||
|
|
||||||
|
fn get_legend() -> SemanticTokensLegend {
|
||||||
|
SemanticTokensLegend {
|
||||||
|
token_types: TokenType::iter()
|
||||||
|
.filter(|e| *e != TokenType::None)
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
|
token_modifiers: Modifier::iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEMANTIC_TOKENS_REGISTRATION_ID: &str = "semantic_tokens";
|
||||||
|
const SEMANTIC_TOKENS_METHOD_ID: &str = "textDocument/semanticTokens";
|
||||||
|
|
||||||
|
pub fn get_semantic_tokens_registration(options: SemanticTokensOptions) -> Registration {
|
||||||
|
Registration {
|
||||||
|
id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(),
|
||||||
|
method: SEMANTIC_TOKENS_METHOD_ID.to_owned(),
|
||||||
|
register_options: Some(
|
||||||
|
serde_json::to_value(options)
|
||||||
|
.expect("semantic tokens options should be representable as JSON value"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_semantic_tokens_unregistration() -> Unregistration {
|
||||||
|
Unregistration {
|
||||||
|
id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(),
|
||||||
|
method: SEMANTIC_TOKENS_METHOD_ID.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_semantic_tokens_options() -> SemanticTokensOptions {
|
||||||
|
SemanticTokensOptions {
|
||||||
|
legend: get_legend(),
|
||||||
|
full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
//! Conversions between Typst and LSP types and representations
|
//! Conversions between Typst and LSP types and representations
|
||||||
|
|
||||||
|
// todo: remove this
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use lsp_types;
|
use lsp_types;
|
||||||
|
|
||||||
pub type LspPosition = lsp_types::Position;
|
pub type LspPosition = lsp_types::Position;
|
||||||
|
|
|
@ -25,8 +25,6 @@ pub use typst::syntax::{
|
||||||
LinkedNode, Source, Spanned, SyntaxKind,
|
LinkedNode, Source, Spanned, SyntaxKind,
|
||||||
};
|
};
|
||||||
pub use typst::World;
|
pub use typst::World;
|
||||||
// use typst_ts_compiler::service::WorkspaceProvider;
|
|
||||||
pub use typst_ts_compiler::TypstSystemWorld;
|
|
||||||
|
|
||||||
pub use crate::analysis::{analyze_expr, AnalysisContext};
|
pub use crate::analysis::{analyze_expr, AnalysisContext};
|
||||||
pub use crate::lsp_typst_boundary::{
|
pub use crate::lsp_typst_boundary::{
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::{find_definition, prelude::*, syntax::get_deref_target, DefinitionLink, SyntaxRequest};
|
use crate::{
|
||||||
|
find_definition, prelude::*, syntax::get_deref_target, DefinitionLink, SemanticRequest,
|
||||||
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
/// The [`textDocument/prepareRename`] request is sent from the client to the
|
/// The [`textDocument/prepareRename`] request is sent from the client to the
|
||||||
|
@ -26,7 +28,7 @@ pub struct PrepareRenameRequest {
|
||||||
|
|
||||||
// todo: rename alias
|
// todo: rename alias
|
||||||
// todo: rename import path?
|
// todo: rename import path?
|
||||||
impl SyntaxRequest for PrepareRenameRequest {
|
impl SemanticRequest for PrepareRenameRequest {
|
||||||
type Response = PrepareRenameResponse;
|
type Response = PrepareRenameResponse;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use log::debug;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
syntax::{get_deref_target, DerefTarget, IdentRef},
|
syntax::{get_deref_target, DerefTarget, IdentRef},
|
||||||
SyntaxRequest,
|
SemanticRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The [`textDocument/references`] request is sent from the client to the
|
/// The [`textDocument/references`] request is sent from the client to the
|
||||||
|
@ -19,7 +19,7 @@ pub struct ReferencesRequest {
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for ReferencesRequest {
|
impl SemanticRequest for ReferencesRequest {
|
||||||
type Response = Vec<LspLocation>;
|
type Response = Vec<LspLocation>;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use lsp_types::TextEdit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
find_definition, find_references, prelude::*, syntax::get_deref_target,
|
find_definition, find_references, prelude::*, syntax::get_deref_target,
|
||||||
validate_renaming_definition, SyntaxRequest,
|
validate_renaming_definition, SemanticRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The [`textDocument/rename`] request is sent from the client to the server to
|
/// The [`textDocument/rename`] request is sent from the client to the server to
|
||||||
|
@ -21,7 +21,7 @@ pub struct RenameRequest {
|
||||||
pub new_name: String,
|
pub new_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for RenameRequest {
|
impl SemanticRequest for RenameRequest {
|
||||||
type Response = WorkspaceEdit;
|
type Response = WorkspaceEdit;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
|
|
@ -1,23 +1,41 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, SyntaxRequest};
|
||||||
|
|
||||||
|
/// The [`textDocument/selectionRange`] request is sent from the client to the
|
||||||
|
/// server to return suggested selection ranges at an array of given positions.
|
||||||
|
/// A selection range is a range around the cursor position which the user might
|
||||||
|
/// be interested in selecting.
|
||||||
|
///
|
||||||
|
/// [`textDocument/selectionRange`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange
|
||||||
|
///
|
||||||
|
/// A selection range in the return array is for the position in the provided
|
||||||
|
/// parameters at the same index. Therefore `params.positions[i]` must be
|
||||||
|
/// contained in `result[i].range`.
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.15.0.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SelectionRangeRequest {
|
pub struct SelectionRangeRequest {
|
||||||
|
/// The path of the document to get selection ranges for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The positions to get selection ranges for.
|
||||||
pub positions: Vec<LspPosition>,
|
pub positions: Vec<LspPosition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionRangeRequest {
|
impl SyntaxRequest for SelectionRangeRequest {
|
||||||
pub fn request(
|
type Response = Vec<SelectionRange>;
|
||||||
|
|
||||||
|
fn request(
|
||||||
self,
|
self,
|
||||||
source: Source,
|
source: &Source,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<Vec<SelectionRange>> {
|
) -> Option<Self::Response> {
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
for position in self.positions {
|
for position in self.positions {
|
||||||
let typst_offset = lsp_to_typst::position(position, position_encoding, &source)?;
|
let typst_offset = lsp_to_typst::position(position, position_encoding, source)?;
|
||||||
let tree = LinkedNode::new(source.root());
|
let tree = LinkedNode::new(source.root());
|
||||||
let leaf = tree.leaf_at(typst_offset + 1)?;
|
let leaf = tree.leaf_at(typst_offset + 1)?;
|
||||||
ranges.push(range_for_node(&source, position_encoding, &leaf));
|
ranges.push(range_for_node(source, position_encoding, &leaf));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ranges)
|
Some(ranges)
|
||||||
|
|
|
@ -1,64 +1,22 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use lsp_types::{
|
use lsp_types::{SemanticToken, SemanticTokensEdit};
|
||||||
Registration, SemanticToken, SemanticTokensEdit, SemanticTokensFullOptions,
|
|
||||||
SemanticTokensLegend, SemanticTokensOptions, Unregistration,
|
|
||||||
};
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||||
|
|
||||||
use crate::{LspPosition, PositionEncoding};
|
use crate::{LspPosition, PositionEncoding};
|
||||||
|
|
||||||
use self::delta::token_delta;
|
use self::delta::token_delta;
|
||||||
use self::modifier_set::ModifierSet;
|
use self::modifier_set::ModifierSet;
|
||||||
use self::typst_tokens::{Modifier, TokenType};
|
|
||||||
|
|
||||||
use self::delta::CacheInner as TokenCacheInner;
|
use self::delta::CacheInner as TokenCacheInner;
|
||||||
|
|
||||||
mod delta;
|
mod delta;
|
||||||
mod modifier_set;
|
mod modifier_set;
|
||||||
mod typst_tokens;
|
mod typst_tokens;
|
||||||
|
pub use self::typst_tokens::{Modifier, TokenType};
|
||||||
|
|
||||||
pub fn get_legend() -> SemanticTokensLegend {
|
/// A semantic token context providing incremental semantic tokens rendering.
|
||||||
SemanticTokensLegend {
|
|
||||||
token_types: TokenType::iter()
|
|
||||||
.filter(|e| *e != TokenType::None)
|
|
||||||
.map(Into::into)
|
|
||||||
.collect(),
|
|
||||||
token_modifiers: Modifier::iter().map(Into::into).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SEMANTIC_TOKENS_REGISTRATION_ID: &str = "semantic_tokens";
|
|
||||||
const SEMANTIC_TOKENS_METHOD_ID: &str = "textDocument/semanticTokens";
|
|
||||||
|
|
||||||
pub fn get_semantic_tokens_registration(options: SemanticTokensOptions) -> Registration {
|
|
||||||
Registration {
|
|
||||||
id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(),
|
|
||||||
method: SEMANTIC_TOKENS_METHOD_ID.to_owned(),
|
|
||||||
register_options: Some(
|
|
||||||
serde_json::to_value(options)
|
|
||||||
.expect("semantic tokens options should be representable as JSON value"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_semantic_tokens_unregistration() -> Unregistration {
|
|
||||||
Unregistration {
|
|
||||||
id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(),
|
|
||||||
method: SEMANTIC_TOKENS_METHOD_ID.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_semantic_tokens_options() -> SemanticTokensOptions {
|
|
||||||
SemanticTokensOptions {
|
|
||||||
legend: get_legend(),
|
|
||||||
full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SemanticTokenContext {
|
pub struct SemanticTokenContext {
|
||||||
cache: RwLock<TokenCacheInner>,
|
cache: RwLock<TokenCacheInner>,
|
||||||
|
@ -70,6 +28,7 @@ pub struct SemanticTokenContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SemanticTokenContext {
|
impl SemanticTokenContext {
|
||||||
|
/// Create a new semantic token context.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
allow_overlapping_token: bool,
|
allow_overlapping_token: bool,
|
||||||
|
@ -83,6 +42,7 @@ impl SemanticTokenContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the semantic tokens for a source.
|
||||||
pub fn get_semantic_tokens_full(&self, source: &Source) -> (Vec<SemanticToken>, String) {
|
pub fn get_semantic_tokens_full(&self, source: &Source) -> (Vec<SemanticToken>, String) {
|
||||||
let root = LinkedNode::new(source.root());
|
let root = LinkedNode::new(source.root());
|
||||||
|
|
||||||
|
@ -98,6 +58,7 @@ impl SemanticTokenContext {
|
||||||
(output, result_id)
|
(output, result_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the semantic tokens delta for a source.
|
||||||
pub fn try_semantic_tokens_delta_from_result_id(
|
pub fn try_semantic_tokens_delta_from_result_id(
|
||||||
&self,
|
&self,
|
||||||
source: &Source,
|
source: &Source,
|
||||||
|
@ -292,7 +253,7 @@ impl Tokenizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Token {
|
struct Token {
|
||||||
pub token_type: TokenType,
|
pub token_type: TokenType,
|
||||||
pub modifiers: ModifierSet,
|
pub modifiers: ModifierSet,
|
||||||
pub range: Range<usize>,
|
pub range: Range<usize>,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
//! Types for tokens used for Typst syntax
|
//! Types for tokens used for Typst syntax
|
||||||
|
|
||||||
|
// todo: remove this
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use lsp_types::{SemanticTokenModifier, SemanticTokenType};
|
use lsp_types::{SemanticTokenModifier, SemanticTokenType};
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
use crate::{prelude::*, SemanticTokenContext};
|
use crate::{prelude::*, SemanticTokenContext};
|
||||||
|
|
||||||
|
/// The [`textDocument/semanticTokens/full/delta`] request is sent from the
|
||||||
|
/// client to the server to resolve the semantic tokens of a given file,
|
||||||
|
/// **returning only the delta**.
|
||||||
|
///
|
||||||
|
/// [`textDocument/semanticTokens/full/delta`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
|
||||||
|
///
|
||||||
|
/// Similar to [`semantic_tokens_full`](Self::semantic_tokens_full), except it
|
||||||
|
/// returns a sequence of [`SemanticTokensEdit`] to transform a previous result
|
||||||
|
/// into a new result.
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.16.0.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SemanticTokensDeltaRequest {
|
pub struct SemanticTokensDeltaRequest {
|
||||||
|
/// The path of the document to get semantic tokens for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The previous result id to compute the delta from.
|
||||||
pub previous_result_id: String,
|
pub previous_result_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SemanticTokensDeltaRequest {
|
impl SemanticTokensDeltaRequest {
|
||||||
|
/// Handles the request to compute the semantic tokens delta for a given
|
||||||
|
/// document.
|
||||||
pub fn request(
|
pub fn request(
|
||||||
self,
|
self,
|
||||||
ctx: &SemanticTokenContext,
|
ctx: &SemanticTokenContext,
|
||||||
|
|
|
@ -1,11 +1,29 @@
|
||||||
use crate::{prelude::*, SemanticTokenContext};
|
use crate::{prelude::*, SemanticTokenContext};
|
||||||
|
|
||||||
|
/// The [`textDocument/semanticTokens/full`] request is sent from the client to
|
||||||
|
/// the server to resolve the semantic tokens of a given file.
|
||||||
|
///
|
||||||
|
/// [`textDocument/semanticTokens/full`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
|
||||||
|
///
|
||||||
|
/// Semantic tokens are used to add additional color information to a file that
|
||||||
|
/// depends on language specific symbol information. A semantic token request
|
||||||
|
/// usually produces a large result. The protocol therefore supports encoding
|
||||||
|
/// tokens with numbers. In addition, optional support for deltas is available,
|
||||||
|
/// i.e. [`semantic_tokens_full_delta`].
|
||||||
|
///
|
||||||
|
/// [`semantic_tokens_full_delta`]: Self::semantic_tokens_full_delta
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// This request was introduced in specification version 3.16.0.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SemanticTokensFullRequest {
|
pub struct SemanticTokensFullRequest {
|
||||||
|
/// The path of the document to get semantic tokens for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SemanticTokensFullRequest {
|
impl SemanticTokensFullRequest {
|
||||||
|
/// Handles the request to compute the semantic tokens for a given document.
|
||||||
pub fn request(
|
pub fn request(
|
||||||
self,
|
self,
|
||||||
ctx: &SemanticTokenContext,
|
ctx: &SemanticTokenContext,
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
use crate::{prelude::*, SyntaxRequest};
|
use crate::{prelude::*, SemanticRequest};
|
||||||
|
|
||||||
|
/// The [`textDocument/signatureHelp`] request is sent from the client to the
|
||||||
|
/// server to request signature information at a given cursor position.
|
||||||
|
///
|
||||||
|
/// [`textDocument/signatureHelp`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SignatureHelpRequest {
|
pub struct SignatureHelpRequest {
|
||||||
|
/// The path of the document to get signature help for.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The position of the cursor to get signature help for.
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for SignatureHelpRequest {
|
impl SemanticRequest for SignatureHelpRequest {
|
||||||
type Response = SignatureHelp;
|
type Response = SignatureHelp;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
@ -20,7 +26,7 @@ impl SyntaxRequest for SignatureHelpRequest {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let values = analyze_expr(ctx.world, &callee_node);
|
let values = analyze_expr(ctx.world(), &callee_node);
|
||||||
|
|
||||||
let function = values.into_iter().find_map(|v| match v.0 {
|
let function = values.into_iter().find_map(|v| match v.0 {
|
||||||
Value::Func(f) => Some(f),
|
Value::Func(f) => Some(f),
|
||||||
|
|
|
@ -1,17 +1,34 @@
|
||||||
use typst_ts_compiler::NotifyApi;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||||
SyntaxRequest,
|
SemanticRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The [`workspace/symbol`] request is sent from the client to the server to
|
||||||
|
/// list project-wide symbols matching the given query string.
|
||||||
|
///
|
||||||
|
/// [`workspace/symbol`]: https://microsoft.github.io/language-server-protocol/specification#workspace_symbol
|
||||||
|
///
|
||||||
|
/// # Compatibility
|
||||||
|
///
|
||||||
|
/// Since 3.17.0, servers can also provider a handler for
|
||||||
|
/// [`workspaceSymbol/resolve`] requests. This allows servers to return
|
||||||
|
/// workspace symbols without a range for a `workspace/symbol` request. Clients
|
||||||
|
/// then need to resolve the range when necessary using the `workspaceSymbol/
|
||||||
|
/// resolve` request.
|
||||||
|
///
|
||||||
|
/// [`workspaceSymbol/resolve`]: Self::symbol_resolve
|
||||||
|
///
|
||||||
|
/// Servers can only use this new model if clients advertise support for it via
|
||||||
|
/// the `workspace.symbol.resolve_support` capability.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SymbolRequest {
|
pub struct SymbolRequest {
|
||||||
|
/// The query string to filter symbols by. It is usually the exact content
|
||||||
|
/// of the user's input box in the UI.
|
||||||
pub pattern: Option<String>,
|
pub pattern: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxRequest for SymbolRequest {
|
impl SemanticRequest for SymbolRequest {
|
||||||
type Response = Vec<SymbolInformation>;
|
type Response = Vec<SymbolInformation>;
|
||||||
|
|
||||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||||
|
@ -19,7 +36,7 @@ impl SyntaxRequest for SymbolRequest {
|
||||||
|
|
||||||
let mut symbols = vec![];
|
let mut symbols = vec![];
|
||||||
|
|
||||||
ctx.world.iter_dependencies(&mut |path, _| {
|
ctx.resources.iter_dependencies(&mut |path, _| {
|
||||||
let Ok(source) = ctx.source_by_path(path) else {
|
let Ok(source) = ctx.source_by_path(path) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,6 @@ use anyhow::{anyhow, Context};
|
||||||
use ecow::{eco_vec, EcoVec};
|
use ecow::{eco_vec, EcoVec};
|
||||||
use log::info;
|
use log::info;
|
||||||
use lsp_types::SymbolKind;
|
use lsp_types::SymbolKind;
|
||||||
use reflexo::error::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typst::{
|
use typst::{
|
||||||
syntax::{
|
syntax::{
|
||||||
|
@ -654,7 +653,7 @@ impl LexicalHierarchyWorker {
|
||||||
let name = if e.starts_with('@') {
|
let name = if e.starts_with('@') {
|
||||||
let spec = e
|
let spec = e
|
||||||
.parse::<PackageSpec>()
|
.parse::<PackageSpec>()
|
||||||
.context("parse package spec failed for name")?;
|
.map_err(|e| anyhow!("parse package spec failed: {:?}", e))?;
|
||||||
spec.name.to_string()
|
spec.name.to_string()
|
||||||
} else {
|
} else {
|
||||||
let e = Path::new(e.as_ref())
|
let e = Path::new(e.as_ref())
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
pub mod import;
|
//! Analyzing the syntax of a source file.
|
||||||
|
//!
|
||||||
|
//! This module must hide all **AST details** from the rest of the codebase.
|
||||||
|
|
||||||
|
// todo: remove this
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
pub(crate) mod import;
|
||||||
pub use import::*;
|
pub use import::*;
|
||||||
pub mod lexical_hierarchy;
|
pub(crate) mod lexical_hierarchy;
|
||||||
pub(crate) use lexical_hierarchy::*;
|
pub use lexical_hierarchy::*;
|
||||||
pub mod matcher;
|
pub(crate) mod matcher;
|
||||||
pub use matcher::*;
|
pub use matcher::*;
|
||||||
pub mod module;
|
pub(crate) mod module;
|
||||||
pub use module::*;
|
pub use module::*;
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
@ -70,7 +77,7 @@ impl<'de> Deserialize<'de> for IdentRef {
|
||||||
.split("..")
|
.split("..")
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
s.parse().map_err(|e| {
|
s.parse().map_err(|e| {
|
||||||
serde::de::Error::custom(format!("failed to parse range: {}", e))
|
serde::de::Error::custom(format!("failed to parse range: {e}"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<usize>, _>>()?;
|
.collect::<Result<Vec<usize>, _>>()?;
|
||||||
|
|
|
@ -7,11 +7,18 @@ use crate::prelude::AnalysisContext;
|
||||||
|
|
||||||
use super::find_imports;
|
use super::find_imports;
|
||||||
|
|
||||||
|
/// The dependency information of a module (file).
|
||||||
pub struct ModuleDependency {
|
pub struct ModuleDependency {
|
||||||
|
/// The dependencies of this module.
|
||||||
pub dependencies: EcoVec<TypstFileId>,
|
pub dependencies: EcoVec<TypstFileId>,
|
||||||
|
/// The dependents of this module.
|
||||||
pub dependents: EcoVec<TypstFileId>,
|
pub dependents: EcoVec<TypstFileId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct the module dependencies of the given context.
|
||||||
|
///
|
||||||
|
/// It will scan all the files in the context, using [`AnalysisContext::files`],
|
||||||
|
/// and find the dependencies and dependents of each file.
|
||||||
pub fn construct_module_dependencies(
|
pub fn construct_module_dependencies(
|
||||||
ctx: &mut AnalysisContext,
|
ctx: &mut AnalysisContext,
|
||||||
) -> HashMap<TypstFileId, ModuleDependency> {
|
) -> HashMap<TypstFileId, ModuleDependency> {
|
||||||
|
@ -31,7 +38,7 @@ pub fn construct_module_dependencies(
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_id = source.id();
|
let file_id = source.id();
|
||||||
let deps = find_imports(ctx.world, &source);
|
let deps = find_imports(ctx.world(), &source);
|
||||||
dependencies
|
dependencies
|
||||||
.entry(file_id)
|
.entry(file_id)
|
||||||
.or_insert_with(|| ModuleDependency {
|
.or_insert_with(|| ModuleDependency {
|
||||||
|
@ -55,6 +62,9 @@ pub fn construct_module_dependencies(
|
||||||
dependencies
|
dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scan the files in the workspace and return the file ids.
|
||||||
|
///
|
||||||
|
/// Note: this function will touch the physical file system.
|
||||||
pub fn scan_workspace_files(root: &Path) -> Vec<TypstFileId> {
|
pub fn scan_workspace_files(root: &Path) -> Vec<TypstFileId> {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
for path in walkdir::WalkDir::new(root).follow_links(false).into_iter() {
|
for path in walkdir::WalkDir::new(root).follow_links(false).into_iter() {
|
||||||
|
|
|
@ -9,21 +9,44 @@ use serde::Serialize;
|
||||||
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
||||||
use typst::syntax::{
|
use typst::syntax::{
|
||||||
ast::{self, AstNode},
|
ast::{self, AstNode},
|
||||||
LinkedNode, Source, SyntaxKind, VirtualPath,
|
FileId as TypstFileId, LinkedNode, Source, SyntaxKind, VirtualPath,
|
||||||
};
|
};
|
||||||
|
use typst::{diag::PackageError, foundations::Bytes};
|
||||||
use typst_ts_compiler::{
|
use typst_ts_compiler::{
|
||||||
service::{CompileDriver, Compiler, WorkspaceProvider},
|
service::{CompileDriver, Compiler, EntryManager},
|
||||||
ShadowApi,
|
NotifyApi, ShadowApi,
|
||||||
};
|
};
|
||||||
use typst_ts_core::{config::CompileOpts, Bytes, TypstFileId};
|
use typst_ts_core::{
|
||||||
|
config::compiler::{EntryOpts, EntryState},
|
||||||
|
package::Registry,
|
||||||
|
};
|
||||||
|
use typst_ts_core::{config::CompileOpts, package::PackageSpec};
|
||||||
|
|
||||||
pub use insta::assert_snapshot;
|
pub use insta::assert_snapshot;
|
||||||
pub use typst_ts_compiler::TypstSystemWorld;
|
pub use typst_ts_compiler::TypstSystemWorld;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::Analysis, prelude::AnalysisContext, typst_to_lsp, LspPosition, PositionEncoding,
|
analysis::{Analysis, AnaylsisResources},
|
||||||
|
prelude::AnalysisContext,
|
||||||
|
typst_to_lsp, LspPosition, PositionEncoding,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WrapWorld<'a>(&'a mut TypstSystemWorld);
|
||||||
|
|
||||||
|
impl<'a> AnaylsisResources for WrapWorld<'a> {
|
||||||
|
fn world(&self) -> &dyn typst::World {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, spec: &PackageSpec) -> Result<std::sync::Arc<Path>, PackageError> {
|
||||||
|
self.0.registry.resolve(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_dependencies(&self, f: &mut dyn FnMut(&reflexo::ImmutPath, typst_ts_compiler::Time)) {
|
||||||
|
self.0.iter_dependencies(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) {
|
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) {
|
||||||
let mut settings = insta::Settings::new();
|
let mut settings = insta::Settings::new();
|
||||||
settings.set_prepend_module_to_snapshot(false);
|
settings.set_prepend_module_to_snapshot(false);
|
||||||
|
@ -36,20 +59,19 @@ pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf))
|
||||||
let contents = contents.replace("\r\n", "\n");
|
let contents = contents.replace("\r\n", "\n");
|
||||||
|
|
||||||
run_with_sources(&contents, |w: &mut TypstSystemWorld, p| {
|
run_with_sources(&contents, |w: &mut TypstSystemWorld, p| {
|
||||||
|
let root = w.workspace_root().unwrap();
|
||||||
let paths = w
|
let paths = w
|
||||||
.shadow_paths()
|
.shadow_paths()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
TypstFileId::new(
|
TypstFileId::new(None, VirtualPath::new(p.strip_prefix(&root).unwrap()))
|
||||||
None,
|
|
||||||
VirtualPath::new(p.strip_prefix(w.workspace_root()).unwrap()),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let w = WrapWorld(w);
|
||||||
let mut ctx = AnalysisContext::new(
|
let mut ctx = AnalysisContext::new(
|
||||||
w,
|
&w,
|
||||||
Analysis {
|
Analysis {
|
||||||
root: w.workspace_root(),
|
root,
|
||||||
position_encoding: PositionEncoding::Utf16,
|
position_encoding: PositionEncoding::Utf16,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -67,7 +89,7 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P
|
||||||
PathBuf::from("/")
|
PathBuf::from("/")
|
||||||
};
|
};
|
||||||
let mut world = TypstSystemWorld::new(CompileOpts {
|
let mut world = TypstSystemWorld::new(CompileOpts {
|
||||||
root_dir: root.clone(),
|
entry: EntryOpts::new_rooted(root.as_path().into(), None),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -99,15 +121,21 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P
|
||||||
last_pw = Some(pw);
|
last_pw = Some(pw);
|
||||||
}
|
}
|
||||||
|
|
||||||
world.set_main_id(TypstFileId::new(None, VirtualPath::new("/main.typ")));
|
world.mutate_entry(EntryState::new_detached()).unwrap();
|
||||||
let mut driver = CompileDriver::new(world);
|
let mut driver = CompileDriver::new(world);
|
||||||
let _ = driver.compile(&mut Default::default());
|
let _ = driver.compile(&mut Default::default());
|
||||||
|
|
||||||
let pw = last_pw.unwrap();
|
let pw = last_pw.unwrap();
|
||||||
driver.world_mut().set_main_id(TypstFileId::new(
|
driver
|
||||||
None,
|
.world_mut()
|
||||||
VirtualPath::new(pw.strip_prefix(root).unwrap()),
|
.mutate_entry(EntryState::new_rooted(
|
||||||
));
|
root.as_path().into(),
|
||||||
|
Some(TypstFileId::new(
|
||||||
|
None,
|
||||||
|
VirtualPath::new(pw.strip_prefix(root).unwrap()),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
f(driver.world_mut(), pw)
|
f(driver.world_mut(), pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ typst.workspace = true
|
||||||
typst-pdf.workspace = true
|
typst-pdf.workspace = true
|
||||||
typst-assets = { workspace = true, features = ["fonts"] }
|
typst-assets = { workspace = true, features = ["fonts"] }
|
||||||
|
|
||||||
typst-ts-core = { version = "0.4.2-rc6", default-features = false, features = [
|
typst-ts-core = { workspace = true, default-features = false, features = [
|
||||||
"flat-vector",
|
"flat-vector",
|
||||||
"vector-bbox",
|
"vector-bbox",
|
||||||
"no-content-hint",
|
"no-content-hint",
|
||||||
|
|
|
@ -5,28 +5,22 @@ pub mod compile;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod typst;
|
pub mod typst;
|
||||||
|
|
||||||
use std::{borrow::Cow, path::PathBuf};
|
|
||||||
|
|
||||||
use ::typst::diag::FileResult;
|
use ::typst::diag::FileResult;
|
||||||
use tokio::sync::{broadcast, watch};
|
use tokio::sync::{broadcast, watch};
|
||||||
use typst_ts_compiler::vfs::notify::FileChangeSet;
|
use typst_ts_compiler::vfs::notify::FileChangeSet;
|
||||||
use typst_ts_core::{config::CompileOpts, ImmutPath};
|
use typst_ts_core::config::compiler::EntryState;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
render::{PdfExportActor, PdfExportConfig},
|
render::{PdfExportActor, PdfExportConfig},
|
||||||
typst::{create_server, CompileActor, OptsState},
|
typst::{create_server, CompileActor},
|
||||||
};
|
};
|
||||||
use crate::TypstLanguageServer;
|
use crate::TypstLanguageServer;
|
||||||
|
|
||||||
impl TypstLanguageServer {
|
impl TypstLanguageServer {
|
||||||
pub fn server(&self, name: String, entry: Option<ImmutPath>) -> CompileActor {
|
pub fn server(&self, name: String, entry: EntryState) -> CompileActor {
|
||||||
let (doc_tx, doc_rx) = watch::channel(None);
|
let (doc_tx, doc_rx) = watch::channel(None);
|
||||||
let (render_tx, _) = broadcast::channel(10);
|
let (render_tx, _) = broadcast::channel(10);
|
||||||
|
|
||||||
// todo: don't ignore entry from typst_extra_args
|
|
||||||
// entry: command.input,
|
|
||||||
let root_dir = self.config.determine_root(entry.as_ref());
|
|
||||||
|
|
||||||
// Run the PDF export actor before preparing cluster to avoid loss of events
|
// Run the PDF export actor before preparing cluster to avoid loss of events
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
PdfExportActor::new(
|
PdfExportActor::new(
|
||||||
|
@ -34,36 +28,14 @@ impl TypstLanguageServer {
|
||||||
render_tx.subscribe(),
|
render_tx.subscribe(),
|
||||||
PdfExportConfig {
|
PdfExportConfig {
|
||||||
substitute_pattern: self.config.output_path.clone(),
|
substitute_pattern: self.config.output_path.clone(),
|
||||||
root: root_dir.clone(),
|
entry: entry.clone(),
|
||||||
path: entry.clone().map(From::from),
|
|
||||||
mode: self.config.export_pdf,
|
mode: self.config.export_pdf,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.run(),
|
.run(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let opts = {
|
// Take all dirty files in memory as the initial snapshot
|
||||||
let mut opts = self.compile_opts.clone();
|
|
||||||
|
|
||||||
if let Some(extras) = &self.config.typst_extra_args {
|
|
||||||
if let Some(inputs) = extras.inputs.as_ref() {
|
|
||||||
if opts.inputs.is_empty() {
|
|
||||||
opts.inputs = inputs.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !extras.font_paths.is_empty() && opts.font_paths.is_empty() {
|
|
||||||
opts.font_paths = extras.font_paths.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
move |root_dir: PathBuf| CompileOpts {
|
|
||||||
root_dir,
|
|
||||||
// todo: additional inputs
|
|
||||||
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
|
|
||||||
..opts
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let snapshot = FileChangeSet::new_inserts(
|
let snapshot = FileChangeSet::new_inserts(
|
||||||
self.memory_changes
|
self.memory_changes
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -74,11 +46,12 @@ impl TypstLanguageServer {
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create the server
|
||||||
create_server(
|
create_server(
|
||||||
name,
|
name,
|
||||||
|
&self.config,
|
||||||
self.const_config(),
|
self.const_config(),
|
||||||
OptsState::new(root_dir.clone(), opts),
|
self.font.clone(),
|
||||||
root_dir,
|
|
||||||
entry,
|
entry,
|
||||||
snapshot,
|
snapshot,
|
||||||
self.diag_tx.clone(),
|
self.diag_tx.clone(),
|
||||||
|
|
|
@ -23,18 +23,31 @@ use typst_ts_compiler::{
|
||||||
ShadowApi,
|
ShadowApi,
|
||||||
};
|
};
|
||||||
use typst_ts_core::{
|
use typst_ts_core::{
|
||||||
|
config::compiler::EntryState,
|
||||||
debug_loc::{SourceLocation, SourceSpanOffset},
|
debug_loc::{SourceLocation, SourceSpanOffset},
|
||||||
error::prelude::{map_string_err, ZResult},
|
error::prelude::{map_string_err, ZResult},
|
||||||
ImmutPath, TypstDocument, TypstFileId,
|
TypstDocument, TypstFileId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use typst_ts_compiler::service::{
|
use typst_ts_compiler::service::{
|
||||||
features::FeatureSet, CompileEnv, CompileReporter, Compiler, ConsoleDiagReporter,
|
features::FeatureSet, CompileEnv, CompileReporter, Compiler, ConsoleDiagReporter, EntryManager,
|
||||||
WorkspaceProvider, WorldExporter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{task::BorrowTask, utils};
|
use crate::{task::BorrowTask, utils};
|
||||||
|
|
||||||
|
pub trait EntryStateExt {
|
||||||
|
fn is_inactive(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntryStateExt for EntryState {
|
||||||
|
fn is_inactive(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
EntryState::Detached | EntryState::Workspace { main: None, .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Interrupts for external sources
|
/// Interrupts for external sources
|
||||||
enum ExternalInterrupt<Ctx> {
|
enum ExternalInterrupt<Ctx> {
|
||||||
/// Compile anyway.
|
/// Compile anyway.
|
||||||
|
@ -89,8 +102,6 @@ struct SuspendState {
|
||||||
pub struct CompileActor<C: Compiler> {
|
pub struct CompileActor<C: Compiler> {
|
||||||
/// The underlying compiler.
|
/// The underlying compiler.
|
||||||
pub compiler: CompileReporter<C>,
|
pub compiler: CompileReporter<C>,
|
||||||
/// The root path of the workspace.
|
|
||||||
pub root: ImmutPath,
|
|
||||||
/// Whether to enable file system watching.
|
/// Whether to enable file system watching.
|
||||||
pub enable_watch: bool,
|
pub enable_watch: bool,
|
||||||
|
|
||||||
|
@ -117,16 +128,11 @@ pub struct CompileActor<C: Compiler> {
|
||||||
suspend_state: SuspendState,
|
suspend_state: SuspendState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Compiler + ShadowApi + WorldExporter + Send + 'static> CompileActor<C>
|
impl<C: Compiler + ShadowApi + Send + 'static> CompileActor<C>
|
||||||
where
|
where
|
||||||
C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>,
|
C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>,
|
||||||
{
|
{
|
||||||
pub fn new_with_features(
|
pub fn new_with_features(compiler: C, entry: EntryState, feature_set: FeatureSet) -> Self {
|
||||||
compiler: C,
|
|
||||||
root: ImmutPath,
|
|
||||||
entry: Option<ImmutPath>,
|
|
||||||
feature_set: FeatureSet,
|
|
||||||
) -> Self {
|
|
||||||
let (steal_send, steal_recv) = mpsc::unbounded_channel();
|
let (steal_send, steal_recv) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let watch_feature_set = Arc::new(
|
let watch_feature_set = Arc::new(
|
||||||
|
@ -138,7 +144,6 @@ where
|
||||||
Self {
|
Self {
|
||||||
compiler: CompileReporter::new(compiler)
|
compiler: CompileReporter::new(compiler)
|
||||||
.with_generic_reporter(ConsoleDiagReporter::default()),
|
.with_generic_reporter(ConsoleDiagReporter::default()),
|
||||||
root,
|
|
||||||
|
|
||||||
logical_tick: 1,
|
logical_tick: 1,
|
||||||
enable_watch: false,
|
enable_watch: false,
|
||||||
|
@ -154,15 +159,15 @@ where
|
||||||
steal_recv,
|
steal_recv,
|
||||||
|
|
||||||
suspend_state: SuspendState {
|
suspend_state: SuspendState {
|
||||||
suspended: entry.is_none(),
|
suspended: entry.is_inactive(),
|
||||||
dirty: false,
|
dirty: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new compiler thread.
|
/// Create a new compiler thread.
|
||||||
pub fn new(compiler: C, root: ImmutPath, entry: Option<ImmutPath>) -> Self {
|
pub fn new(compiler: C, entry: EntryState) -> Self {
|
||||||
Self::new_with_features(compiler, root, entry, FeatureSet::default())
|
Self::new_with_features(compiler, entry, FeatureSet::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn success_doc(&self) -> Option<VersionedDocument> {
|
pub fn success_doc(&self) -> Option<VersionedDocument> {
|
||||||
|
@ -327,8 +332,8 @@ where
|
||||||
Some(compile_thread)
|
Some(compile_thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn change_entry(&mut self, entry: Option<Arc<Path>>) {
|
pub(crate) fn change_entry(&mut self, entry: EntryState) {
|
||||||
let suspending = entry.is_none();
|
let suspending = entry.is_inactive();
|
||||||
if suspending {
|
if suspending {
|
||||||
self.suspend_state.suspended = true;
|
self.suspend_state.suspended = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -530,7 +535,7 @@ impl<C: Compiler> CompileActor<C> {
|
||||||
self,
|
self,
|
||||||
CompileClient {
|
CompileClient {
|
||||||
intr_tx: steal_send,
|
intr_tx: steal_send,
|
||||||
_ctx: typst_ts_core::PhantomParamData::default(),
|
_ctx: std::marker::PhantomData,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -544,7 +549,7 @@ impl<C: Compiler> CompileActor<C> {
|
||||||
pub struct CompileClient<Ctx> {
|
pub struct CompileClient<Ctx> {
|
||||||
intr_tx: mpsc::UnboundedSender<ExternalInterrupt<Ctx>>,
|
intr_tx: mpsc::UnboundedSender<ExternalInterrupt<Ctx>>,
|
||||||
|
|
||||||
_ctx: typst_ts_core::PhantomParamData<Ctx>,
|
_ctx: std::marker::PhantomData<fn(&mut Ctx)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<Ctx> Send for CompileClient<Ctx> {}
|
unsafe impl<Ctx> Send for CompileClient<Ctx> {}
|
||||||
|
@ -555,7 +560,7 @@ impl<Ctx> CompileClient<Ctx> {
|
||||||
let (intr_tx, _) = mpsc::unbounded_channel();
|
let (intr_tx, _) = mpsc::unbounded_channel();
|
||||||
Self {
|
Self {
|
||||||
intr_tx,
|
intr_tx,
|
||||||
_ctx: typst_ts_core::PhantomParamData::default(),
|
_ctx: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -627,7 +632,7 @@ pub struct DocToSrcJumpInfo {
|
||||||
// todo: remove constraint to CompilerWorld
|
// todo: remove constraint to CompilerWorld
|
||||||
impl<F: CompilerFeat, Ctx: Compiler<World = CompilerWorld<F>>> CompileClient<CompileActor<Ctx>>
|
impl<F: CompilerFeat, Ctx: Compiler<World = CompilerWorld<F>>> CompileClient<CompileActor<Ctx>>
|
||||||
where
|
where
|
||||||
Ctx::World: WorkspaceProvider,
|
Ctx::World: EntryManager,
|
||||||
{
|
{
|
||||||
/// fixme: character is 0-based, UTF-16 code unit.
|
/// fixme: character is 0-based, UTF-16 code unit.
|
||||||
/// We treat it as UTF-8 now.
|
/// We treat it as UTF-8 now.
|
||||||
|
@ -643,7 +648,7 @@ where
|
||||||
let world = this.compiler.world();
|
let world = this.compiler.world();
|
||||||
|
|
||||||
let relative_path = filepath
|
let relative_path = filepath
|
||||||
.strip_prefix(&this.compiler.world().workspace_root())
|
.strip_prefix(&this.compiler.world().workspace_root()?)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let source_id = TypstFileId::new(None, VirtualPath::new(relative_path));
|
let source_id = TypstFileId::new(None, VirtualPath::new(relative_path));
|
||||||
|
@ -666,7 +671,7 @@ where
|
||||||
|
|
||||||
let filepath = Path::new(&loc.filepath);
|
let filepath = Path::new(&loc.filepath);
|
||||||
let relative_path = filepath
|
let relative_path = filepath
|
||||||
.strip_prefix(&this.compiler.world().workspace_root())
|
.strip_prefix(&this.compiler.world().workspace_root()?)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let source_id = TypstFileId::new(None, VirtualPath::new(relative_path));
|
let source_id = TypstFileId::new(None, VirtualPath::new(relative_path));
|
||||||
|
|
|
@ -13,7 +13,7 @@ use tokio::sync::{
|
||||||
oneshot, watch,
|
oneshot, watch,
|
||||||
};
|
};
|
||||||
use typst::foundations::Smart;
|
use typst::foundations::Smart;
|
||||||
use typst_ts_core::{path::PathClean, ImmutPath, TypstDocument};
|
use typst_ts_core::{config::compiler::EntryState, path::PathClean, ImmutPath, TypstDocument};
|
||||||
|
|
||||||
use crate::ExportPdfMode;
|
use crate::ExportPdfMode;
|
||||||
|
|
||||||
|
@ -29,15 +29,13 @@ pub enum RenderActorRequest {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PdfPathVars {
|
pub struct PdfPathVars {
|
||||||
pub root: Option<ImmutPath>,
|
pub entry: EntryState,
|
||||||
pub path: Option<ImmutPath>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct PdfExportConfig {
|
pub struct PdfExportConfig {
|
||||||
pub substitute_pattern: String,
|
pub substitute_pattern: String,
|
||||||
pub root: Option<ImmutPath>,
|
pub entry: EntryState,
|
||||||
pub path: Option<ImmutPath>,
|
|
||||||
pub mode: ExportPdfMode,
|
pub mode: ExportPdfMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +44,7 @@ pub struct PdfExportActor {
|
||||||
document: watch::Receiver<Option<Arc<TypstDocument>>>,
|
document: watch::Receiver<Option<Arc<TypstDocument>>>,
|
||||||
|
|
||||||
pub substitute_pattern: String,
|
pub substitute_pattern: String,
|
||||||
pub root: Option<ImmutPath>,
|
pub entry: EntryState,
|
||||||
pub path: Option<ImmutPath>,
|
|
||||||
pub mode: ExportPdfMode,
|
pub mode: ExportPdfMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +58,7 @@ impl PdfExportActor {
|
||||||
render_rx,
|
render_rx,
|
||||||
document,
|
document,
|
||||||
substitute_pattern: config.substitute_pattern,
|
substitute_pattern: config.substitute_pattern,
|
||||||
root: config.root,
|
entry: config.entry,
|
||||||
path: config.path,
|
|
||||||
mode: config.mode,
|
mode: config.mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,13 +84,11 @@ impl PdfExportActor {
|
||||||
match req {
|
match req {
|
||||||
RenderActorRequest::ChangeConfig(cfg) => {
|
RenderActorRequest::ChangeConfig(cfg) => {
|
||||||
self.substitute_pattern = cfg.substitute_pattern;
|
self.substitute_pattern = cfg.substitute_pattern;
|
||||||
self.root = cfg.root;
|
self.entry = cfg.entry;
|
||||||
self.path = cfg.path;
|
|
||||||
self.mode = cfg.mode;
|
self.mode = cfg.mode;
|
||||||
}
|
}
|
||||||
RenderActorRequest::ChangeExportPath(cfg) => {
|
RenderActorRequest::ChangeExportPath(cfg) => {
|
||||||
self.root = cfg.root;
|
self.entry = cfg.entry;
|
||||||
self.path = cfg.path;
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let sender = match &req {
|
let sender = match &req {
|
||||||
|
@ -132,23 +126,36 @@ impl PdfExportActor {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// pub entry: EntryState,
|
||||||
|
let root = self.entry.root();
|
||||||
|
let main = self.entry.main();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"PdfRenderActor: check path {:?} with output directory {}",
|
"PdfRenderActor: check path {:?} and root {:?} with output directory {}",
|
||||||
self.path, self.substitute_pattern
|
main, root, self.substitute_pattern
|
||||||
);
|
);
|
||||||
if let Some((root, path)) = self.root.as_ref().zip(self.path.as_ref()) {
|
|
||||||
let should_do = matches!(req, RenderActorRequest::DoExport(..));
|
let root = root?;
|
||||||
let should_do = should_do || get_mode(self.mode) == eq_mode;
|
let main = main?;
|
||||||
let should_do = should_do || validate_document(&req, self.mode, &document);
|
|
||||||
if should_do {
|
// todo: package??
|
||||||
return match self.export_pdf(&document, root, path).await {
|
if main.package().is_some() {
|
||||||
Ok(pdf) => Some(pdf),
|
return None;
|
||||||
Err(err) => {
|
}
|
||||||
error!("PdfRenderActor: failed to export PDF: {err}", err = err);
|
|
||||||
None
|
let path = main.vpath().resolve(&root)?;
|
||||||
}
|
|
||||||
};
|
let should_do = matches!(req, RenderActorRequest::DoExport(..));
|
||||||
}
|
let should_do = should_do || get_mode(self.mode) == eq_mode;
|
||||||
|
let should_do = should_do || validate_document(&req, self.mode, &document);
|
||||||
|
if should_do {
|
||||||
|
return match self.export_pdf(&document, &root, &path).await {
|
||||||
|
Ok(pdf) => Some(pdf),
|
||||||
|
Err(err) => {
|
||||||
|
error!("PdfRenderActor: failed to export PDF: {err}", err = err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mode(mode: ExportPdfMode) -> ExportPdfMode {
|
fn get_mode(mode: ExportPdfMode) -> ExportPdfMode {
|
||||||
|
|
|
@ -1,49 +1,46 @@
|
||||||
//! The typst actors running compilations.
|
//! The typst actors running compilations.
|
||||||
|
|
||||||
use core::fmt;
|
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{error, info, trace};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use tinymist_query::{
|
use tinymist_query::{
|
||||||
analysis::{Analysis, AnalysisContext},
|
analysis::{Analysis, AnalysisContext, AnaylsisResources},
|
||||||
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, FoldRequestFeature,
|
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, FoldRequestFeature,
|
||||||
OnExportRequest, OnSaveExportRequest, PositionEncoding, StatefulRequest, SyntaxRequest,
|
OnExportRequest, OnSaveExportRequest, PositionEncoding, SemanticRequest, StatefulRequest,
|
||||||
VersionedDocument,
|
VersionedDocument,
|
||||||
};
|
};
|
||||||
use tokio::sync::{broadcast, mpsc, oneshot, watch};
|
use tokio::sync::{broadcast, mpsc, oneshot, watch};
|
||||||
use typst::{
|
use typst::{
|
||||||
diag::{SourceDiagnostic, SourceResult},
|
diag::{SourceDiagnostic, SourceResult},
|
||||||
syntax::VirtualPath,
|
|
||||||
util::Deferred,
|
util::Deferred,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
use typst_preview::{CompilationHandle, CompilationHandleImpl, CompileStatus};
|
use typst_preview::{CompilationHandle, CompilationHandleImpl, CompileStatus};
|
||||||
use typst_ts_compiler::{
|
use typst_ts_compiler::{
|
||||||
service::{
|
service::{CompileDriverImpl, CompileEnv, CompileMiddleware, Compiler, EntryManager, EnvWorld},
|
||||||
CompileDriver as CompileDriverInner, CompileExporter, CompileMiddleware, Compiler,
|
|
||||||
EnvWorld, WorkspaceProvider, WorldExporter,
|
|
||||||
},
|
|
||||||
vfs::notify::{FileChangeSet, MemoryEvent},
|
vfs::notify::{FileChangeSet, MemoryEvent},
|
||||||
TypstSystemWorld,
|
|
||||||
};
|
};
|
||||||
use typst_ts_core::{
|
use typst_ts_core::{
|
||||||
config::CompileOpts, error::prelude::*, typst::prelude::EcoVec, Error, ImmutPath,
|
config::compiler::EntryState, error::prelude::*, typst::prelude::EcoVec, Error, ImmutPath,
|
||||||
TypstDocument, TypstWorld,
|
TypstDocument, TypstWorld,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::compile::CompileClient as TsCompileClient;
|
use super::compile::CompileClient as TsCompileClient;
|
||||||
use super::{compile::CompileActor as CompileActorInner, render::PdfExportConfig};
|
use super::{compile::CompileActor as CompileActorInner, render::PdfExportConfig};
|
||||||
use crate::ConstConfig;
|
use crate::{actor::compile::EntryStateExt, ConstConfig};
|
||||||
use crate::{
|
use crate::{
|
||||||
actor::render::{PdfPathVars, RenderActorRequest},
|
actor::render::{PdfPathVars, RenderActorRequest},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
world::{LspWorld, LspWorldBuilder, SharedFontResolver},
|
||||||
|
Config,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "preview"))]
|
#[cfg(not(feature = "preview"))]
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
@ -60,34 +57,20 @@ pub trait CompilationHandle: Send + 'static {
|
||||||
fn notify_compile(&self, res: Result<Arc<TypstDocument>, CompileStatus>);
|
fn notify_compile(&self, res: Result<Arc<TypstDocument>, CompileStatus>);
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompileService<H> = CompileActorInner<Reporter<CompileExporter<CompileDriver>, H>>;
|
type CompileDriverInner = CompileDriverImpl<LspWorld>;
|
||||||
type CompileClient<H> = TsCompileClient<CompileService<H>>;
|
type CompileService = CompileActorInner<CompileDriver>;
|
||||||
|
type CompileClient = TsCompileClient<CompileService>;
|
||||||
|
|
||||||
type DiagnosticsSender = mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>;
|
type DiagnosticsSender = mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>;
|
||||||
|
|
||||||
pub enum OptsState {
|
|
||||||
Exact(CompileOpts),
|
|
||||||
Rootless(Box<dyn FnOnce(PathBuf) -> CompileOpts + Send + Sync>),
|
|
||||||
}
|
|
||||||
impl OptsState {
|
|
||||||
pub(crate) fn new(
|
|
||||||
root: Option<Arc<Path>>,
|
|
||||||
opts: impl FnOnce(PathBuf) -> CompileOpts + Send + Sync + 'static,
|
|
||||||
) -> OptsState {
|
|
||||||
match root {
|
|
||||||
Some(root) => OptsState::Exact(opts(root.as_ref().to_owned())),
|
|
||||||
None => OptsState::Rootless(Box::new(opts)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_server(
|
pub fn create_server(
|
||||||
diag_group: String,
|
diag_group: String,
|
||||||
|
config: &Config,
|
||||||
cfg: &ConstConfig,
|
cfg: &ConstConfig,
|
||||||
opts: OptsState,
|
// opts: OptsState,
|
||||||
root: Option<ImmutPath>,
|
font_resolver: Deferred<SharedFontResolver>,
|
||||||
entry: Option<ImmutPath>,
|
entry: EntryState,
|
||||||
snapshot: FileChangeSet,
|
snapshot: FileChangeSet,
|
||||||
diag_tx: DiagnosticsSender,
|
diag_tx: DiagnosticsSender,
|
||||||
doc_sender: watch::Sender<Option<Arc<TypstDocument>>>,
|
doc_sender: watch::Sender<Option<Arc<TypstDocument>>>,
|
||||||
|
@ -95,14 +78,6 @@ pub fn create_server(
|
||||||
) -> CompileActor {
|
) -> CompileActor {
|
||||||
let pos_encoding = cfg.position_encoding;
|
let pos_encoding = cfg.position_encoding;
|
||||||
|
|
||||||
info!(
|
|
||||||
"TypstActor: creating server for {} with opts(determined={:?})",
|
|
||||||
diag_group,
|
|
||||||
matches!(opts, OptsState::Exact(_))
|
|
||||||
);
|
|
||||||
|
|
||||||
let (root_tx, root_rx) = oneshot::channel();
|
|
||||||
|
|
||||||
let inner = Deferred::new({
|
let inner = Deferred::new({
|
||||||
let current_runtime = tokio::runtime::Handle::current();
|
let current_runtime = tokio::runtime::Handle::current();
|
||||||
let handler = CompileHandler {
|
let handler = CompileHandler {
|
||||||
|
@ -115,63 +90,31 @@ pub fn create_server(
|
||||||
let render_tx = render_tx.clone();
|
let render_tx = render_tx.clone();
|
||||||
|
|
||||||
move || {
|
move || {
|
||||||
let opts = match opts {
|
info!("TypstActor: creating server for {diag_group}");
|
||||||
OptsState::Exact(opts) => opts,
|
|
||||||
OptsState::Rootless(opts) => {
|
|
||||||
let root: ImmutPath = match utils::threaded_receive(root_rx) {
|
|
||||||
Ok(Some(root)) => root,
|
|
||||||
Ok(None) => {
|
|
||||||
error!("TypstActor: failed to receive root path: root is none");
|
|
||||||
return CompileClient::faked();
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("TypstActor: failed to receive root path: {:#}", err);
|
|
||||||
return CompileClient::faked();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
opts(root.as_ref().into())
|
let font_resolver = font_resolver.wait().clone();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let root: ImmutPath = opts.root_dir.as_path().into();
|
let world =
|
||||||
|
LspWorldBuilder::build(entry.clone(), font_resolver).expect("incorrect options");
|
||||||
info!(
|
let driver = CompileDriverInner::new(world);
|
||||||
"TypstActor: creating server for {} with arguments {:#?}",
|
let driver = CompileDriver {
|
||||||
diag_group,
|
inner: driver,
|
||||||
ShowOpts(&opts)
|
handler,
|
||||||
);
|
doc_sender,
|
||||||
|
render_tx: render_tx.clone(),
|
||||||
// todo: entry is PathBuf, which is inefficient
|
|
||||||
let compiler_driver = CompileDriver::new(opts, entry.clone(), handler);
|
|
||||||
let handler: CompileHandler = compiler_driver.handler.clone();
|
|
||||||
|
|
||||||
let ontyped_render_tx = render_tx.clone();
|
|
||||||
let driver = CompileExporter::new(compiler_driver).with_exporter(Box::new(
|
|
||||||
move |_w: &dyn TypstWorld, doc| {
|
|
||||||
let _ = doc_sender.send(Some(doc));
|
|
||||||
// todo: is it right that ignore zero broadcast receiver?
|
|
||||||
let _ = ontyped_render_tx.send(RenderActorRequest::OnTyped);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
));
|
|
||||||
let driver = Reporter {
|
|
||||||
diag_group: diag_group.clone(),
|
diag_group: diag_group.clone(),
|
||||||
position_encoding: pos_encoding,
|
position_encoding: pos_encoding,
|
||||||
diag_tx,
|
diag_tx,
|
||||||
inner: driver,
|
|
||||||
cb: handler.clone(),
|
|
||||||
};
|
};
|
||||||
let driver =
|
|
||||||
CompileActorInner::new(driver, root.clone(), entry.clone()).with_watch(true);
|
|
||||||
|
|
||||||
let (server, client) = driver.split();
|
let actor = CompileActorInner::new(driver, entry).with_watch(true);
|
||||||
|
let (server, client) = actor.split();
|
||||||
|
|
||||||
// We do send memory changes instead of initializing compiler with them.
|
// We do send memory changes instead of initializing compiler with them.
|
||||||
// This is because there are state recorded inside of the compiler actor, and we
|
// This is because there are state recorded inside of the compiler actor, and we
|
||||||
// must update them.
|
// must update them.
|
||||||
client.add_memory_changes(MemoryEvent::Update(snapshot));
|
client.add_memory_changes(MemoryEvent::Update(snapshot));
|
||||||
|
|
||||||
current_runtime.spawn(server.spawn());
|
current_runtime.spawn(server.spawn());
|
||||||
|
|
||||||
client
|
client
|
||||||
|
@ -180,8 +123,7 @@ pub fn create_server(
|
||||||
|
|
||||||
CompileActor::new(
|
CompileActor::new(
|
||||||
diag_group,
|
diag_group,
|
||||||
root_tx,
|
config.clone(),
|
||||||
root,
|
|
||||||
entry,
|
entry,
|
||||||
pos_encoding,
|
pos_encoding,
|
||||||
inner,
|
inner,
|
||||||
|
@ -189,20 +131,6 @@ pub fn create_server(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShowOpts<'a>(&'a CompileOpts);
|
|
||||||
|
|
||||||
impl<'a> fmt::Debug for ShowOpts<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("CompileOpts")
|
|
||||||
.field("root_dir", &self.0.root_dir)
|
|
||||||
.field("entry", &self.0.entry)
|
|
||||||
.field("inputs", &self.0.inputs)
|
|
||||||
.field("font_paths", &self.0.font_paths)
|
|
||||||
.field("no_system_fonts", &self.0.no_system_fonts)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! query_state {
|
macro_rules! query_state {
|
||||||
($self:ident, $method:ident, $req:expr) => {{
|
($self:ident, $method:ident, $req:expr) => {{
|
||||||
let res = $self.steal_state(move |w, doc| $req.request(w, doc));
|
let res = $self.steal_state(move |w, doc| $req.request(w, doc));
|
||||||
|
@ -247,7 +175,15 @@ impl CompilationHandle for CompileHandler {
|
||||||
|
|
||||||
pub struct CompileDriver {
|
pub struct CompileDriver {
|
||||||
inner: CompileDriverInner,
|
inner: CompileDriverInner,
|
||||||
|
#[allow(unused)]
|
||||||
handler: CompileHandler,
|
handler: CompileHandler,
|
||||||
|
|
||||||
|
doc_sender: watch::Sender<Option<Arc<TypstDocument>>>,
|
||||||
|
render_tx: broadcast::Sender<RenderActorRequest>,
|
||||||
|
|
||||||
|
diag_group: String,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
|
diag_tx: DiagnosticsSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompileMiddleware for CompileDriver {
|
impl CompileMiddleware for CompileDriver {
|
||||||
|
@ -260,90 +196,26 @@ impl CompileMiddleware for CompileDriver {
|
||||||
fn inner_mut(&mut self) -> &mut Self::Compiler {
|
fn inner_mut(&mut self) -> &mut Self::Compiler {
|
||||||
&mut self.inner
|
&mut self.inner
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl CompileDriver {
|
fn wrap_compile(&mut self, env: &mut CompileEnv) -> SourceResult<Arc<typst::model::Document>> {
|
||||||
pub fn new(opts: CompileOpts, entry: Option<ImmutPath>, handler: CompileHandler) -> Self {
|
|
||||||
let world = TypstSystemWorld::new(opts).expect("incorrect options");
|
|
||||||
let driver = CompileDriverInner::new(world);
|
|
||||||
|
|
||||||
let mut this = Self {
|
|
||||||
inner: driver,
|
|
||||||
handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(entry) = entry {
|
|
||||||
this.set_entry_file(entry.as_ref().to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: determine root
|
|
||||||
fn set_entry_file(&mut self, entry: PathBuf) {
|
|
||||||
// let candidates = self
|
|
||||||
// .current
|
|
||||||
// .iter()
|
|
||||||
// .filter_map(|(root, package)| Some((root,
|
|
||||||
// package.uri_to_vpath(uri).ok()?))) .inspect(|(package_root,
|
|
||||||
// path)| trace!(%package_root, ?path, %uri, "considering
|
|
||||||
// candidate for full id"));
|
|
||||||
|
|
||||||
// // Our candidates are projects containing a URI, so we expect to get
|
|
||||||
// a set of // subdirectories. The "best" is the "most
|
|
||||||
// specific", that is, the project that is a // subdirectory of
|
|
||||||
// the rest. This should have the longest length.
|
|
||||||
// let (best_package_root, best_path) =
|
|
||||||
// candidates.max_by_key(|(_, path)|
|
|
||||||
// path.as_rootless_path().components().count())?;
|
|
||||||
|
|
||||||
// let package_id = PackageId::new_current(best_package_root.clone());
|
|
||||||
// let full_file_id = FullFileId::new(package_id, best_path);
|
|
||||||
|
|
||||||
self.inner.set_entry_file(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Reporter<C, H> {
|
|
||||||
diag_group: String,
|
|
||||||
position_encoding: PositionEncoding,
|
|
||||||
diag_tx: DiagnosticsSender,
|
|
||||||
inner: C,
|
|
||||||
#[allow(unused)]
|
|
||||||
cb: H,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Compiler<World = TypstSystemWorld>, H> CompileMiddleware for Reporter<C, H>
|
|
||||||
where
|
|
||||||
H: CompilationHandle,
|
|
||||||
{
|
|
||||||
type Compiler = C;
|
|
||||||
|
|
||||||
fn inner(&self) -> &Self::Compiler {
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner_mut(&mut self) -> &mut Self::Compiler {
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_compile(
|
|
||||||
&mut self,
|
|
||||||
env: &mut typst_ts_compiler::service::CompileEnv,
|
|
||||||
) -> SourceResult<Arc<TypstDocument>> {
|
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
self.cb.status(CompileStatus::Compiling);
|
self.handler.status(CompileStatus::Compiling);
|
||||||
match self.inner_mut().compile(env) {
|
match self.inner_mut().compile(env) {
|
||||||
Ok(doc) => {
|
Ok(doc) => {
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
self.cb.notify_compile(Ok(doc.clone()));
|
self.handler.notify_compile(Ok(doc.clone()));
|
||||||
|
|
||||||
|
let _ = self.doc_sender.send(Some(doc.clone()));
|
||||||
|
// todo: is it right that ignore zero broadcast receiver?
|
||||||
|
let _ = self.render_tx.send(RenderActorRequest::OnTyped);
|
||||||
|
|
||||||
self.notify_diagnostics(EcoVec::new());
|
self.notify_diagnostics(EcoVec::new());
|
||||||
Ok(doc)
|
Ok(doc)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
self.cb.notify_compile(Err(CompileStatus::CompileError));
|
self.handler
|
||||||
|
.notify_compile(Err(CompileStatus::CompileError));
|
||||||
|
|
||||||
self.notify_diagnostics(err);
|
self.notify_diagnostics(err);
|
||||||
Err(EcoVec::new())
|
Err(EcoVec::new())
|
||||||
|
@ -352,13 +224,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Compiler + WorldExporter, H> WorldExporter for Reporter<C, H> {
|
impl CompileDriver {
|
||||||
fn export(&mut self, output: Arc<typst::model::Document>) -> SourceResult<()> {
|
|
||||||
self.inner.export(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Compiler<World = TypstSystemWorld>, H> Reporter<C, H> {
|
|
||||||
fn push_diagnostics(&mut self, diagnostics: Option<DiagnosticsMap>) {
|
fn push_diagnostics(&mut self, diagnostics: Option<DiagnosticsMap>) {
|
||||||
let err = self.diag_tx.send((self.diag_group.clone(), diagnostics));
|
let err = self.diag_tx.send((self.diag_group.clone(), diagnostics));
|
||||||
if let Err(err) = err {
|
if let Err(err) = err {
|
||||||
|
@ -370,8 +236,17 @@ impl<C: Compiler<World = TypstSystemWorld>, H> Reporter<C, H> {
|
||||||
trace!("notify diagnostics: {:#?}", diagnostics);
|
trace!("notify diagnostics: {:#?}", diagnostics);
|
||||||
|
|
||||||
// todo encoding
|
// todo encoding
|
||||||
|
let w = self.inner.world_mut();
|
||||||
|
// todo: root
|
||||||
|
let root = w.entry.root().clone().unwrap();
|
||||||
let diagnostics = tinymist_query::convert_diagnostics(
|
let diagnostics = tinymist_query::convert_diagnostics(
|
||||||
self.inner.world(),
|
&AnalysisContext::new(
|
||||||
|
&WrapWorld(w),
|
||||||
|
Analysis {
|
||||||
|
root,
|
||||||
|
position_encoding: self.position_encoding,
|
||||||
|
},
|
||||||
|
),
|
||||||
diagnostics.as_ref(),
|
diagnostics.as_ref(),
|
||||||
self.position_encoding,
|
self.position_encoding,
|
||||||
);
|
);
|
||||||
|
@ -379,8 +254,8 @@ impl<C: Compiler<World = TypstSystemWorld>, H> Reporter<C, H> {
|
||||||
// todo: better way to remove diagnostics
|
// todo: better way to remove diagnostics
|
||||||
// todo: check all errors in this file
|
// todo: check all errors in this file
|
||||||
|
|
||||||
let main = self.inner.world().main;
|
let detached = self.inner.world().entry.is_inactive();
|
||||||
let valid = main.is_some_and(|e| e.vpath() != &VirtualPath::new("detached.typ"));
|
let valid = !detached;
|
||||||
|
|
||||||
self.push_diagnostics(valid.then_some(diagnostics));
|
self.push_diagnostics(valid.then_some(diagnostics));
|
||||||
}
|
}
|
||||||
|
@ -389,10 +264,11 @@ impl<C: Compiler<World = TypstSystemWorld>, H> Reporter<C, H> {
|
||||||
pub struct CompileActor {
|
pub struct CompileActor {
|
||||||
diag_group: String,
|
diag_group: String,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
root_tx: Mutex<Option<oneshot::Sender<Option<ImmutPath>>>>,
|
config: Config,
|
||||||
root: OnceCell<Option<ImmutPath>>,
|
// root_tx: Mutex<Option<oneshot::Sender<Option<ImmutPath>>>>,
|
||||||
entry: Arc<Mutex<Option<ImmutPath>>>,
|
// root: OnceCell<Option<ImmutPath>>,
|
||||||
inner: Deferred<CompileClient<CompileHandler>>,
|
entry: Arc<Mutex<EntryState>>,
|
||||||
|
inner: Deferred<CompileClient>,
|
||||||
render_tx: broadcast::Sender<RenderActorRequest>,
|
render_tx: broadcast::Sender<RenderActorRequest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,20 +276,20 @@ impl CompileActor {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn new(
|
fn new(
|
||||||
diag_group: String,
|
diag_group: String,
|
||||||
root_tx: oneshot::Sender<Option<ImmutPath>>,
|
config: Config,
|
||||||
root: Option<ImmutPath>,
|
entry: EntryState,
|
||||||
entry: Option<ImmutPath>,
|
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
inner: Deferred<CompileClient<CompileHandler>>,
|
inner: Deferred<CompileClient>,
|
||||||
render_tx: broadcast::Sender<RenderActorRequest>,
|
render_tx: broadcast::Sender<RenderActorRequest>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
diag_group,
|
diag_group,
|
||||||
root_tx: Mutex::new(root.is_none().then_some(root_tx)),
|
config,
|
||||||
root: match root {
|
// root_tx: Mutex::new(root.is_none().then_some(root_tx)),
|
||||||
Some(root) => OnceCell::from(Some(root)),
|
// root: match root {
|
||||||
None => OnceCell::new(),
|
// Some(root) => OnceCell::from(Some(root)),
|
||||||
},
|
// None => OnceCell::new(),
|
||||||
|
// },
|
||||||
position_encoding,
|
position_encoding,
|
||||||
entry: Arc::new(Mutex::new(entry)),
|
entry: Arc::new(Mutex::new(entry)),
|
||||||
inner,
|
inner,
|
||||||
|
@ -421,14 +297,14 @@ impl CompileActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner(&self) -> &CompileClient<CompileHandler> {
|
fn inner(&self) -> &CompileClient {
|
||||||
self.inner.wait()
|
self.inner.wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Steal the compiler thread and run the given function.
|
/// Steal the compiler thread and run the given function.
|
||||||
pub fn steal<Ret: Send + 'static>(
|
pub fn steal<Ret: Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
f: impl FnOnce(&mut CompileService<CompileHandler>) -> Ret + Send + 'static,
|
f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static,
|
||||||
) -> ZResult<Ret> {
|
) -> ZResult<Ret> {
|
||||||
self.inner().steal(f)
|
self.inner().steal(f)
|
||||||
}
|
}
|
||||||
|
@ -436,15 +312,13 @@ impl CompileActor {
|
||||||
/// Steal the compiler thread and run the given function.
|
/// Steal the compiler thread and run the given function.
|
||||||
pub async fn steal_async<Ret: Send + 'static>(
|
pub async fn steal_async<Ret: Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
f: impl FnOnce(&mut CompileService<CompileHandler>, tokio::runtime::Handle) -> Ret
|
f: impl FnOnce(&mut CompileService, tokio::runtime::Handle) -> Ret + Send + 'static,
|
||||||
+ Send
|
|
||||||
+ 'static,
|
|
||||||
) -> ZResult<Ret> {
|
) -> ZResult<Ret> {
|
||||||
self.inner().steal_async(f).await
|
self.inner().steal_async(f).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settle(&self) {
|
pub fn settle(&self) {
|
||||||
let _ = self.change_entry(None, |_| None);
|
let _ = self.change_entry(None);
|
||||||
info!("TypstActor({}): settle requested", self.diag_group);
|
info!("TypstActor({}): settle requested", self.diag_group);
|
||||||
let res = self.inner().settle();
|
let res = self.inner().settle();
|
||||||
match res {
|
match res {
|
||||||
|
@ -458,86 +332,66 @@ impl CompileActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_entry(
|
pub fn change_entry(&self, path: Option<ImmutPath>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
path: Option<ImmutPath>,
|
|
||||||
resolve_root: impl FnOnce(Option<ImmutPath>) -> Option<ImmutPath>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if path.as_deref().is_some_and(|p| !p.is_absolute()) {
|
if path.as_deref().is_some_and(|p| !p.is_absolute()) {
|
||||||
return Err(error_once!("entry file must be absolute", path: path.unwrap().display()));
|
return Err(error_once!("entry file must be absolute", path: path.unwrap().display()));
|
||||||
}
|
}
|
||||||
{
|
|
||||||
let path = path.clone();
|
|
||||||
self.root.get_or_init(|| {
|
|
||||||
info!("TypstActor({}): delayed root resolution", self.diag_group);
|
|
||||||
let mut root_tx = self.root_tx.lock();
|
|
||||||
let root_tx = root_tx.take().unwrap();
|
|
||||||
let root = resolve_root(path);
|
|
||||||
let _ = root_tx.send(root.clone());
|
|
||||||
|
|
||||||
info!("TypstActor({}): resolved root: {root:?}", self.diag_group);
|
let next_entry = self.config.determine_entry(path);
|
||||||
root
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// todo: more robust rollback logic
|
// todo: more robust rollback logic
|
||||||
let entry = self.entry.clone();
|
let entry = self.entry.clone();
|
||||||
let should_change = {
|
let should_change = {
|
||||||
let mut entry = entry.lock();
|
let prev_entry = entry.lock();
|
||||||
let should_change = entry.as_deref() != path.as_deref();
|
let should_change = next_entry != *prev_entry;
|
||||||
let prev = entry.clone();
|
should_change.then(|| prev_entry.clone())
|
||||||
*entry = path.clone();
|
|
||||||
|
|
||||||
should_change.then_some(prev)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(prev) = should_change {
|
if let Some(prev) = should_change {
|
||||||
let next = path.clone();
|
let next = next_entry.clone();
|
||||||
|
|
||||||
debug!(
|
info!(
|
||||||
"the entry file of TypstActor({}) is changed to {next:?}",
|
"the entry file of TypstActor({}) is changing to {next:?}",
|
||||||
self.diag_group,
|
self.diag_group,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.render_tx
|
self.render_tx
|
||||||
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
|
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
|
||||||
root: self.root.get().cloned().flatten(),
|
entry: next.clone(),
|
||||||
path: next.clone(),
|
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
let res = self.steal(move |compiler| {
|
let res = self.steal(move |compiler| {
|
||||||
let root = compiler.compiler.world().workspace_root();
|
compiler.change_entry(next.clone());
|
||||||
if path.as_ref().is_some_and(|p| !p.starts_with(&root)) {
|
|
||||||
warn!("entry file is not in workspace root {path:?}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(path) = &path {
|
let next_is_inactive = next.is_inactive();
|
||||||
let driver = &mut compiler.compiler.compiler.inner.compiler;
|
let res = compiler.compiler.world_mut().mutate_entry(next);
|
||||||
driver.set_entry_file(path.as_ref().to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
compiler.change_entry(path.clone());
|
if next_is_inactive {
|
||||||
|
|
||||||
if path.is_none() {
|
|
||||||
info!("TypstActor: removing diag");
|
info!("TypstActor: removing diag");
|
||||||
compiler.compiler.compiler.push_diagnostics(None);
|
compiler.compiler.compiler.push_diagnostics(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.map(|_| ())
|
||||||
|
.map_err(|err| error_once!("failed to change entry", err: format!("{err:?}")))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let res = match res {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(res) => Err(res),
|
||||||
|
};
|
||||||
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
self.render_tx
|
self.render_tx
|
||||||
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
|
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
|
||||||
root: self.root.get().cloned().flatten(),
|
entry: prev.clone(),
|
||||||
path: prev.clone(),
|
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut entry = entry.lock();
|
let mut entry = entry.lock();
|
||||||
// todo: the rollback is actually not atomic
|
// todo: the rollback is actually not atomic
|
||||||
if *entry == next {
|
if *entry == next_entry {
|
||||||
*entry = prev;
|
*entry = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,59 +406,45 @@ impl CompileActor {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_memory_changes(
|
pub fn add_memory_changes(&self, event: MemoryEvent) {
|
||||||
&self,
|
|
||||||
event: MemoryEvent,
|
|
||||||
resolve_root: impl FnOnce(Option<ImmutPath>) -> Option<ImmutPath>,
|
|
||||||
) {
|
|
||||||
self.root.get_or_init(|| {
|
|
||||||
info!(
|
|
||||||
"TypstActor({}): delayed root resolution on memory change events",
|
|
||||||
self.diag_group
|
|
||||||
);
|
|
||||||
|
|
||||||
// determine path by event
|
|
||||||
let entry = {
|
|
||||||
let entry = self.entry.lock().clone();
|
|
||||||
|
|
||||||
entry.or_else(|| match &event {
|
|
||||||
MemoryEvent::Sync(changeset) | MemoryEvent::Update(changeset) => changeset
|
|
||||||
.inserts
|
|
||||||
.first()
|
|
||||||
.map(|e| e.0.clone())
|
|
||||||
.or_else(|| changeset.removes.first().cloned()),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut root_tx = self.root_tx.lock();
|
|
||||||
let root_tx = root_tx.take().unwrap();
|
|
||||||
let root = resolve_root(entry);
|
|
||||||
let _ = root_tx.send(root.clone());
|
|
||||||
|
|
||||||
info!("TypstActor({}): resolved root: {root:?}", self.diag_group);
|
|
||||||
root
|
|
||||||
});
|
|
||||||
|
|
||||||
self.inner.wait().add_memory_changes(event);
|
self.inner.wait().add_memory_changes(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn change_export_pdf(&self, config: PdfExportConfig) {
|
pub(crate) fn change_export_pdf(&self, config: PdfExportConfig) {
|
||||||
let entry = self.entry.lock();
|
let entry = self.entry.lock().clone();
|
||||||
let path = entry
|
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.clone().with_extension("pdf").into());
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.render_tx
|
.render_tx
|
||||||
.send(RenderActorRequest::ChangeConfig(PdfExportConfig {
|
.send(RenderActorRequest::ChangeConfig(PdfExportConfig {
|
||||||
substitute_pattern: config.substitute_pattern,
|
substitute_pattern: config.substitute_pattern,
|
||||||
root: self.root.get().cloned().flatten(),
|
// root: self.root.get().cloned().flatten(),
|
||||||
path,
|
entry,
|
||||||
mode: config.mode,
|
mode: config.mode,
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WrapWorld<'a>(&'a mut LspWorld);
|
||||||
|
|
||||||
|
impl<'a> AnaylsisResources for WrapWorld<'a> {
|
||||||
|
fn world(&self) -> &dyn typst::World {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(
|
||||||
|
&self,
|
||||||
|
spec: &typst_ts_core::package::PackageSpec,
|
||||||
|
) -> Result<Arc<Path>, typst::diag::PackageError> {
|
||||||
|
use typst_ts_compiler::package::Registry;
|
||||||
|
self.0.registry.resolve(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_dependencies(&self, f: &mut dyn FnMut(&ImmutPath, typst_ts_compiler::Time)) {
|
||||||
|
use typst_ts_compiler::NotifyApi;
|
||||||
|
self.0.iter_dependencies(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CompileActor {
|
impl CompileActor {
|
||||||
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
|
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
|
||||||
use CompilerQueryRequest::*;
|
use CompilerQueryRequest::*;
|
||||||
|
@ -669,14 +509,17 @@ impl CompileActor {
|
||||||
let enc = self.position_encoding;
|
let enc = self.position_encoding;
|
||||||
|
|
||||||
self.steal(move |compiler| {
|
self.steal(move |compiler| {
|
||||||
// todo: record analysis
|
|
||||||
let doc = compiler.success_doc();
|
let doc = compiler.success_doc();
|
||||||
let w = compiler.compiler.world_mut();
|
let w = compiler.compiler.world_mut();
|
||||||
|
|
||||||
let Some(main) = w.main else {
|
let Some(main) = w.main_id() else {
|
||||||
log::error!("TypstActor: main file is not set");
|
log::error!("TypstActor: main file is not set");
|
||||||
return Err(anyhow!("main file is not set"));
|
return Err(anyhow!("main file is not set"));
|
||||||
};
|
};
|
||||||
|
let Some(root) = w.entry.root() else {
|
||||||
|
log::error!("TypstActor: root is not set");
|
||||||
|
return Err(anyhow!("root is not set"));
|
||||||
|
};
|
||||||
w.source(main).map_err(|err| {
|
w.source(main).map_err(|err| {
|
||||||
log::info!("TypstActor: failed to prepare main file: {:?}", err);
|
log::info!("TypstActor: failed to prepare main file: {:?}", err);
|
||||||
anyhow!("failed to get source: {err}")
|
anyhow!("failed to get source: {err}")
|
||||||
|
@ -686,11 +529,12 @@ impl CompileActor {
|
||||||
anyhow!("failed to prepare env")
|
anyhow!("failed to prepare env")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let w = WrapWorld(w);
|
||||||
Ok(f(
|
Ok(f(
|
||||||
&mut AnalysisContext::new(
|
&mut AnalysisContext::new(
|
||||||
w,
|
&w,
|
||||||
Analysis {
|
Analysis {
|
||||||
root: w.root.clone(),
|
root,
|
||||||
position_encoding: enc,
|
position_encoding: enc,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -704,15 +548,39 @@ impl CompileActor {
|
||||||
f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static,
|
f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static,
|
||||||
) -> anyhow::Result<T> {
|
) -> anyhow::Result<T> {
|
||||||
let enc = self.position_encoding;
|
let enc = self.position_encoding;
|
||||||
|
// let opts = match opts {
|
||||||
|
// OptsState::Exact(opts) => opts,
|
||||||
|
// OptsState::Rootless(opts) => {
|
||||||
|
// let root: ImmutPath = match utils::threaded_receive(root_rx) {
|
||||||
|
// Ok(Some(root)) => root,
|
||||||
|
// Ok(None) => {
|
||||||
|
// error!("TypstActor: failed to receive root path: root is
|
||||||
|
// none"); return CompileClient::faked();
|
||||||
|
// }
|
||||||
|
// Err(err) => {
|
||||||
|
// error!("TypstActor: failed to receive root path: {:#}", err);
|
||||||
|
// return CompileClient::faked();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// opts(root.as_ref().into())
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// mut opts: CompileOnceOpts,
|
||||||
|
// let inputs = std::mem::take(&mut opts.inputs);
|
||||||
|
// w.set_inputs(Arc::new(Prehashed::new(inputs)));
|
||||||
|
|
||||||
self.steal(move |compiler| {
|
self.steal(move |compiler| {
|
||||||
// todo: record analysis
|
|
||||||
let w = compiler.compiler.world_mut();
|
let w = compiler.compiler.world_mut();
|
||||||
|
|
||||||
let Some(main) = w.main else {
|
let Some(main) = w.main_id() else {
|
||||||
log::error!("TypstActor: main file is not set");
|
log::error!("TypstActor: main file is not set");
|
||||||
return Err(anyhow!("main file is not set"));
|
return Err(anyhow!("main file is not set"));
|
||||||
};
|
};
|
||||||
|
let Some(root) = w.entry.root() else {
|
||||||
|
log::error!("TypstActor: root is not set");
|
||||||
|
return Err(anyhow!("root is not set"));
|
||||||
|
};
|
||||||
w.source(main).map_err(|err| {
|
w.source(main).map_err(|err| {
|
||||||
log::info!("TypstActor: failed to prepare main file: {:?}", err);
|
log::info!("TypstActor: failed to prepare main file: {:?}", err);
|
||||||
anyhow!("failed to get source: {err}")
|
anyhow!("failed to get source: {err}")
|
||||||
|
@ -722,10 +590,11 @@ impl CompileActor {
|
||||||
anyhow!("failed to prepare env")
|
anyhow!("failed to prepare env")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let w = WrapWorld(w);
|
||||||
Ok(f(&mut AnalysisContext::new(
|
Ok(f(&mut AnalysisContext::new(
|
||||||
w,
|
&w,
|
||||||
Analysis {
|
Analysis {
|
||||||
root: w.root.clone(),
|
root,
|
||||||
position_encoding: enc,
|
position_encoding: enc,
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
@ -11,10 +12,18 @@ use serde_json::{Map, Value as JsonValue};
|
||||||
use tinymist_query::{get_semantic_tokens_options, PositionEncoding};
|
use tinymist_query::{get_semantic_tokens_options, PositionEncoding};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use typst::foundations::IntoValue;
|
use typst::foundations::IntoValue;
|
||||||
use typst_ts_core::{config::CompileOpts, ImmutPath, TypstDict};
|
use typst::syntax::VirtualPath;
|
||||||
|
use typst::util::Deferred;
|
||||||
|
use typst_ts_core::config::compiler::EntryState;
|
||||||
|
use typst_ts_core::error::prelude::*;
|
||||||
|
use typst_ts_core::{ImmutPath, TypstDict, TypstFileId as FileId};
|
||||||
|
|
||||||
use crate::actor::cluster::CompileClusterActor;
|
use crate::actor::cluster::CompileClusterActor;
|
||||||
use crate::{invalid_params, LspHost, LspResult, TypstLanguageServer, TypstLanguageServerArgs};
|
use crate::world::{CompileOpts, SharedFontResolver};
|
||||||
|
use crate::{
|
||||||
|
invalid_params, CompileFontOpts, LspHost, LspResult, TypstLanguageServer,
|
||||||
|
TypstLanguageServerArgs,
|
||||||
|
};
|
||||||
|
|
||||||
// todo: svelte-language-server responds to a Goto Definition request with
|
// todo: svelte-language-server responds to a Goto Definition request with
|
||||||
// LocationLink[] even if the client does not report the
|
// LocationLink[] even if the client does not report the
|
||||||
|
@ -283,6 +292,18 @@ impl Config {
|
||||||
return Some(path.as_path().into());
|
return Some(path.as_path().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(extras) = &self.typst_extra_args {
|
||||||
|
// todo: inputs
|
||||||
|
// if let Some(inputs) = extras.inputs.as_ref() {
|
||||||
|
// if opts.inputs.is_empty() {
|
||||||
|
// opts.inputs = inputs.clone();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if let Some(root) = &extras.root_dir {
|
||||||
|
return Some(root.as_path().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path) = &self
|
if let Some(path) = &self
|
||||||
.typst_extra_args
|
.typst_extra_args
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -314,6 +335,34 @@ impl Config {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn determine_entry(&self, entry: Option<ImmutPath>) -> EntryState {
|
||||||
|
// todo: don't ignore entry from typst_extra_args
|
||||||
|
// entry: command.input,
|
||||||
|
let root_dir = self.determine_root(entry.as_ref());
|
||||||
|
|
||||||
|
let entry = match (entry, root_dir) {
|
||||||
|
(Some(entry), Some(root)) => match entry.strip_prefix(&root) {
|
||||||
|
Ok(stripped) => Some(EntryState::new_rooted(
|
||||||
|
root,
|
||||||
|
Some(FileId::new(None, VirtualPath::new(stripped))),
|
||||||
|
)),
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("Entry is not in root directory: err {err:?}: entry: {entry:?}, root: {root:?}");
|
||||||
|
EntryState::new_rootless(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Some(entry), None) => EntryState::new_rootless(entry),
|
||||||
|
(None, Some(root)) => Some(EntryState::new_workspace(root)),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.unwrap_or_else(|| match self.determine_root(None) {
|
||||||
|
Some(root) => EntryState::new_workspace(root),
|
||||||
|
// todo
|
||||||
|
None => EntryState::new_detached(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn validate(&self) -> anyhow::Result<()> {
|
fn validate(&self) -> anyhow::Result<()> {
|
||||||
if let Some(root) = &self.root_path {
|
if let Some(root) = &self.root_path {
|
||||||
if !root.is_absolute() {
|
if !root.is_absolute() {
|
||||||
|
@ -436,7 +485,7 @@ impl Init {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Errors if the configuration could not be updated.
|
/// Errors if the configuration could not be updated.
|
||||||
pub fn initialize(
|
pub fn initialize(
|
||||||
self,
|
mut self,
|
||||||
params: InitializeParams,
|
params: InitializeParams,
|
||||||
) -> (TypstLanguageServer, LspResult<InitializeResult>) {
|
) -> (TypstLanguageServer, LspResult<InitializeResult>) {
|
||||||
// self.tracing_init();
|
// self.tracing_init();
|
||||||
|
@ -447,44 +496,59 @@ impl Init {
|
||||||
"initialized with const_config {const_config:?}",
|
"initialized with const_config {const_config:?}",
|
||||||
const_config = cc
|
const_config = cc
|
||||||
);
|
);
|
||||||
|
let mut config = Config {
|
||||||
|
roots: match params.workspace_folders.as_ref() {
|
||||||
|
Some(roots) => roots
|
||||||
|
.iter()
|
||||||
|
.map(|root| &root.uri)
|
||||||
|
.map(Url::to_file_path)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.unwrap(),
|
||||||
|
#[allow(deprecated)] // `params.root_path` is marked as deprecated
|
||||||
|
None => params
|
||||||
|
.root_uri
|
||||||
|
.as_ref()
|
||||||
|
.map(|uri| uri.to_file_path().unwrap())
|
||||||
|
.or_else(|| params.root_path.clone().map(PathBuf::from))
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
let res = match ¶ms.initialization_options {
|
||||||
|
Some(init) => config
|
||||||
|
.update(init)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
.map_err(invalid_params),
|
||||||
|
None => Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
let mut config = Config::default();
|
// prepare fonts
|
||||||
|
// todo: on font resolving failure, downgrade to a fake font book
|
||||||
|
let font = {
|
||||||
|
let opts = std::mem::take(&mut self.compile_opts.font);
|
||||||
|
if opts.font_paths.is_empty() {
|
||||||
|
if let Some(font_paths) = config.typst_extra_args.as_ref().map(|x| &x.font_paths) {
|
||||||
|
self.compile_opts.font.font_paths = font_paths.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deferred::new(|| create_font_book(opts).expect("failed to create font book"))
|
||||||
|
};
|
||||||
|
|
||||||
// Bootstrap server
|
// Bootstrap server
|
||||||
let (diag_tx, diag_rx) = mpsc::unbounded_channel();
|
let (diag_tx, diag_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let mut service = TypstLanguageServer::new(TypstLanguageServerArgs {
|
let mut service = TypstLanguageServer::new(TypstLanguageServerArgs {
|
||||||
client: self.host.clone(),
|
client: self.host.clone(),
|
||||||
compile_opts: self.compile_opts,
|
compile_opts: self.compile_opts.once,
|
||||||
const_config: cc.clone(),
|
const_config: cc.clone(),
|
||||||
diag_tx,
|
diag_tx,
|
||||||
|
font,
|
||||||
});
|
});
|
||||||
|
|
||||||
config.roots = match params.workspace_folders.as_ref() {
|
if let Err(err) = res {
|
||||||
Some(roots) => roots
|
return (service, Err(err));
|
||||||
.iter()
|
|
||||||
.map(|root| &root.uri)
|
|
||||||
.map(Url::to_file_path)
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
.unwrap(),
|
|
||||||
#[allow(deprecated)] // `params.root_path` is marked as deprecated
|
|
||||||
None => params
|
|
||||||
.root_uri
|
|
||||||
.as_ref()
|
|
||||||
.map(|uri| uri.to_file_path().unwrap())
|
|
||||||
.or_else(|| params.root_path.clone().map(PathBuf::from))
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
if let Some(init) = ¶ms.initialization_options {
|
|
||||||
if let Err(err) = config
|
|
||||||
.update(init)
|
|
||||||
.as_ref()
|
|
||||||
.map_err(ToString::to_string)
|
|
||||||
.map_err(invalid_params)
|
|
||||||
{
|
|
||||||
return (service, Err(err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("initialized with config {config:?}", config = config);
|
info!("initialized with config {config:?}", config = config);
|
||||||
|
@ -498,7 +562,7 @@ impl Init {
|
||||||
published_primary: false,
|
published_primary: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let primary = service.server("primary".to_owned(), None);
|
let primary = service.server("primary".to_owned(), service.config.determine_entry(None));
|
||||||
if service.primary.is_some() {
|
if service.primary.is_some() {
|
||||||
panic!("primary already initialized");
|
panic!("primary already initialized");
|
||||||
}
|
}
|
||||||
|
@ -588,6 +652,14 @@ impl Init {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_font_book(opts: CompileFontOpts) -> ZResult<SharedFontResolver> {
|
||||||
|
let res = crate::world::LspWorldBuilder::resolve_fonts(opts)?;
|
||||||
|
Ok(SharedFontResolver {
|
||||||
|
inner: Arc::new(res),
|
||||||
|
// inner: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -34,6 +34,7 @@ mod task;
|
||||||
mod tools;
|
mod tools;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
mod world;
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -66,10 +67,13 @@ use tinymist_query::{
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use typst::diag::StrResult;
|
use typst::diag::StrResult;
|
||||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
||||||
|
use typst::util::Deferred;
|
||||||
use typst_ts_compiler::service::Compiler;
|
use typst_ts_compiler::service::Compiler;
|
||||||
use typst_ts_core::{config::CompileOpts, error::prelude::*, ImmutPath};
|
use typst_ts_core::{error::prelude::*, ImmutPath};
|
||||||
|
|
||||||
pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>;
|
pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>;
|
||||||
|
use world::SharedFontResolver;
|
||||||
|
pub use world::{CompileFontOpts, CompileOnceOpts, CompileOpts};
|
||||||
|
|
||||||
use crate::actor::render::PdfExportConfig;
|
use crate::actor::render::PdfExportConfig;
|
||||||
use crate::init::*;
|
use crate::init::*;
|
||||||
|
@ -285,9 +289,10 @@ fn as_path_pos(inp: TextDocumentPositionParams) -> (PathBuf, Position) {
|
||||||
|
|
||||||
pub struct TypstLanguageServerArgs {
|
pub struct TypstLanguageServerArgs {
|
||||||
pub client: LspHost,
|
pub client: LspHost,
|
||||||
pub compile_opts: CompileOpts,
|
pub compile_opts: CompileOnceOpts,
|
||||||
pub const_config: ConstConfig,
|
pub const_config: ConstConfig,
|
||||||
pub diag_tx: mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>,
|
pub diag_tx: mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>,
|
||||||
|
pub font: Deferred<SharedFontResolver>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The object providing the language server functionality.
|
/// The object providing the language server functionality.
|
||||||
|
@ -307,7 +312,7 @@ pub struct TypstLanguageServer {
|
||||||
/// For example, the position encoding.
|
/// For example, the position encoding.
|
||||||
pub const_config: ConstConfig,
|
pub const_config: ConstConfig,
|
||||||
/// The default opts for the compiler.
|
/// The default opts for the compiler.
|
||||||
pub compile_opts: CompileOpts,
|
pub compile_opts: CompileOnceOpts,
|
||||||
|
|
||||||
// Command maps
|
// Command maps
|
||||||
/// Extra commands provided with `textDocument/executeCommand`.
|
/// Extra commands provided with `textDocument/executeCommand`.
|
||||||
|
@ -322,6 +327,7 @@ pub struct TypstLanguageServer {
|
||||||
primary: Option<CompileActor>,
|
primary: Option<CompileActor>,
|
||||||
pinning: bool,
|
pinning: bool,
|
||||||
main: Option<CompileActor>,
|
main: Option<CompileActor>,
|
||||||
|
font: Deferred<SharedFontResolver>,
|
||||||
tokens_ctx: SemanticTokenContext,
|
tokens_ctx: SemanticTokenContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +356,7 @@ impl TypstLanguageServer {
|
||||||
primary: None,
|
primary: None,
|
||||||
pinning: false,
|
pinning: false,
|
||||||
main: None,
|
main: None,
|
||||||
|
font: args.font,
|
||||||
tokens_ctx,
|
tokens_ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,7 @@ use log::{info, trace, warn};
|
||||||
use lsp_types::{InitializeParams, InitializedParams};
|
use lsp_types::{InitializeParams, InitializedParams};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tinymist::{init::Init, transport::io_transport, LspHost};
|
use tinymist::{init::Init, transport::io_transport, CompileFontOpts, CompileOpts, LspHost};
|
||||||
use typst_ts_core::config::CompileOpts;
|
|
||||||
|
|
||||||
use crate::args::CliArguments;
|
use crate::args::CliArguments;
|
||||||
|
|
||||||
|
@ -126,8 +125,11 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let (mut service, initialize_result) = Init {
|
let (mut service, initialize_result) = Init {
|
||||||
host: host.clone(),
|
host: host.clone(),
|
||||||
compile_opts: CompileOpts {
|
compile_opts: CompileOpts {
|
||||||
font_paths: args.font_paths,
|
font: CompileFontOpts {
|
||||||
no_system_fonts: args.no_system_fonts,
|
font_paths: args.font_paths,
|
||||||
|
no_system_fonts: args.no_system_fonts,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ use std::path::PathBuf;
|
||||||
use ::typst::{diag::FileResult, syntax::Source};
|
use ::typst::{diag::FileResult, syntax::Source};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lsp_types::TextDocumentContentChangeEvent;
|
use lsp_types::TextDocumentContentChangeEvent;
|
||||||
use tinymist_query::{lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, PositionEncoding};
|
use tinymist_query::{
|
||||||
|
lsp_to_typst, CompilerQueryRequest, CompilerQueryResponse, PositionEncoding, SyntaxRequest,
|
||||||
|
};
|
||||||
use typst_ts_compiler::{
|
use typst_ts_compiler::{
|
||||||
vfs::notify::{FileChangeSet, MemoryEvent},
|
vfs::notify::{FileChangeSet, MemoryEvent},
|
||||||
Time,
|
Time,
|
||||||
|
@ -22,16 +24,18 @@ pub struct MemoryFileMeta {
|
||||||
|
|
||||||
impl TypstLanguageServer {
|
impl TypstLanguageServer {
|
||||||
/// Updates the main entry
|
/// Updates the main entry
|
||||||
// todo: the changed entry may be out of root directory
|
|
||||||
pub fn update_main_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
|
pub fn update_main_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
|
||||||
self.pinning = new_entry.is_some();
|
self.pinning = new_entry.is_some();
|
||||||
match (new_entry, self.main.is_some()) {
|
match (new_entry, self.main.is_some()) {
|
||||||
(Some(new_entry), true) => {
|
(Some(new_entry), true) => {
|
||||||
let main = self.main.as_mut().unwrap();
|
let main = self.main.as_mut().unwrap();
|
||||||
main.change_entry(Some(new_entry), |e| self.config.determine_root(e.as_ref()))?;
|
main.change_entry(Some(new_entry))?;
|
||||||
}
|
}
|
||||||
(Some(new_entry), false) => {
|
(Some(new_entry), false) => {
|
||||||
let main_node = self.server("main".to_owned(), Some(new_entry));
|
let main_node = self.server(
|
||||||
|
"main".to_owned(),
|
||||||
|
self.config.determine_entry(Some(new_entry)),
|
||||||
|
);
|
||||||
|
|
||||||
self.main = Some(main_node);
|
self.main = Some(main_node);
|
||||||
}
|
}
|
||||||
|
@ -47,11 +51,7 @@ impl TypstLanguageServer {
|
||||||
|
|
||||||
/// Updates the primary (focusing) entry
|
/// Updates the primary (focusing) entry
|
||||||
pub fn update_primary_entry(&self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
|
pub fn update_primary_entry(&self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
|
||||||
self.primary().change_entry(new_entry.clone(), |e| {
|
self.primary().change_entry(new_entry.clone())
|
||||||
self.config.determine_root(e.as_ref())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +62,7 @@ impl TypstLanguageServer {
|
||||||
let clients_to_notify = (primary.into_iter()).chain(main);
|
let clients_to_notify = (primary.into_iter()).chain(main);
|
||||||
|
|
||||||
for client in clients_to_notify {
|
for client in clients_to_notify {
|
||||||
client.add_memory_changes(MemoryEvent::Update(files.clone()), |e| {
|
client.add_memory_changes(MemoryEvent::Update(files.clone()));
|
||||||
self.config.determine_root(e.as_ref())
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -169,7 +167,7 @@ macro_rules! query_source {
|
||||||
let source = snapshot.content.clone();
|
let source = snapshot.content.clone();
|
||||||
|
|
||||||
let enc = $self.const_config.position_encoding;
|
let enc = $self.const_config.position_encoding;
|
||||||
let res = $req.request(source, enc);
|
let res = $req.request(&source, enc);
|
||||||
Ok(CompilerQueryResponse::$method(res))
|
Ok(CompilerQueryResponse::$method(res))
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -197,20 +195,16 @@ impl TypstLanguageServer {
|
||||||
SelectionRange(req) => query_source!(self, SelectionRange, req),
|
SelectionRange(req) => query_source!(self, SelectionRange, req),
|
||||||
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req),
|
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req),
|
||||||
_ => {
|
_ => {
|
||||||
let query_target = match self.main.as_ref() {
|
match self.main.as_ref() {
|
||||||
Some(main) if self.pinning => main,
|
Some(main) if self.pinning => main.query(query),
|
||||||
Some(..) | None => {
|
Some(..) | None => {
|
||||||
// todo: race condition, we need atomic primary query
|
// todo: race condition, we need atomic primary query
|
||||||
if let Some(path) = query.associated_path() {
|
if let Some(path) = query.associated_path() {
|
||||||
self.primary().change_entry(Some(path.into()), |e| {
|
self.primary().change_entry(Some(path.into()))?;
|
||||||
self.config.determine_root(e.as_ref())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
self.primary()
|
self.primary().query(query)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
query_target.query(query)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ use typst::diag::{bail, eco_format, FileError, FileResult, StrResult};
|
||||||
use typst::syntax::package::{PackageManifest, PackageSpec, TemplateInfo};
|
use typst::syntax::package::{PackageManifest, PackageSpec, TemplateInfo};
|
||||||
use typst::syntax::VirtualPath;
|
use typst::syntax::VirtualPath;
|
||||||
use typst::World;
|
use typst::World;
|
||||||
use typst_ts_compiler::TypstSystemWorld;
|
|
||||||
use typst_ts_core::{Bytes, ImmutPath, TypstFileId};
|
use typst_ts_core::{Bytes, ImmutPath, TypstFileId};
|
||||||
|
|
||||||
|
use crate::world::LspWorld;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TemplateSource {
|
pub enum TemplateSource {
|
||||||
Package(PackageSpec),
|
Package(PackageSpec),
|
||||||
|
@ -19,7 +20,7 @@ pub struct InitTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute an initialization command.
|
/// Execute an initialization command.
|
||||||
pub fn get_entry(world: &TypstSystemWorld, tmpl: TemplateSource) -> StrResult<Bytes> {
|
pub fn get_entry(world: &LspWorld, tmpl: TemplateSource) -> StrResult<Bytes> {
|
||||||
let TemplateSource::Package(spec) = tmpl;
|
let TemplateSource::Package(spec) = tmpl;
|
||||||
|
|
||||||
let toml_id = TypstFileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
|
let toml_id = TypstFileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
|
||||||
|
@ -41,7 +42,7 @@ pub fn get_entry(world: &TypstSystemWorld, tmpl: TemplateSource) -> StrResult<By
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute an initialization command.
|
/// Execute an initialization command.
|
||||||
pub fn init(world: &TypstSystemWorld, task: InitTask) -> StrResult<PathBuf> {
|
pub fn init(world: &LspWorld, task: InitTask) -> StrResult<PathBuf> {
|
||||||
let TemplateSource::Package(spec) = task.tmpl;
|
let TemplateSource::Package(spec) = task.tmpl;
|
||||||
let project_dir = task
|
let project_dir = task
|
||||||
.dir
|
.dir
|
||||||
|
@ -71,7 +72,7 @@ pub fn init(world: &TypstSystemWorld, task: InitTask) -> StrResult<PathBuf> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the manifest of the package located at `package_path`.
|
/// Parses the manifest of the package located at `package_path`.
|
||||||
fn parse_manifest(world: &TypstSystemWorld, toml_id: TypstFileId) -> StrResult<PackageManifest> {
|
fn parse_manifest(world: &LspWorld, toml_id: TypstFileId) -> StrResult<PackageManifest> {
|
||||||
let toml_data = world
|
let toml_data = world
|
||||||
.file(toml_id)
|
.file(toml_id)
|
||||||
.map_err(|err| eco_format!("failed to read package manifest ({})", err))?;
|
.map_err(|err| eco_format!("failed to read package manifest ({})", err))?;
|
||||||
|
@ -86,7 +87,7 @@ fn parse_manifest(world: &TypstSystemWorld, toml_id: TypstFileId) -> StrResult<P
|
||||||
/// Creates the project directory with the template's contents and returns the
|
/// Creates the project directory with the template's contents and returns the
|
||||||
/// path at which it was created.
|
/// path at which it was created.
|
||||||
fn scaffold_project(
|
fn scaffold_project(
|
||||||
world: &TypstSystemWorld,
|
world: &LspWorld,
|
||||||
tmpl_info: &TemplateInfo,
|
tmpl_info: &TemplateInfo,
|
||||||
toml_id: TypstFileId,
|
toml_id: TypstFileId,
|
||||||
project_dir: &Path,
|
project_dir: &Path,
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
use typst::diag::{eco_format, StrResult};
|
use typst::diag::{eco_format, StrResult};
|
||||||
use typst::syntax::package::{PackageVersion, VersionlessPackageSpec};
|
use typst::syntax::package::{PackageVersion, VersionlessPackageSpec};
|
||||||
use typst_ts_compiler::package::Registry;
|
use typst_ts_compiler::package::Registry;
|
||||||
use typst_ts_compiler::TypstSystemWorld;
|
|
||||||
|
use crate::world::LspWorld;
|
||||||
|
|
||||||
mod init;
|
mod init;
|
||||||
pub use init::*;
|
pub use init::*;
|
||||||
|
|
||||||
/// Try to determine the latest version of a package.
|
/// Try to determine the latest version of a package.
|
||||||
pub fn determine_latest_version(
|
pub fn determine_latest_version(
|
||||||
world: &TypstSystemWorld,
|
world: &LspWorld,
|
||||||
spec: &VersionlessPackageSpec,
|
spec: &VersionlessPackageSpec,
|
||||||
) -> StrResult<PackageVersion> {
|
) -> StrResult<PackageVersion> {
|
||||||
if spec.namespace == "preview" {
|
if spec.namespace == "preview" {
|
||||||
|
|
113
crates/tinymist/src/world.rs
Normal file
113
crates/tinymist/src/world.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use std::{borrow::Cow, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use comemo::Prehashed;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use typst_ts_core::{
|
||||||
|
config::{compiler::EntryState, CompileFontOpts as FontOptsInner},
|
||||||
|
error::prelude::*,
|
||||||
|
font::FontResolverImpl,
|
||||||
|
FontResolver, TypstDict,
|
||||||
|
};
|
||||||
|
|
||||||
|
use typst_ts_compiler::{
|
||||||
|
font::system::SystemFontSearcher,
|
||||||
|
package::http::HttpRegistry,
|
||||||
|
vfs::{system::SystemAccessModel, Vfs},
|
||||||
|
world::CompilerWorld,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct CompileOpts {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub once: CompileOnceOpts,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub font: CompileFontOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct CompileOnceOpts {
|
||||||
|
/// The root directory for compilation routine.
|
||||||
|
#[serde(rename = "rootDir")]
|
||||||
|
pub root_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path to entry
|
||||||
|
pub entry: PathBuf,
|
||||||
|
|
||||||
|
/// Additional input arguments to compile the entry file.
|
||||||
|
pub inputs: TypstDict,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct CompileFontOpts {
|
||||||
|
/// Path to font profile for cache
|
||||||
|
#[serde(rename = "fontProfileCachePath")]
|
||||||
|
pub font_profile_cache_path: PathBuf,
|
||||||
|
|
||||||
|
/// will remove later
|
||||||
|
#[serde(rename = "fontPaths")]
|
||||||
|
pub font_paths: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Exclude system font paths
|
||||||
|
#[serde(rename = "noSystemFonts")]
|
||||||
|
pub no_system_fonts: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SharedFontResolver {
|
||||||
|
pub inner: Arc<FontResolverImpl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontResolver for SharedFontResolver {
|
||||||
|
fn font(&self, idx: usize) -> Option<typst_ts_core::TypstFont> {
|
||||||
|
self.inner.font(idx)
|
||||||
|
}
|
||||||
|
fn font_book(&self) -> &Prehashed<typst::text::FontBook> {
|
||||||
|
self.inner.font_book()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// type trait of [`LspWorld`].
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SystemCompilerFeat;
|
||||||
|
|
||||||
|
impl typst_ts_compiler::world::CompilerFeat for SystemCompilerFeat {
|
||||||
|
/// Uses [`SharedFontResolver`] directly.
|
||||||
|
type FontResolver = SharedFontResolver;
|
||||||
|
/// It accesses a physical file system.
|
||||||
|
type AccessModel = SystemAccessModel;
|
||||||
|
/// It performs native HTTP requests for fetching package data.
|
||||||
|
type Registry = HttpRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The compiler world in system environment.
|
||||||
|
pub type LspWorld = CompilerWorld<SystemCompilerFeat>;
|
||||||
|
|
||||||
|
pub struct LspWorldBuilder;
|
||||||
|
// Self::resolve_fonts(opts)?,
|
||||||
|
|
||||||
|
impl LspWorldBuilder {
|
||||||
|
/// Create [`LspWorld`] with the given options.
|
||||||
|
/// See SystemCompilerFeat for instantiation details.
|
||||||
|
/// See [`CompileOpts`] for available options.
|
||||||
|
pub fn build(entry: EntryState, font_resolver: SharedFontResolver) -> ZResult<LspWorld> {
|
||||||
|
Ok(CompilerWorld::new_raw(
|
||||||
|
entry,
|
||||||
|
Vfs::new(SystemAccessModel {}),
|
||||||
|
HttpRegistry::default(),
|
||||||
|
font_resolver,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve fonts from given options.
|
||||||
|
pub(crate) fn resolve_fonts(opts: CompileFontOpts) -> ZResult<FontResolverImpl> {
|
||||||
|
let mut searcher = SystemFontSearcher::new();
|
||||||
|
searcher.resolve_opts(FontOptsInner {
|
||||||
|
font_profile_cache_path: opts.font_profile_cache_path,
|
||||||
|
font_paths: opts.font_paths,
|
||||||
|
no_system_fonts: opts.no_system_fonts,
|
||||||
|
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
|
||||||
|
})?;
|
||||||
|
Ok(searcher.into())
|
||||||
|
}
|
||||||
|
}
|
|
@ -254,7 +254,7 @@ async function commandPinMain(isPin: boolean): Promise<void> {
|
||||||
if (!isPin) {
|
if (!isPin) {
|
||||||
await client?.sendRequest("workspace/executeCommand", {
|
await client?.sendRequest("workspace/executeCommand", {
|
||||||
command: "tinymist.pinMain",
|
command: "tinymist.pinMain",
|
||||||
arguments: ["detached"],
|
arguments: [null],
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue