mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-23 12:45:04 +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
|
@ -34,12 +34,6 @@ typst.workspace = true
|
|||
typst-ide.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
|
||||
if_chain = "1"
|
||||
|
@ -49,8 +43,14 @@ once_cell.workspace = true
|
|||
insta.workspace = true
|
||||
serde.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" }
|
||||
hex = { version = "0.4" }
|
||||
|
||||
# [lints]
|
||||
# workspace = true
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Semantic static and dynamic analysis of the source code.
|
||||
|
||||
pub mod def_use;
|
||||
pub use def_use::*;
|
||||
pub mod track_values;
|
||||
|
|
|
@ -252,7 +252,7 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
|||
ModSrc::Expr(_) => {}
|
||||
ModSrc::Path(p) => {
|
||||
let src = find_source_by_import_path(
|
||||
self.ctx.ctx.world,
|
||||
self.ctx.ctx.world(),
|
||||
self.current_id,
|
||||
p.deref(),
|
||||
);
|
||||
|
|
|
@ -8,15 +8,10 @@ use once_cell::sync::OnceCell;
|
|||
use reflexo::{cow_mut::CowMut, ImmutPath};
|
||||
use typst::syntax::FileId as TypstFileId;
|
||||
use typst::{
|
||||
diag::{eco_format, FileError, FileResult},
|
||||
syntax::{Source, VirtualPath},
|
||||
diag::{eco_format, FileError, FileResult, PackageError},
|
||||
syntax::{package::PackageSpec, Source, VirtualPath},
|
||||
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 crate::{
|
||||
|
@ -37,7 +32,7 @@ impl ModuleAnalysisCache {
|
|||
/// Get the source of a file.
|
||||
pub fn source(&self, ctx: &AnalysisContext, file_id: TypstFileId) -> FileResult<Source> {
|
||||
self.source
|
||||
.get_or_init(|| ctx.world.source(file_id))
|
||||
.get_or_init(|| ctx.world().source(file_id))
|
||||
.clone()
|
||||
}
|
||||
|
||||
|
@ -73,10 +68,22 @@ pub struct AnalysisCaches {
|
|||
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.
|
||||
pub struct AnalysisContext<'a> {
|
||||
/// The world surface for Typst compiler
|
||||
pub world: &'a TypstSystemWorld,
|
||||
pub resources: &'a dyn AnaylsisResources,
|
||||
/// The analysis data
|
||||
pub analysis: CowMut<'a, Analysis>,
|
||||
caches: AnalysisCaches,
|
||||
|
@ -84,14 +91,19 @@ pub struct AnalysisContext<'a> {
|
|||
|
||||
impl<'w> AnalysisContext<'w> {
|
||||
/// 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 {
|
||||
world,
|
||||
resources: world,
|
||||
analysis: CowMut::Owned(a),
|
||||
caches: AnalysisCaches::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the world surface for Typst compiler.
|
||||
pub fn world(&self) -> &dyn World {
|
||||
self.resources.world()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) -> &Vec<TypstFileId> {
|
||||
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
|
||||
// will be resolved.
|
||||
let root = match id.package() {
|
||||
Some(spec) => self.world.registry.resolve(spec)?,
|
||||
Some(spec) => self.resources.resolve(spec)?,
|
||||
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> {
|
||||
lsp_to_typst::position(position, self.analysis.position_encoding, src)
|
||||
}
|
||||
|
||||
pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<TypstRange> {
|
||||
lsp_to_typst::range(position, self.analysis.position_encoding, src)
|
||||
}
|
||||
|
||||
/// Convert a Typst offset to a LSP position.
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn to_lsp_range(&self, position: TypstRange, src: &Source) -> LspRange {
|
||||
typst_to_lsp::range(position, src, self.analysis.position_encoding)
|
||||
/// Convert a LSP range to a Typst range.
|
||||
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 {
|
||||
self.analysis.position_encoding
|
||||
/// Convert a Typst range to a LSP range.
|
||||
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 crate::{prelude::*, SyntaxRequest};
|
||||
use crate::{prelude::*, SemanticRequest};
|
||||
|
||||
/// The [`textDocument/codeLens`] request is sent from the client to the server
|
||||
/// to compute code lenses for a given text document.
|
||||
|
@ -12,7 +12,7 @@ pub struct CodeLensRequest {
|
|||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for CodeLensRequest {
|
||||
impl SemanticRequest for CodeLensRequest {
|
||||
type Response = Vec<CodeLens>;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
|
|
@ -1,9 +1,32 @@
|
|||
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)]
|
||||
pub struct CompletionRequest {
|
||||
/// The path of the document to compute completions.
|
||||
pub path: PathBuf,
|
||||
/// The position in the document at which to compute completions.
|
||||
pub position: LspPosition,
|
||||
/// Whether the completion is triggered explicitly.
|
||||
pub explicit: bool,
|
||||
}
|
||||
|
||||
|
@ -35,7 +58,7 @@ impl StatefulRequest for CompletionRequest {
|
|||
let explicit = false;
|
||||
|
||||
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 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.
|
||||
pub fn convert_diagnostics<'a>(
|
||||
project: &TypstSystemWorld,
|
||||
project: &AnalysisContext,
|
||||
errors: impl IntoIterator<Item = &'a TypstDiagnostic>,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> DiagnosticsMap {
|
||||
|
@ -23,7 +23,7 @@ pub fn convert_diagnostics<'a>(
|
|||
}
|
||||
|
||||
fn convert_diagnostic(
|
||||
project: &TypstSystemWorld,
|
||||
project: &AnalysisContext,
|
||||
typst_diagnostic: &TypstDiagnostic,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> anyhow::Result<(Url, LspDiagnostic)> {
|
||||
|
@ -31,10 +31,10 @@ fn convert_diagnostic(
|
|||
let lsp_range;
|
||||
if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) {
|
||||
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);
|
||||
} else {
|
||||
uri = Url::from_file_path(project.root.clone()).unwrap();
|
||||
uri = Url::from_file_path(project.analysis.root.clone()).unwrap();
|
||||
lsp_range = LspRange::default();
|
||||
};
|
||||
|
||||
|
@ -59,13 +59,13 @@ fn convert_diagnostic(
|
|||
}
|
||||
|
||||
fn tracepoint_to_relatedinformation(
|
||||
project: &TypstSystemWorld,
|
||||
project: &AnalysisContext,
|
||||
tracepoint: &Spanned<Tracepoint>,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> anyhow::Result<Option<DiagnosticRelatedInformation>> {
|
||||
if let Some(id) = tracepoint.span.id() {
|
||||
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) {
|
||||
let lsp_range = typst_to_lsp::range(typst_range, &source, position_encoding);
|
||||
|
@ -84,7 +84,7 @@ fn tracepoint_to_relatedinformation(
|
|||
}
|
||||
|
||||
fn diagnostic_related_information(
|
||||
project: &TypstSystemWorld,
|
||||
project: &AnalysisContext,
|
||||
typst_diagnostic: &TypstDiagnostic,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> anyhow::Result<Vec<DiagnosticRelatedInformation>> {
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
use crate::{
|
||||
prelude::*,
|
||||
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)]
|
||||
pub struct DocumentSymbolRequest {
|
||||
/// The path of the document to retrieve symbols from.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl DocumentSymbolRequest {
|
||||
pub fn request(
|
||||
impl SyntaxRequest for DocumentSymbolRequest {
|
||||
type Response = DocumentSymbolResponse;
|
||||
|
||||
fn request(
|
||||
self,
|
||||
source: Source,
|
||||
source: &Source,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<DocumentSymbolResponse> {
|
||||
) -> Option<Self::Response> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +78,7 @@ mod tests {
|
|||
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,32 +1,46 @@
|
|||
use crate::{
|
||||
prelude::*,
|
||||
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)]
|
||||
pub struct FoldingRangeRequest {
|
||||
/// The path of the document to get folding ranges for.
|
||||
pub path: PathBuf,
|
||||
/// If set, the client can only provide folding ranges that consist of whole
|
||||
/// lines.
|
||||
pub line_folding_only: bool,
|
||||
}
|
||||
|
||||
impl FoldingRangeRequest {
|
||||
pub fn request(
|
||||
impl SyntaxRequest for FoldingRangeRequest {
|
||||
type Response = Vec<FoldingRange>;
|
||||
|
||||
fn request(
|
||||
self,
|
||||
source: Source,
|
||||
source: &Source,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<Vec<FoldingRange>> {
|
||||
) -> Option<Self::Response> {
|
||||
let line_folding_only = self.line_folding_only;
|
||||
|
||||
let symbols = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Braced)?;
|
||||
|
||||
let mut results = vec![];
|
||||
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));
|
||||
|
||||
calc_folding_range(
|
||||
&symbols,
|
||||
&source,
|
||||
source,
|
||||
position_encoding,
|
||||
line_folding_only,
|
||||
loc,
|
||||
|
@ -126,7 +140,7 @@ mod tests {
|
|||
|
||||
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()));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,16 +6,35 @@ use lsp_types::LocationLink;
|
|||
use crate::{
|
||||
prelude::*,
|
||||
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)]
|
||||
pub struct GotoDeclarationRequest {
|
||||
/// The path of the document to get the declaration location for.
|
||||
pub path: PathBuf,
|
||||
/// The position of the symbol to get the declaration location for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for GotoDeclarationRequest {
|
||||
impl SemanticRequest for GotoDeclarationRequest {
|
||||
type Response = GotoDeclarationResponse;
|
||||
|
||||
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,
|
||||
LexicalModKind, LexicalVarKind,
|
||||
},
|
||||
SyntaxRequest,
|
||||
SemanticRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/definition`] request asks the server for the definition
|
||||
|
@ -36,7 +36,7 @@ pub struct GotoDefinitionRequest {
|
|||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for GotoDefinitionRequest {
|
||||
impl SemanticRequest for GotoDefinitionRequest {
|
||||
type Response = GotoDefinitionResponse;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
@ -96,7 +96,7 @@ pub(crate) fn find_definition(
|
|||
let parent = path.parent()?;
|
||||
let def_fid = parent.span().id()?;
|
||||
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 {
|
||||
kind: LexicalKind::Mod(LexicalModKind::PathVar),
|
||||
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_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 {
|
||||
// mostly builtin functions
|
||||
if let Value::Func(f) = v.0 {
|
||||
|
@ -193,7 +193,7 @@ pub(crate) fn find_definition(
|
|||
let root = LinkedNode::new(def_source.root());
|
||||
let def_name = root.leaf_at(def.range.start + 1)?;
|
||||
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(..)));
|
||||
log::info!("okay for function: {func:?}");
|
||||
|
||||
|
|
|
@ -8,9 +8,18 @@ use crate::{
|
|||
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)]
|
||||
pub struct HoverRequest {
|
||||
/// The path of the document to get hover information for.
|
||||
pub path: PathBuf,
|
||||
/// The position of the symbol to get hover information for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
|
@ -31,7 +40,10 @@ impl StatefulRequest for HoverRequest {
|
|||
|
||||
let contents = def_tooltip(ctx, &source, cursor).or_else(|| {
|
||||
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) => {
|
||||
let deref_node = deref_target.node();
|
||||
// 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 {
|
||||
Tooltip::Text(s) => format!("// Values: {}", s),
|
||||
Tooltip::Code(s) => format!("// Values: {}", s),
|
||||
Tooltip::Text(s) => format!("// Values: {s}"),
|
||||
Tooltip::Code(s) => format!("// Values: {s}"),
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Some(LspHoverContents::Scalar(lsp_types::MarkedString::String(
|
||||
|
|
|
@ -10,23 +10,30 @@ use typst::{
|
|||
util::LazyHash,
|
||||
};
|
||||
|
||||
use crate::{prelude::*, SyntaxRequest};
|
||||
use crate::{prelude::*, SemanticRequest};
|
||||
|
||||
/// Configuration for inlay hints.
|
||||
pub struct InlayHintConfig {
|
||||
// positional arguments group
|
||||
/// Show inlay hints for positional arguments.
|
||||
pub on_pos_args: bool,
|
||||
/// Disable inlay hints for single positional arguments.
|
||||
pub off_single_pos_arg: bool,
|
||||
|
||||
// variadic arguments group
|
||||
/// Show inlay hints for variadic arguments.
|
||||
pub on_variadic_args: bool,
|
||||
/// Disable inlay hints for all variadic arguments but the first variadic
|
||||
/// argument.
|
||||
pub only_first_variadic_args: bool,
|
||||
|
||||
// todo
|
||||
// The typst sugar grammar
|
||||
/// Show inlay hints for content block arguments.
|
||||
pub on_content_block_args: bool,
|
||||
}
|
||||
|
||||
impl InlayHintConfig {
|
||||
/// A smart configuration that enables most useful inlay hints.
|
||||
pub const fn smart() -> Self {
|
||||
Self {
|
||||
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)]
|
||||
pub struct InlayHintRequest {
|
||||
/// The path of the document to get inlay hints for.
|
||||
pub path: PathBuf,
|
||||
/// The range of the document to get inlay hints for.
|
||||
pub range: LspRange,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for InlayHintRequest {
|
||||
impl SemanticRequest for InlayHintRequest {
|
||||
type Response = Vec<InlayHint>;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
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!(
|
||||
"got inlay hints on {source:?} => {hints:?}",
|
||||
source = source.id(),
|
||||
|
@ -516,7 +534,7 @@ impl ParamSpec {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Signature {
|
||||
pub(crate) struct Signature {
|
||||
pub pos: Vec<Arc<ParamSpec>>,
|
||||
pub named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
|
||||
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;
|
||||
pub mod analysis;
|
||||
pub mod syntax;
|
||||
|
@ -8,7 +17,7 @@ pub(crate) mod diagnostics;
|
|||
use std::sync::Arc;
|
||||
|
||||
pub use analysis::AnalysisContext;
|
||||
use typst::model::Document as TypstDocument;
|
||||
use typst::{model::Document as TypstDocument, syntax::Source};
|
||||
|
||||
pub use diagnostics::*;
|
||||
pub(crate) mod code_lens;
|
||||
|
@ -48,31 +57,57 @@ pub use references::*;
|
|||
|
||||
pub mod lsp_typst_boundary;
|
||||
pub use lsp_typst_boundary::*;
|
||||
pub(crate) mod lsp_features;
|
||||
pub use lsp_features::*;
|
||||
|
||||
mod prelude;
|
||||
|
||||
/// A compiled document with an self-incremented logical version.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VersionedDocument {
|
||||
/// The version of the document.
|
||||
pub version: usize,
|
||||
/// The compiled document.
|
||||
pub document: Arc<TypstDocument>,
|
||||
}
|
||||
|
||||
/// A request handler with given syntax information.
|
||||
pub trait SyntaxRequest {
|
||||
/// The response type of the request.
|
||||
type Response;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response>;
|
||||
}
|
||||
|
||||
pub trait StatefulRequest {
|
||||
type Response;
|
||||
|
||||
/// Request the information from the given source.
|
||||
fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
v: Option<VersionedDocument>,
|
||||
source: &Source,
|
||||
positing_encoding: PositionEncoding,
|
||||
) -> 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 {
|
||||
use super::prelude::*;
|
||||
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
|
||||
|
||||
// todo: remove this
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use lsp_types;
|
||||
|
||||
pub type LspPosition = lsp_types::Position;
|
||||
|
|
|
@ -25,8 +25,6 @@ pub use typst::syntax::{
|
|||
LinkedNode, Source, Spanned, SyntaxKind,
|
||||
};
|
||||
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::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;
|
||||
|
||||
/// The [`textDocument/prepareRename`] request is sent from the client to the
|
||||
|
@ -26,7 +28,7 @@ pub struct PrepareRenameRequest {
|
|||
|
||||
// todo: rename alias
|
||||
// todo: rename import path?
|
||||
impl SyntaxRequest for PrepareRenameRequest {
|
||||
impl SemanticRequest for PrepareRenameRequest {
|
||||
type Response = PrepareRenameResponse;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
|
|
@ -3,7 +3,7 @@ use log::debug;
|
|||
use crate::{
|
||||
prelude::*,
|
||||
syntax::{get_deref_target, DerefTarget, IdentRef},
|
||||
SyntaxRequest,
|
||||
SemanticRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/references`] request is sent from the client to the
|
||||
|
@ -19,7 +19,7 @@ pub struct ReferencesRequest {
|
|||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for ReferencesRequest {
|
||||
impl SemanticRequest for ReferencesRequest {
|
||||
type Response = Vec<LspLocation>;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
|
|
@ -3,7 +3,7 @@ use lsp_types::TextEdit;
|
|||
|
||||
use crate::{
|
||||
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
|
||||
|
@ -21,7 +21,7 @@ pub struct RenameRequest {
|
|||
pub new_name: String,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for RenameRequest {
|
||||
impl SemanticRequest for RenameRequest {
|
||||
type Response = WorkspaceEdit;
|
||||
|
||||
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)]
|
||||
pub struct SelectionRangeRequest {
|
||||
/// The path of the document to get selection ranges for.
|
||||
pub path: PathBuf,
|
||||
/// The positions to get selection ranges for.
|
||||
pub positions: Vec<LspPosition>,
|
||||
}
|
||||
|
||||
impl SelectionRangeRequest {
|
||||
pub fn request(
|
||||
impl SyntaxRequest for SelectionRangeRequest {
|
||||
type Response = Vec<SelectionRange>;
|
||||
|
||||
fn request(
|
||||
self,
|
||||
source: Source,
|
||||
source: &Source,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<Vec<SelectionRange>> {
|
||||
) -> Option<Self::Response> {
|
||||
let mut ranges = Vec::new();
|
||||
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 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)
|
||||
|
|
|
@ -1,64 +1,22 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use lsp_types::{
|
||||
Registration, SemanticToken, SemanticTokensEdit, SemanticTokensFullOptions,
|
||||
SemanticTokensLegend, SemanticTokensOptions, Unregistration,
|
||||
};
|
||||
use lsp_types::{SemanticToken, SemanticTokensEdit};
|
||||
use parking_lot::RwLock;
|
||||
use strum::IntoEnumIterator;
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||
|
||||
use crate::{LspPosition, PositionEncoding};
|
||||
|
||||
use self::delta::token_delta;
|
||||
use self::modifier_set::ModifierSet;
|
||||
use self::typst_tokens::{Modifier, TokenType};
|
||||
|
||||
use self::delta::CacheInner as TokenCacheInner;
|
||||
|
||||
mod delta;
|
||||
mod modifier_set;
|
||||
mod typst_tokens;
|
||||
pub use self::typst_tokens::{Modifier, TokenType};
|
||||
|
||||
pub 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()
|
||||
}
|
||||
}
|
||||
|
||||
/// A semantic token context providing incremental semantic tokens rendering.
|
||||
#[derive(Default)]
|
||||
pub struct SemanticTokenContext {
|
||||
cache: RwLock<TokenCacheInner>,
|
||||
|
@ -70,6 +28,7 @@ pub struct SemanticTokenContext {
|
|||
}
|
||||
|
||||
impl SemanticTokenContext {
|
||||
/// Create a new semantic token context.
|
||||
pub fn new(
|
||||
position_encoding: PositionEncoding,
|
||||
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) {
|
||||
let root = LinkedNode::new(source.root());
|
||||
|
||||
|
@ -98,6 +58,7 @@ impl SemanticTokenContext {
|
|||
(output, result_id)
|
||||
}
|
||||
|
||||
/// Get the semantic tokens delta for a source.
|
||||
pub fn try_semantic_tokens_delta_from_result_id(
|
||||
&self,
|
||||
source: &Source,
|
||||
|
@ -292,7 +253,7 @@ impl Tokenizer {
|
|||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Token {
|
||||
struct Token {
|
||||
pub token_type: TokenType,
|
||||
pub modifiers: ModifierSet,
|
||||
pub range: Range<usize>,
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
//! Types for tokens used for Typst syntax
|
||||
|
||||
// todo: remove this
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use lsp_types::{SemanticTokenModifier, SemanticTokenType};
|
||||
use strum::EnumIter;
|
||||
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
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)]
|
||||
pub struct SemanticTokensDeltaRequest {
|
||||
/// The path of the document to get semantic tokens for.
|
||||
pub path: PathBuf,
|
||||
/// The previous result id to compute the delta from.
|
||||
pub previous_result_id: String,
|
||||
}
|
||||
|
||||
impl SemanticTokensDeltaRequest {
|
||||
/// Handles the request to compute the semantic tokens delta for a given
|
||||
/// document.
|
||||
pub fn request(
|
||||
self,
|
||||
ctx: &SemanticTokenContext,
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
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)]
|
||||
pub struct SemanticTokensFullRequest {
|
||||
/// The path of the document to get semantic tokens for.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl SemanticTokensFullRequest {
|
||||
/// Handles the request to compute the semantic tokens for a given document.
|
||||
pub fn request(
|
||||
self,
|
||||
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)]
|
||||
pub struct SignatureHelpRequest {
|
||||
/// The path of the document to get signature help for.
|
||||
pub path: PathBuf,
|
||||
/// The position of the cursor to get signature help for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for SignatureHelpRequest {
|
||||
impl SemanticRequest for SignatureHelpRequest {
|
||||
type Response = SignatureHelp;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
@ -20,7 +26,7 @@ impl SyntaxRequest for SignatureHelpRequest {
|
|||
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 {
|
||||
Value::Func(f) => Some(f),
|
||||
|
|
|
@ -1,17 +1,34 @@
|
|||
use typst_ts_compiler::NotifyApi;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
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)]
|
||||
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>,
|
||||
}
|
||||
|
||||
impl SyntaxRequest for SymbolRequest {
|
||||
impl SemanticRequest for SymbolRequest {
|
||||
type Response = Vec<SymbolInformation>;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
|
@ -19,7 +36,7 @@ impl SyntaxRequest for SymbolRequest {
|
|||
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ use anyhow::{anyhow, Context};
|
|||
use ecow::{eco_vec, EcoVec};
|
||||
use log::info;
|
||||
use lsp_types::SymbolKind;
|
||||
use reflexo::error::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::{
|
||||
syntax::{
|
||||
|
@ -654,7 +653,7 @@ impl LexicalHierarchyWorker {
|
|||
let name = if e.starts_with('@') {
|
||||
let spec = e
|
||||
.parse::<PackageSpec>()
|
||||
.context("parse package spec failed for name")?;
|
||||
.map_err(|e| anyhow!("parse package spec failed: {:?}", e))?;
|
||||
spec.name.to_string()
|
||||
} else {
|
||||
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 mod lexical_hierarchy;
|
||||
pub(crate) use lexical_hierarchy::*;
|
||||
pub mod matcher;
|
||||
pub(crate) mod lexical_hierarchy;
|
||||
pub use lexical_hierarchy::*;
|
||||
pub(crate) mod matcher;
|
||||
pub use matcher::*;
|
||||
pub mod module;
|
||||
pub(crate) mod module;
|
||||
pub use module::*;
|
||||
|
||||
use core::fmt;
|
||||
|
@ -70,7 +77,7 @@ impl<'de> Deserialize<'de> for IdentRef {
|
|||
.split("..")
|
||||
.map(|s| {
|
||||
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>, _>>()?;
|
||||
|
|
|
@ -7,11 +7,18 @@ use crate::prelude::AnalysisContext;
|
|||
|
||||
use super::find_imports;
|
||||
|
||||
/// The dependency information of a module (file).
|
||||
pub struct ModuleDependency {
|
||||
/// The dependencies of this module.
|
||||
pub dependencies: EcoVec<TypstFileId>,
|
||||
/// The dependents of this module.
|
||||
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(
|
||||
ctx: &mut AnalysisContext,
|
||||
) -> HashMap<TypstFileId, ModuleDependency> {
|
||||
|
@ -31,7 +38,7 @@ pub fn construct_module_dependencies(
|
|||
};
|
||||
|
||||
let file_id = source.id();
|
||||
let deps = find_imports(ctx.world, &source);
|
||||
let deps = find_imports(ctx.world(), &source);
|
||||
dependencies
|
||||
.entry(file_id)
|
||||
.or_insert_with(|| ModuleDependency {
|
||||
|
@ -55,6 +62,9 @@ pub fn construct_module_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> {
|
||||
let mut res = vec![];
|
||||
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 typst::syntax::{
|
||||
ast::{self, AstNode},
|
||||
LinkedNode, Source, SyntaxKind, VirtualPath,
|
||||
FileId as TypstFileId, LinkedNode, Source, SyntaxKind, VirtualPath,
|
||||
};
|
||||
use typst::{diag::PackageError, foundations::Bytes};
|
||||
use typst_ts_compiler::{
|
||||
service::{CompileDriver, Compiler, WorkspaceProvider},
|
||||
ShadowApi,
|
||||
service::{CompileDriver, Compiler, EntryManager},
|
||||
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 typst_ts_compiler::TypstSystemWorld;
|
||||
|
||||
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)) {
|
||||
let mut settings = insta::Settings::new();
|
||||
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");
|
||||
|
||||
run_with_sources(&contents, |w: &mut TypstSystemWorld, p| {
|
||||
let root = w.workspace_root().unwrap();
|
||||
let paths = w
|
||||
.shadow_paths()
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
TypstFileId::new(
|
||||
None,
|
||||
VirtualPath::new(p.strip_prefix(w.workspace_root()).unwrap()),
|
||||
)
|
||||
TypstFileId::new(None, VirtualPath::new(p.strip_prefix(&root).unwrap()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let w = WrapWorld(w);
|
||||
let mut ctx = AnalysisContext::new(
|
||||
w,
|
||||
&w,
|
||||
Analysis {
|
||||
root: w.workspace_root(),
|
||||
root,
|
||||
position_encoding: PositionEncoding::Utf16,
|
||||
},
|
||||
);
|
||||
|
@ -67,7 +89,7 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P
|
|||
PathBuf::from("/")
|
||||
};
|
||||
let mut world = TypstSystemWorld::new(CompileOpts {
|
||||
root_dir: root.clone(),
|
||||
entry: EntryOpts::new_rooted(root.as_path().into(), None),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -99,15 +121,21 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P
|
|||
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 _ = driver.compile(&mut Default::default());
|
||||
|
||||
let pw = last_pw.unwrap();
|
||||
driver.world_mut().set_main_id(TypstFileId::new(
|
||||
None,
|
||||
VirtualPath::new(pw.strip_prefix(root).unwrap()),
|
||||
));
|
||||
driver
|
||||
.world_mut()
|
||||
.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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue