mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: support goto/find references inside of modules (#34)
* feat: support goto declaration inside of modules * fix: change goto declarations provider to references provider * fix: redact uri in references response
This commit is contained in:
parent
9d344570b4
commit
dbd1726d08
19 changed files with 489 additions and 46 deletions
|
@ -10,6 +10,8 @@ pub mod reference;
|
||||||
pub use reference::*;
|
pub use reference::*;
|
||||||
pub mod def_use;
|
pub mod def_use;
|
||||||
pub use def_use::*;
|
pub use def_use::*;
|
||||||
|
pub mod matcher;
|
||||||
|
pub use matcher::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod lexical_hierarchy_tests {
|
mod lexical_hierarchy_tests {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
|
use log::info;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typst::{syntax::Source, World};
|
use typst::{syntax::Source, World};
|
||||||
|
@ -26,8 +27,8 @@ enum Ns {
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct IdentRef {
|
pub struct IdentRef {
|
||||||
name: String,
|
pub name: String,
|
||||||
range: Range<usize>,
|
pub range: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for IdentRef {
|
impl PartialOrd for IdentRef {
|
||||||
|
@ -72,6 +73,19 @@ pub struct DefUseInfo {
|
||||||
exports_refs: Vec<DefId>,
|
exports_refs: Vec<DefId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DefUseInfo {
|
||||||
|
pub fn get_def(&self, fid: TypstFileId, ident: &IdentRef) -> Option<(DefId, &IdentDef)> {
|
||||||
|
let (id, _, def) = self.ident_defs.get_full(&(fid, ident.clone()))?;
|
||||||
|
Some((DefId(id as u64), def))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_refs(&self, id: DefId) -> impl Iterator<Item = &IdentRef> {
|
||||||
|
self.ident_refs
|
||||||
|
.iter()
|
||||||
|
.filter_map(move |(k, v)| if *v == id { Some(k) } else { None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_def_use(world: Tracked<'_, dyn World>, source: Source) -> Option<Arc<DefUseInfo>> {
|
pub fn get_def_use(world: Tracked<'_, dyn World>, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||||
let ctx = SearchCtx {
|
let ctx = SearchCtx {
|
||||||
world,
|
world,
|
||||||
|
@ -171,6 +185,7 @@ impl<'a, 'w> DefUseCollector<'a, 'w> {
|
||||||
let external_info =
|
let external_info =
|
||||||
find_source_by_import_path(self.ctx.world, self.current_id, path)
|
find_source_by_import_path(self.ctx.world, self.current_id, path)
|
||||||
.and_then(|source| {
|
.and_then(|source| {
|
||||||
|
info!("diving source for def use: {:?}", source.id());
|
||||||
Some(source.id()).zip(get_def_use_inner(self.ctx, source))
|
Some(source.id()).zip(get_def_use_inner(self.ctx, source))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use typst::{
|
||||||
};
|
};
|
||||||
use typst_ts_core::TypstFileId;
|
use typst_ts_core::TypstFileId;
|
||||||
|
|
||||||
use crate::analysis::find_source_by_import;
|
use crate::analysis::{deref_lvalue, find_source_by_import};
|
||||||
use crate::{prelude::*, TypstSpan};
|
use crate::{prelude::*, TypstSpan};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -73,13 +73,6 @@ impl Definition<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deref_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
|
|
||||||
while let Some(e) = node.cast::<ast::Parenthesized>() {
|
|
||||||
node = node.find(e.expr().span())?;
|
|
||||||
}
|
|
||||||
Some(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance_prev_adjacent(node: LinkedNode) -> Option<LinkedNode> {
|
fn advance_prev_adjacent(node: LinkedNode) -> Option<LinkedNode> {
|
||||||
// this is aworkaround for a bug in the parser
|
// this is aworkaround for a bug in the parser
|
||||||
if node.len() == 0 {
|
if node.len() == 0 {
|
||||||
|
|
69
crates/tinymist-query/src/analysis/matcher.rs
Normal file
69
crates/tinymist-query/src/analysis/matcher.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use log::debug;
|
||||||
|
use typst::syntax::{
|
||||||
|
ast::{self, AstNode},
|
||||||
|
LinkedNode, SyntaxKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deref_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
|
||||||
|
while let Some(e) = node.cast::<ast::Parenthesized>() {
|
||||||
|
node = node.find(e.expr().span())?;
|
||||||
|
}
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DerefTarget<'a> {
|
||||||
|
VarAccess(LinkedNode<'a>),
|
||||||
|
Callee(LinkedNode<'a>),
|
||||||
|
ImportPath(LinkedNode<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DerefTarget<'a> {
|
||||||
|
pub fn node(&self) -> &LinkedNode {
|
||||||
|
match self {
|
||||||
|
DerefTarget::VarAccess(node) => node,
|
||||||
|
DerefTarget::Callee(node) => node,
|
||||||
|
DerefTarget::ImportPath(node) => node,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_deref_target(node: LinkedNode) -> Option<DerefTarget> {
|
||||||
|
let mut ancestor = node;
|
||||||
|
while !ancestor.is::<ast::Expr>() {
|
||||||
|
ancestor = ancestor.parent()?.clone();
|
||||||
|
}
|
||||||
|
debug!("deref expr: {ancestor:?}");
|
||||||
|
let ancestor = deref_lvalue(ancestor)?;
|
||||||
|
debug!("deref lvalue: {ancestor:?}");
|
||||||
|
|
||||||
|
let may_ident = ancestor.cast::<ast::Expr>()?;
|
||||||
|
if !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match may_ident {
|
||||||
|
// todo: label, reference
|
||||||
|
// todo: import
|
||||||
|
// todo: include
|
||||||
|
ast::Expr::FuncCall(call) => DerefTarget::Callee(ancestor.find(call.callee().span())?),
|
||||||
|
ast::Expr::Set(set) => DerefTarget::Callee(ancestor.find(set.target().span())?),
|
||||||
|
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
|
||||||
|
DerefTarget::VarAccess(ancestor.find(may_ident.span())?)
|
||||||
|
}
|
||||||
|
ast::Expr::Str(..) => {
|
||||||
|
let parent = ancestor.parent()?;
|
||||||
|
if parent.kind() != SyntaxKind::ModuleImport {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(DerefTarget::ImportPath(ancestor.find(may_ident.span())?));
|
||||||
|
}
|
||||||
|
ast::Expr::Import(..) => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("unsupported kind {kind:?}", kind = ancestor.kind());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
#let /* ident after */ f() = 1;
|
||||||
|
#(f());
|
||||||
|
#(f());
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/goto_definition.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/goto_definition/at_def.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"originSelectionRange": "0:23:0:24",
|
||||||
|
"targetRange": "0:24:0:26",
|
||||||
|
"targetSelectionRange": "0:24:0:26"
|
||||||
|
}
|
||||||
|
]
|
3
crates/tinymist-query/src/fixtures/references/at_def.typ
Normal file
3
crates/tinymist-query/src/fixtures/references/at_def.typ
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#let /* ident after */ f() = 1;
|
||||||
|
#(f());
|
||||||
|
#(f());
|
2
crates/tinymist-query/src/fixtures/references/base.typ
Normal file
2
crates/tinymist-query/src/fixtures/references/base.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#let x = 1;
|
||||||
|
#(/* position after */ x);
|
10
crates/tinymist-query/src/fixtures/references/redefine.typ
Normal file
10
crates/tinymist-query/src/fixtures/references/redefine.typ
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#let /* ident after */ x = 1;
|
||||||
|
|
||||||
|
#let y = {
|
||||||
|
x + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#let f(y) = x + y;
|
||||||
|
|
||||||
|
#let x = x;
|
||||||
|
#let f = x;
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/references.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/references/at_def.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"range": "2:2:2:3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": "1:2:1:3"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/goto_declaration.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/goto_declaration/base.typ
|
||||||
|
---
|
||||||
|
null
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/references.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/references/redefine.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"range": "8:9:8:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": "3:2:3:3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": "3:6:3:7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": "6:12:6:13"
|
||||||
|
}
|
||||||
|
]
|
69
crates/tinymist-query/src/goto_declaration.rs
Normal file
69
crates/tinymist-query/src/goto_declaration.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use comemo::Track;
|
||||||
|
use log::debug;
|
||||||
|
use lsp_types::LocationLink;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
analysis::{get_def_use, get_deref_target, DerefTarget},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GotoDeclarationRequest {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub position: LspPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GotoDeclarationRequest {
|
||||||
|
pub fn request(
|
||||||
|
self,
|
||||||
|
world: &TypstSystemWorld,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
|
) -> Option<GotoDeclarationResponse> {
|
||||||
|
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
||||||
|
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||||
|
let cursor = offset + 1;
|
||||||
|
|
||||||
|
let w: &dyn World = world;
|
||||||
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
|
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||||
|
let deref_target = get_deref_target(ast_node)?;
|
||||||
|
|
||||||
|
let use_site = deref_target.node();
|
||||||
|
let origin_selection_range =
|
||||||
|
typst_to_lsp::range(use_site.range(), &source, position_encoding);
|
||||||
|
|
||||||
|
let def_use = get_def_use(w.track(), source.clone())?;
|
||||||
|
let ref_spans = find_declarations(w, def_use, deref_target)?;
|
||||||
|
|
||||||
|
let mut links = vec![];
|
||||||
|
for ref_range in ref_spans {
|
||||||
|
let ref_id = source.id();
|
||||||
|
let ref_source = &source;
|
||||||
|
|
||||||
|
let span_path = world.path_for_id(ref_id).ok()?;
|
||||||
|
let range = typst_to_lsp::range(ref_range, ref_source, position_encoding);
|
||||||
|
|
||||||
|
let uri = Url::from_file_path(span_path).ok()?;
|
||||||
|
|
||||||
|
links.push(LocationLink {
|
||||||
|
origin_selection_range: Some(origin_selection_range),
|
||||||
|
target_uri: uri,
|
||||||
|
target_range: range,
|
||||||
|
target_selection_range: range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("goto_declartion: {links:?}");
|
||||||
|
Some(GotoDeclarationResponse::Link(links))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_declarations(
|
||||||
|
_w: &dyn World,
|
||||||
|
_def_use: Arc<crate::analysis::DefUseInfo>,
|
||||||
|
_deref_target: DerefTarget<'_>,
|
||||||
|
) -> Option<Vec<Range<usize>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
|
@ -8,36 +8,40 @@ use std::sync::Arc;
|
||||||
use typst_ts_core::TypstDocument;
|
use typst_ts_core::TypstDocument;
|
||||||
|
|
||||||
pub use diagnostics::*;
|
pub use diagnostics::*;
|
||||||
pub(crate) mod signature_help;
|
pub(crate) mod code_lens;
|
||||||
pub use signature_help::*;
|
pub use code_lens::*;
|
||||||
|
pub(crate) mod completion;
|
||||||
|
pub use completion::*;
|
||||||
pub(crate) mod document_symbol;
|
pub(crate) mod document_symbol;
|
||||||
pub use document_symbol::*;
|
pub use document_symbol::*;
|
||||||
pub(crate) mod symbol;
|
pub(crate) mod folding_range;
|
||||||
pub use symbol::*;
|
pub use folding_range::*;
|
||||||
|
pub(crate) mod goto_declaration;
|
||||||
|
pub use goto_declaration::*;
|
||||||
|
pub(crate) mod goto_definition;
|
||||||
|
pub use goto_definition::*;
|
||||||
|
pub(crate) mod hover;
|
||||||
|
pub use hover::*;
|
||||||
|
pub(crate) mod inlay_hint;
|
||||||
|
pub use inlay_hint::*;
|
||||||
|
pub(crate) mod rename;
|
||||||
|
pub use rename::*;
|
||||||
|
pub(crate) mod selection_range;
|
||||||
|
pub use selection_range::*;
|
||||||
pub(crate) mod semantic_tokens;
|
pub(crate) mod semantic_tokens;
|
||||||
pub use semantic_tokens::*;
|
pub use semantic_tokens::*;
|
||||||
pub(crate) mod semantic_tokens_full;
|
pub(crate) mod semantic_tokens_full;
|
||||||
pub use semantic_tokens_full::*;
|
pub use semantic_tokens_full::*;
|
||||||
pub(crate) mod semantic_tokens_delta;
|
pub(crate) mod semantic_tokens_delta;
|
||||||
pub use semantic_tokens_delta::*;
|
pub use semantic_tokens_delta::*;
|
||||||
pub(crate) mod hover;
|
pub(crate) mod signature_help;
|
||||||
pub use hover::*;
|
pub use signature_help::*;
|
||||||
pub(crate) mod completion;
|
pub(crate) mod symbol;
|
||||||
pub use completion::*;
|
pub use symbol::*;
|
||||||
pub(crate) mod folding_range;
|
|
||||||
pub use folding_range::*;
|
|
||||||
pub(crate) mod selection_range;
|
|
||||||
pub use selection_range::*;
|
|
||||||
pub(crate) mod goto_definition;
|
|
||||||
pub use goto_definition::*;
|
|
||||||
pub(crate) mod inlay_hint;
|
|
||||||
pub use inlay_hint::*;
|
|
||||||
pub(crate) mod prepare_rename;
|
pub(crate) mod prepare_rename;
|
||||||
pub use prepare_rename::*;
|
pub use prepare_rename::*;
|
||||||
pub(crate) mod rename;
|
pub(crate) mod references;
|
||||||
pub use rename::*;
|
pub use references::*;
|
||||||
pub(crate) mod code_lens;
|
|
||||||
pub use code_lens::*;
|
|
||||||
|
|
||||||
pub mod lsp_typst_boundary;
|
pub mod lsp_typst_boundary;
|
||||||
pub use lsp_typst_boundary::*;
|
pub use lsp_typst_boundary::*;
|
||||||
|
@ -78,6 +82,8 @@ mod polymorphic {
|
||||||
OnSaveExport(OnSaveExportRequest),
|
OnSaveExport(OnSaveExportRequest),
|
||||||
Hover(HoverRequest),
|
Hover(HoverRequest),
|
||||||
GotoDefinition(GotoDefinitionRequest),
|
GotoDefinition(GotoDefinitionRequest),
|
||||||
|
GotoDeclaration(GotoDeclarationRequest),
|
||||||
|
References(ReferencesRequest),
|
||||||
InlayHint(InlayHintRequest),
|
InlayHint(InlayHintRequest),
|
||||||
CodeLens(CodeLensRequest),
|
CodeLens(CodeLensRequest),
|
||||||
Completion(CompletionRequest),
|
Completion(CompletionRequest),
|
||||||
|
@ -100,6 +106,8 @@ mod polymorphic {
|
||||||
CompilerQueryRequest::OnSaveExport(..) => Mergable,
|
CompilerQueryRequest::OnSaveExport(..) => Mergable,
|
||||||
CompilerQueryRequest::Hover(..) => PinnedFirst,
|
CompilerQueryRequest::Hover(..) => PinnedFirst,
|
||||||
CompilerQueryRequest::GotoDefinition(..) => PinnedFirst,
|
CompilerQueryRequest::GotoDefinition(..) => PinnedFirst,
|
||||||
|
CompilerQueryRequest::GotoDeclaration(..) => PinnedFirst,
|
||||||
|
CompilerQueryRequest::References(..) => PinnedFirst,
|
||||||
CompilerQueryRequest::InlayHint(..) => Unique,
|
CompilerQueryRequest::InlayHint(..) => Unique,
|
||||||
CompilerQueryRequest::CodeLens(..) => Unique,
|
CompilerQueryRequest::CodeLens(..) => Unique,
|
||||||
CompilerQueryRequest::Completion(..) => Mergable,
|
CompilerQueryRequest::Completion(..) => Mergable,
|
||||||
|
@ -121,6 +129,8 @@ mod polymorphic {
|
||||||
CompilerQueryRequest::OnSaveExport(req) => &req.path,
|
CompilerQueryRequest::OnSaveExport(req) => &req.path,
|
||||||
CompilerQueryRequest::Hover(req) => &req.path,
|
CompilerQueryRequest::Hover(req) => &req.path,
|
||||||
CompilerQueryRequest::GotoDefinition(req) => &req.path,
|
CompilerQueryRequest::GotoDefinition(req) => &req.path,
|
||||||
|
CompilerQueryRequest::GotoDeclaration(req) => &req.path,
|
||||||
|
CompilerQueryRequest::References(req) => &req.path,
|
||||||
CompilerQueryRequest::InlayHint(req) => &req.path,
|
CompilerQueryRequest::InlayHint(req) => &req.path,
|
||||||
CompilerQueryRequest::CodeLens(req) => &req.path,
|
CompilerQueryRequest::CodeLens(req) => &req.path,
|
||||||
CompilerQueryRequest::Completion(req) => &req.path,
|
CompilerQueryRequest::Completion(req) => &req.path,
|
||||||
|
@ -143,6 +153,8 @@ mod polymorphic {
|
||||||
OnSaveExport(()),
|
OnSaveExport(()),
|
||||||
Hover(Option<Hover>),
|
Hover(Option<Hover>),
|
||||||
GotoDefinition(Option<GotoDefinitionResponse>),
|
GotoDefinition(Option<GotoDefinitionResponse>),
|
||||||
|
GotoDeclaration(Option<GotoDeclarationResponse>),
|
||||||
|
References(Option<Vec<LspLocation>>),
|
||||||
InlayHint(Option<Vec<InlayHint>>),
|
InlayHint(Option<Vec<InlayHint>>),
|
||||||
CodeLens(Option<Vec<CodeLens>>),
|
CodeLens(Option<Vec<CodeLens>>),
|
||||||
Completion(Option<CompletionResponse>),
|
Completion(Option<CompletionResponse>),
|
||||||
|
@ -171,7 +183,10 @@ mod tests {
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
||||||
use typst::syntax::{LinkedNode, Source, VirtualPath};
|
use typst::syntax::{
|
||||||
|
ast::{self, AstNode},
|
||||||
|
LinkedNode, Source, SyntaxKind, VirtualPath,
|
||||||
|
};
|
||||||
use typst_ts_compiler::{
|
use typst_ts_compiler::{
|
||||||
service::{CompileDriver, Compiler, WorkspaceProvider},
|
service::{CompileDriver, Compiler, WorkspaceProvider},
|
||||||
ShadowApi,
|
ShadowApi,
|
||||||
|
@ -252,25 +267,86 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_test_position(s: &Source) -> LspPosition {
|
pub fn find_test_position(s: &Source) -> LspPosition {
|
||||||
let re = s.text().find("/* position */").map(|e| (e, true));
|
enum AstMatcher {
|
||||||
let re = re.or_else(|| s.text().find("/* position after */").zip(Some(false)));
|
MatchAny { prev: bool },
|
||||||
let (re, prev) = re
|
MatchIdent { prev: bool },
|
||||||
|
}
|
||||||
|
use AstMatcher::*;
|
||||||
|
|
||||||
|
let re = s
|
||||||
|
.text()
|
||||||
|
.find("/* position */")
|
||||||
|
.map(|e| (e, MatchAny { prev: true }));
|
||||||
|
let re = re.or_else(|| {
|
||||||
|
s.text()
|
||||||
|
.find("/* position after */")
|
||||||
|
.zip(Some(MatchAny { prev: false }))
|
||||||
|
});
|
||||||
|
let re = re.or_else(|| {
|
||||||
|
s.text()
|
||||||
|
.find("/* ident */")
|
||||||
|
.zip(Some(MatchIdent { prev: true }))
|
||||||
|
});
|
||||||
|
let re = re.or_else(|| {
|
||||||
|
s.text()
|
||||||
|
.find("/* ident after */")
|
||||||
|
.zip(Some(MatchIdent { prev: false }))
|
||||||
|
});
|
||||||
|
let (re, m) = re
|
||||||
.ok_or_else(|| panic!("No position marker found in source:\n{}", s.text()))
|
.ok_or_else(|| panic!("No position marker found in source:\n{}", s.text()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let n = LinkedNode::new(s.root());
|
let n = LinkedNode::new(s.root());
|
||||||
let mut n = n.leaf_at(re + 1).unwrap();
|
let mut n = n.leaf_at(re + 1).unwrap();
|
||||||
|
|
||||||
while n.kind().is_trivia() {
|
let match_prev = match &m {
|
||||||
let m = if prev {
|
MatchAny { prev } => *prev,
|
||||||
n.prev_sibling()
|
MatchIdent { prev } => *prev,
|
||||||
} else {
|
};
|
||||||
n.next_sibling()
|
let match_ident = match m {
|
||||||
};
|
MatchAny { .. } => false,
|
||||||
n = m.or_else(|| n.parent().cloned()).unwrap();
|
MatchIdent { .. } => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
'match_loop: loop {
|
||||||
|
if n.kind().is_trivia() {
|
||||||
|
let m = if match_prev {
|
||||||
|
n.prev_sibling()
|
||||||
|
} else {
|
||||||
|
n.next_sibling()
|
||||||
|
};
|
||||||
|
n = m.or_else(|| n.parent().cloned()).unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if match_ident {
|
||||||
|
match n.kind() {
|
||||||
|
SyntaxKind::Closure => {
|
||||||
|
let c = n.cast::<ast::Closure>().unwrap();
|
||||||
|
if let Some(name) = c.name() {
|
||||||
|
if let Some(m) = n.find(name.span()) {
|
||||||
|
n = m;
|
||||||
|
break 'match_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxKind::LetBinding => {
|
||||||
|
let c = n.cast::<ast::LetBinding>().unwrap();
|
||||||
|
if let Some(name) = c.kind().bindings().first() {
|
||||||
|
if let Some(m) = n.find(name.span()) {
|
||||||
|
n = m;
|
||||||
|
break 'match_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
typst_to_lsp::offset_to_position(n.offset() + 1, PositionEncoding::Utf16, s)
|
// eprintln!("position: {:?} -> {:?}", n.offset(), n);
|
||||||
|
|
||||||
|
typst_to_lsp::offset_to_position(n.offset(), PositionEncoding::Utf16, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub static REDACT_URI: Lazy<RedactFields> = Lazy::new(||
|
// pub static REDACT_URI: Lazy<RedactFields> = Lazy::new(||
|
||||||
|
@ -278,6 +354,7 @@ mod tests {
|
||||||
pub static REDACT_LOC: Lazy<RedactFields> = Lazy::new(|| {
|
pub static REDACT_LOC: Lazy<RedactFields> = Lazy::new(|| {
|
||||||
RedactFields::from_iter([
|
RedactFields::from_iter([
|
||||||
"location",
|
"location",
|
||||||
|
"uri",
|
||||||
"range",
|
"range",
|
||||||
"selectionRange",
|
"selectionRange",
|
||||||
"targetRange",
|
"targetRange",
|
||||||
|
|
|
@ -9,9 +9,9 @@ pub use comemo::{Track, Tracked};
|
||||||
pub use itertools::{Format, Itertools};
|
pub use itertools::{Format, Itertools};
|
||||||
pub use log::{error, trace};
|
pub use log::{error, trace};
|
||||||
pub use lsp_types::{
|
pub use lsp_types::{
|
||||||
CodeLens, CompletionResponse, DiagnosticRelatedInformation, DocumentSymbol,
|
request::GotoDeclarationResponse, CodeLens, CompletionResponse, DiagnosticRelatedInformation,
|
||||||
DocumentSymbolResponse, Documentation, FoldingRange, GotoDefinitionResponse, Hover, InlayHint,
|
DocumentSymbol, DocumentSymbolResponse, Documentation, FoldingRange, GotoDefinitionResponse,
|
||||||
Location as LspLocation, MarkupContent, MarkupKind, Position as LspPosition,
|
Hover, InlayHint, Location as LspLocation, MarkupContent, MarkupKind, Position as LspPosition,
|
||||||
PrepareRenameResponse, SelectionRange, SemanticTokens, SemanticTokensDelta,
|
PrepareRenameResponse, SelectionRange, SemanticTokens, SemanticTokensDelta,
|
||||||
SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation,
|
SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation,
|
||||||
SymbolInformation, Url, WorkspaceEdit,
|
SymbolInformation, Url, WorkspaceEdit,
|
||||||
|
|
125
crates/tinymist-query/src/references.rs
Normal file
125
crates/tinymist-query/src/references.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use comemo::Track;
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
analysis::{get_def_use, get_deref_target, DerefTarget, IdentRef},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReferencesRequest {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub position: LspPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReferencesRequest {
|
||||||
|
pub fn request(
|
||||||
|
self,
|
||||||
|
world: &TypstSystemWorld,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
|
) -> Option<Vec<LspLocation>> {
|
||||||
|
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
||||||
|
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||||
|
let cursor = offset + 1;
|
||||||
|
|
||||||
|
let w: &dyn World = world;
|
||||||
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
|
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||||
|
let deref_target = get_deref_target(ast_node)?;
|
||||||
|
|
||||||
|
let def_use = get_def_use(w.track(), source.clone())?;
|
||||||
|
let ref_spans = find_declarations(w, def_use, deref_target)?;
|
||||||
|
|
||||||
|
let mut locations = vec![];
|
||||||
|
for ref_range in ref_spans {
|
||||||
|
let ref_id = source.id();
|
||||||
|
let ref_source = &source;
|
||||||
|
|
||||||
|
let span_path = world.path_for_id(ref_id).ok()?;
|
||||||
|
let range = typst_to_lsp::range(ref_range, ref_source, position_encoding);
|
||||||
|
|
||||||
|
let uri = Url::from_file_path(span_path).ok()?;
|
||||||
|
|
||||||
|
locations.push(LspLocation { uri, range });
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("references: {locations:?}");
|
||||||
|
Some(locations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_declarations(
|
||||||
|
_w: &dyn World,
|
||||||
|
def_use: Arc<crate::analysis::DefUseInfo>,
|
||||||
|
deref_target: DerefTarget<'_>,
|
||||||
|
) -> Option<Vec<Range<usize>>> {
|
||||||
|
let node = match deref_target {
|
||||||
|
DerefTarget::VarAccess(node) => node,
|
||||||
|
DerefTarget::Callee(node) => node,
|
||||||
|
DerefTarget::ImportPath(..) => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut may_ident = node.cast::<ast::Expr>()?;
|
||||||
|
let name;
|
||||||
|
loop {
|
||||||
|
match may_ident {
|
||||||
|
ast::Expr::Parenthesized(e) => {
|
||||||
|
may_ident = e.expr();
|
||||||
|
}
|
||||||
|
ast::Expr::FieldAccess(e) => {
|
||||||
|
may_ident = e.target();
|
||||||
|
}
|
||||||
|
ast::Expr::MathIdent(e) => {
|
||||||
|
name = e.get().to_string();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ast::Expr::Ident(e) => {
|
||||||
|
name = e.get().to_string();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident = node.find(may_ident.span())?;
|
||||||
|
|
||||||
|
// todo: if it is exported, find all the references in the workspace
|
||||||
|
let ident_ref = IdentRef {
|
||||||
|
name,
|
||||||
|
range: ident.range(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (id, _) = def_use.get_def(ident.span().id()?, &ident_ref)?;
|
||||||
|
Some(
|
||||||
|
def_use
|
||||||
|
.get_refs(id)
|
||||||
|
.map(|r| r.range.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
// goto_definition
|
||||||
|
snapshot_testing("references", &|world, path| {
|
||||||
|
let source = get_suitable_source_in_workspace(world, &path).unwrap();
|
||||||
|
|
||||||
|
let request = ReferencesRequest {
|
||||||
|
path: path.clone(),
|
||||||
|
position: find_test_position(&source),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = request.request(world, PositionEncoding::Utf16);
|
||||||
|
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -679,6 +679,8 @@ impl CompileActor {
|
||||||
}
|
}
|
||||||
Hover(req) => query_state!(self, Hover, req),
|
Hover(req) => query_state!(self, Hover, req),
|
||||||
GotoDefinition(req) => query_world!(self, GotoDefinition, req),
|
GotoDefinition(req) => query_world!(self, GotoDefinition, req),
|
||||||
|
GotoDeclaration(req) => query_world!(self, GotoDeclaration, req),
|
||||||
|
References(req) => query_world!(self, References, req),
|
||||||
InlayHint(req) => query_world!(self, InlayHint, req),
|
InlayHint(req) => query_world!(self, InlayHint, req),
|
||||||
CodeLens(req) => query_world!(self, CodeLens, req),
|
CodeLens(req) => query_world!(self, CodeLens, req),
|
||||||
Completion(req) => query_state!(self, Completion, req),
|
Completion(req) => query_state!(self, Completion, req),
|
||||||
|
|
|
@ -27,6 +27,10 @@ trait InitializeParamsExt {
|
||||||
fn supports_document_formatting_dynamic_registration(&self) -> bool;
|
fn supports_document_formatting_dynamic_registration(&self) -> bool;
|
||||||
fn line_folding_only(&self) -> bool;
|
fn line_folding_only(&self) -> bool;
|
||||||
fn root_paths(&self) -> Vec<PathBuf>;
|
fn root_paths(&self) -> Vec<PathBuf>;
|
||||||
|
|
||||||
|
// todo: svelte-language-server responds to a Goto Definition request with
|
||||||
|
// LocationLink[] even if the client does not report the
|
||||||
|
// textDocument.definition.linkSupport capability.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InitializeParamsExt for InitializeParams {
|
impl InitializeParamsExt for InitializeParams {
|
||||||
|
@ -586,6 +590,7 @@ impl Init {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
definition_provider: Some(OneOf::Left(true)),
|
definition_provider: Some(OneOf::Left(true)),
|
||||||
|
references_provider: Some(OneOf::Left(true)),
|
||||||
completion_provider: Some(CompletionOptions {
|
completion_provider: Some(CompletionOptions {
|
||||||
trigger_characters: Some(vec![
|
trigger_characters: Some(vec![
|
||||||
String::from("#"),
|
String::from("#"),
|
||||||
|
|
|
@ -48,7 +48,10 @@ use futures::future::BoxFuture;
|
||||||
use log::{error, info, trace, warn};
|
use log::{error, info, trace, warn};
|
||||||
use lsp_server::{ErrorCode, Message, Notification, Request, ResponseError};
|
use lsp_server::{ErrorCode, Message, Notification, Request, ResponseError};
|
||||||
use lsp_types::notification::{Notification as NotificationTrait, PublishDiagnostics};
|
use lsp_types::notification::{Notification as NotificationTrait, PublishDiagnostics};
|
||||||
use lsp_types::request::{RegisterCapability, UnregisterCapability, WorkspaceConfiguration};
|
use lsp_types::request::{
|
||||||
|
GotoDeclarationParams, GotoDeclarationResponse, RegisterCapability, UnregisterCapability,
|
||||||
|
WorkspaceConfiguration,
|
||||||
|
};
|
||||||
use lsp_types::*;
|
use lsp_types::*;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
|
@ -366,6 +369,8 @@ impl TypstLanguageServer {
|
||||||
request_fn!(PrepareRenameRequest, Self::prepare_rename),
|
request_fn!(PrepareRenameRequest, Self::prepare_rename),
|
||||||
request_fn!(Rename, Self::rename),
|
request_fn!(Rename, Self::rename),
|
||||||
request_fn!(GotoDefinition, Self::goto_definition),
|
request_fn!(GotoDefinition, Self::goto_definition),
|
||||||
|
request_fn!(GotoDeclaration, Self::goto_declaration),
|
||||||
|
request_fn!(References, Self::references),
|
||||||
request_fn!(WorkspaceSymbolRequest, Self::symbol),
|
request_fn!(WorkspaceSymbolRequest, Self::symbol),
|
||||||
request_fn!(ExecuteCommand, Self::execute_command),
|
request_fn!(ExecuteCommand, Self::execute_command),
|
||||||
])
|
])
|
||||||
|
@ -842,6 +847,19 @@ impl TypstLanguageServer {
|
||||||
run_query!(self.GotoDefinition(path, position))
|
run_query!(self.GotoDefinition(path, position))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn goto_declaration(
|
||||||
|
&self,
|
||||||
|
params: GotoDeclarationParams,
|
||||||
|
) -> LspResult<Option<GotoDeclarationResponse>> {
|
||||||
|
let (path, position) = as_path_pos(params.text_document_position_params);
|
||||||
|
run_query!(self.GotoDeclaration(path, position))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn references(&self, params: ReferenceParams) -> LspResult<Option<Vec<Location>>> {
|
||||||
|
let (path, position) = as_path_pos(params.text_document_position);
|
||||||
|
run_query!(self.References(path, position))
|
||||||
|
}
|
||||||
|
|
||||||
fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
|
fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
|
||||||
let (path, position) = as_path_pos(params.text_document_position_params);
|
let (path, position) = as_path_pos(params.text_document_position_params);
|
||||||
run_query!(self.Hover(path, position))
|
run_query!(self.Hover(path, position))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue