refactor: improving names of matched structs and documenting matchers (#1022)

This commit is contained in:
Myriad-Dreamin 2024-12-18 16:35:15 +08:00 committed by GitHub
parent 902bd17cba
commit ad0c1e8aca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 391 additions and 412 deletions

View file

@ -104,7 +104,7 @@ mod matcher_tests {
use typst::syntax::LinkedNode; use typst::syntax::LinkedNode;
use typst_shim::syntax::LinkedNodeExt; use typst_shim::syntax::LinkedNodeExt;
use crate::{syntax::get_def_target, tests::*}; use crate::{syntax::classify_def, tests::*};
#[test] #[test]
fn test() { fn test() {
@ -118,7 +118,7 @@ mod matcher_tests {
let root = LinkedNode::new(source.root()); let root = LinkedNode::new(source.root());
let node = root.leaf_at_compat(pos).unwrap(); let node = root.leaf_at_compat(pos).unwrap();
let result = get_def_target(node).map(|e| format!("{:?}", e.node().range())); let result = classify_def(node).map(|e| format!("{:?}", e.node().range()));
let result = result.as_deref().unwrap_or("<nil>"); let result = result.as_deref().unwrap_or("<nil>");
assert_snapshot!(result); assert_snapshot!(result);
@ -436,7 +436,7 @@ mod signature_tests {
use typst_shim::syntax::LinkedNodeExt; use typst_shim::syntax::LinkedNodeExt;
use crate::analysis::{analyze_signature, Signature, SignatureTarget}; use crate::analysis::{analyze_signature, Signature, SignatureTarget};
use crate::syntax::get_deref_target; use crate::syntax::classify_syntax;
use crate::tests::*; use crate::tests::*;
#[test] #[test]
@ -450,7 +450,7 @@ mod signature_tests {
let root = LinkedNode::new(source.root()); let root = LinkedNode::new(source.root());
let callee_node = root.leaf_at_compat(pos).unwrap(); let callee_node = root.leaf_at_compat(pos).unwrap();
let callee_node = get_deref_target(callee_node, pos).unwrap(); let callee_node = classify_syntax(callee_node, pos).unwrap();
let callee_node = callee_node.node(); let callee_node = callee_node.node();
let result = analyze_signature( let result = analyze_signature(

View file

@ -5,7 +5,7 @@ use typst::introspection::Introspector;
use typst::model::BibliographyElem; use typst::model::BibliographyElem;
use super::{prelude::*, InsTy, SharedContext}; use super::{prelude::*, InsTy, SharedContext};
use crate::syntax::{Decl, DeclExpr, DerefTarget, Expr, ExprInfo}; use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass};
use crate::ty::DocSource; use crate::ty::DocSource;
use crate::VersionedDocument; use crate::VersionedDocument;
@ -60,17 +60,21 @@ pub fn definition(
ctx: &Arc<SharedContext>, ctx: &Arc<SharedContext>,
source: &Source, source: &Source,
document: Option<&VersionedDocument>, document: Option<&VersionedDocument>,
deref_target: DerefTarget, syntax: SyntaxClass,
) -> Option<Definition> { ) -> Option<Definition> {
match deref_target { match syntax {
// todi: field access // todi: field access
DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => { SyntaxClass::VarAccess(node) | SyntaxClass::Callee(node) => {
find_ident_definition(ctx, source, node) find_ident_definition(ctx, source, node)
} }
DerefTarget::ImportPath(path) | DerefTarget::IncludePath(path) => { SyntaxClass::ImportPath(path) | SyntaxClass::IncludePath(path) => {
DefResolver::new(ctx, source)?.of_span(path.span()) DefResolver::new(ctx, source)?.of_span(path.span())
} }
DerefTarget::Label(r) | DerefTarget::Ref(r) => { SyntaxClass::Label {
node: r,
is_error: false,
}
| SyntaxClass::Ref(r) => {
let ref_expr: ast::Expr = r.cast()?; let ref_expr: ast::Expr = r.cast()?;
let name = match ref_expr { let name = match ref_expr {
ast::Expr::Ref(r) => r.target(), ast::Expr::Ref(r) => r.target(),
@ -82,7 +86,11 @@ pub fn definition(
find_bib_definition(ctx, introspector, name) find_bib_definition(ctx, introspector, name)
.or_else(|| find_ref_definition(introspector, name, ref_expr)) .or_else(|| find_ref_definition(introspector, name, ref_expr))
} }
DerefTarget::LabelError(..) | DerefTarget::Normal(..) => None, SyntaxClass::Label {
node: _,
is_error: true,
}
| SyntaxClass::Normal(..) => None,
} }
} }

View file

@ -32,8 +32,8 @@ use crate::analysis::{
}; };
use crate::docs::{DefDocs, TidyModuleDocs}; use crate::docs::{DefDocs, TidyModuleDocs};
use crate::syntax::{ use crate::syntax::{
construct_module_dependencies, get_deref_target, resolve_id_by_path, scan_workspace_files, classify_syntax, construct_module_dependencies, resolve_id_by_path, scan_workspace_files, Decl,
Decl, DefKind, DerefTarget, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, DefKind, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, SyntaxClass,
}; };
use crate::upstream::{tooltip_, CompletionFeat, Tooltip}; use crate::upstream::{tooltip_, CompletionFeat, Tooltip};
use crate::{ use crate::{
@ -562,36 +562,39 @@ impl SharedContext {
self.source_by_id(self.file_id_by_path(p)?) self.source_by_id(self.file_id_by_path(p)?)
} }
/// Get a syntax object at a position. /// Classifies the syntax under span that can be operated on by IDE
pub fn deref_syntax<'s>(&self, source: &'s Source, span: Span) -> Option<DerefTarget<'s>> { /// functionality.
pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
let node = LinkedNode::new(source.root()).find(span)?; let node = LinkedNode::new(source.root()).find(span)?;
let cursor = node.offset() + 1; let cursor = node.offset() + 1;
get_deref_target(node, cursor) classify_syntax(node, cursor)
} }
/// Get a syntax object at a position. /// Classifies the syntax under position that can be operated on by IDE
pub fn deref_syntax_at<'s>( /// functionality.
pub fn classify_pos<'s>(
&self, &self,
source: &'s Source, source: &'s Source,
position: LspPosition, position: LspPosition,
shift: usize, shift: usize,
) -> Option<DerefTarget<'s>> { ) -> Option<SyntaxClass<'s>> {
let (_, deref_target) = self.deref_syntax_at_(source, position, shift)?; let (_, expr) = self.classify_pos_(source, position, shift)?;
deref_target expr
} }
/// Get a syntax object at a position. /// Classifies the syntax under position that can be operated on by IDE
pub fn deref_syntax_at_<'s>( /// functionality.
pub fn classify_pos_<'s>(
&self, &self,
source: &'s Source, source: &'s Source,
position: LspPosition, position: LspPosition,
shift: usize, shift: usize,
) -> Option<(usize, Option<DerefTarget<'s>>)> { ) -> Option<(usize, Option<SyntaxClass<'s>>)> {
let offset = self.to_typst_pos(position, source)?; let offset = self.to_typst_pos(position, source)?;
let cursor = ceil_char_boundary(source.text(), offset + shift); let cursor = ceil_char_boundary(source.text(), offset + shift);
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
Some((cursor, get_deref_target(node, cursor))) Some((cursor, classify_syntax(node, cursor)))
} }
/// Get the real definition of a compilation. /// Get the real definition of a compilation.
@ -784,8 +787,8 @@ impl SharedContext {
doc: Option<&VersionedDocument>, doc: Option<&VersionedDocument>,
span: Span, span: Span,
) -> Option<Definition> { ) -> Option<Definition> {
let target = self.deref_syntax(source, span)?; let expr = self.classify_span(source, span)?;
definition(self, source, doc, target) definition(self, source, doc, expr)
} }
pub(crate) fn def_of_decl(&self, decl: &Interned<Decl>) -> Option<Definition> { pub(crate) fn def_of_decl(&self, decl: &Interned<Decl>) -> Option<Definition> {
@ -800,9 +803,9 @@ impl SharedContext {
self: &Arc<Self>, self: &Arc<Self>,
source: &Source, source: &Source,
doc: Option<&VersionedDocument>, doc: Option<&VersionedDocument>,
deref_target: DerefTarget, syntax: SyntaxClass,
) -> Option<Definition> { ) -> Option<Definition> {
definition(self, source, doc, deref_target) definition(self, source, doc, syntax)
} }
pub(crate) fn type_of_span(self: &Arc<Self>, s: Span) -> Option<Ty> { pub(crate) fn type_of_span(self: &Arc<Self>, s: Span) -> Option<Ty> {

View file

@ -8,7 +8,7 @@ use super::{
ArgsTy, Sig, SigChecker, SigShape, SigSurfaceKind, SigTy, Ty, TyCtx, TyCtxMut, TypeBounds, ArgsTy, Sig, SigChecker, SigShape, SigSurfaceKind, SigTy, Ty, TyCtx, TyCtxMut, TypeBounds,
TypeScheme, TypeVar, TypeScheme, TypeVar,
}; };
use crate::syntax::{get_check_target, get_check_target_by_context, CheckTarget, ParamTarget}; use crate::syntax::{classify_cursor, classify_cursor_by_context, ArgClass, CursorClass};
use crate::ty::BuiltinTy; use crate::ty::BuiltinTy;
/// With given type information, check the type of a literal expression again by /// With given type information, check the type of a literal expression again by
@ -49,7 +49,7 @@ impl SignatureReceiver {
fn check_signature<'a>( fn check_signature<'a>(
receiver: &'a mut SignatureReceiver, receiver: &'a mut SignatureReceiver,
target: &'a ParamTarget, arg: &'a ArgClass,
) -> impl FnMut(&mut PostTypeChecker, Sig, &[Interned<ArgsTy>], bool) -> Option<()> + 'a { ) -> impl FnMut(&mut PostTypeChecker, Sig, &[Interned<ArgsTy>], bool) -> Option<()> + 'a {
move |worker, sig, args, pol| { move |worker, sig, args, pol| {
let (sig, _is_partialize) = match sig { let (sig, _is_partialize) = match sig {
@ -59,15 +59,15 @@ fn check_signature<'a>(
let SigShape { sig: sig_ins, .. } = sig.shape(worker)?; let SigShape { sig: sig_ins, .. } = sig.shape(worker)?;
match &target { match &arg {
ParamTarget::Named(n) => { ArgClass::Named(n) => {
let ident = n.cast::<ast::Ident>()?; let ident = n.cast::<ast::Ident>()?;
let ty = sig_ins.named(&ident.into())?; let ty = sig_ins.named(&ident.into())?;
receiver.insert(ty.clone(), !pol); receiver.insert(ty.clone(), !pol);
Some(()) Some(())
} }
ParamTarget::Positional { ArgClass::Positional {
// todo: spreads // todo: spreads
spreads: _, spreads: _,
positional, positional,
@ -182,7 +182,7 @@ impl<'a> PostTypeChecker<'a> {
None None
}; };
let contextual_self_ty = self.check_target(get_check_target(node.clone()), context_ty); let contextual_self_ty = self.check_cursor(classify_cursor(node.clone()), context_ty);
crate::log_debug_ct!( crate::log_debug_ct!(
"post check(res): {:?}::{:?} -> {self_ty:?}, {contextual_self_ty:?}", "post check(res): {:?}::{:?} -> {self_ty:?}, {contextual_self_ty:?}",
context.kind(), context.kind(),
@ -196,14 +196,14 @@ impl<'a> PostTypeChecker<'a> {
Ty::union(self.check(node), ty) Ty::union(self.check(node), ty)
} }
fn check_target(&mut self, node: Option<CheckTarget>, context_ty: Option<Ty>) -> Option<Ty> { fn check_cursor(&mut self, cursor: Option<CursorClass>, context_ty: Option<Ty>) -> Option<Ty> {
let Some(node) = node else { let Some(cursor) = cursor else {
return context_ty; return context_ty;
}; };
crate::log_debug_ct!("post check target: {node:?}"); crate::log_debug_ct!("post check target: {cursor:?}");
match &node { match &cursor {
CheckTarget::Param { CursorClass::Arg {
callee, callee,
args: _, args: _,
target, target,
@ -219,13 +219,13 @@ impl<'a> PostTypeChecker<'a> {
let mut resp = SignatureReceiver::default(); let mut resp = SignatureReceiver::default();
match target { match target {
ParamTarget::Named(n) => { ArgClass::Named(n) => {
let ident = n.cast::<ast::Ident>()?.into(); let ident = n.cast::<ast::Ident>()?.into();
let ty = sig.primary().get_named(&ident)?; let ty = sig.primary().get_named(&ident)?;
// todo: losing docs // todo: losing docs
resp.insert(ty.ty.clone(), false); resp.insert(ty.ty.clone(), false);
} }
ParamTarget::Positional { ArgClass::Positional {
// todo: spreads // todo: spreads
spreads: _, spreads: _,
positional, positional,
@ -259,7 +259,7 @@ impl<'a> PostTypeChecker<'a> {
crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds); crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds);
Some(resp.finalize()) Some(resp.finalize())
} }
CheckTarget::Element { container, target } => { CursorClass::Element { container, target } => {
let container_ty = self.check_or(container, context_ty)?; let container_ty = self.check_or(container, context_ty)?;
crate::log_debug_ct!("post check element target: ({container_ty:?})::{target:?}"); crate::log_debug_ct!("post check element target: ({container_ty:?})::{target:?}");
@ -275,7 +275,7 @@ impl<'a> PostTypeChecker<'a> {
crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds); crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds);
Some(resp.finalize()) Some(resp.finalize())
} }
CheckTarget::Paren { CursorClass::Paren {
container, container,
is_before, is_before,
} => { } => {
@ -287,7 +287,7 @@ impl<'a> PostTypeChecker<'a> {
// e.g. completing `""` on `let x = ("|")` // e.g. completing `""` on `let x = ("|")`
resp.bounds.lbs.push(container_ty.clone()); resp.bounds.lbs.push(container_ty.clone());
let target = ParamTarget::positional_from_before(true); let target = ArgClass::positional_from_before(true);
self.check_element_of( self.check_element_of(
&container_ty, &container_ty,
false, false,
@ -298,15 +298,13 @@ impl<'a> PostTypeChecker<'a> {
crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds); crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds);
Some(resp.finalize()) Some(resp.finalize())
} }
CheckTarget::ImportPath(..) | CheckTarget::IncludePath(..) => Some(Ty::Builtin( CursorClass::ImportPath(..) | CursorClass::IncludePath(..) => Some(Ty::Builtin(
BuiltinTy::Path(crate::ty::PathPreference::Source { BuiltinTy::Path(crate::ty::PathPreference::Source {
allow_package: true, allow_package: true,
}), }),
)), )),
CheckTarget::LabelError(target) CursorClass::Label { node: target, .. } | CursorClass::Normal(target) => {
| CheckTarget::Label(target) let label_ty = matches!(cursor, CursorClass::Label { is_error: true, .. })
| CheckTarget::Normal(target) => {
let label_ty = matches!(node, CheckTarget::LabelError(_))
.then_some(Ty::Builtin(BuiltinTy::Label)); .then_some(Ty::Builtin(BuiltinTy::Label));
let ty = self.check_or(target, context_ty); let ty = self.check_or(target, context_ty);
crate::log_debug_ct!("post check target normal: {ty:?} {label_ty:?}"); crate::log_debug_ct!("post check target normal: {ty:?} {label_ty:?}");
@ -331,13 +329,13 @@ impl<'a> PostTypeChecker<'a> {
} }
} }
} }
SyntaxKind::Args => self.check_target( SyntaxKind::Args => self.check_cursor(
// todo: not well behaved // todo: not well behaved
get_check_target_by_context(context.clone(), node.clone()), classify_cursor_by_context(context.clone(), node.clone()),
None, None,
), ),
// todo: constraint node // todo: constraint node
SyntaxKind::Named => self.check_target(get_check_target(context.clone()), None), SyntaxKind::Named => self.check_cursor(classify_cursor(context.clone()), None),
_ => None, _ => None,
} }
} }

View file

@ -10,7 +10,7 @@ use super::{
}; };
use crate::analysis::PostTypeChecker; use crate::analysis::PostTypeChecker;
use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs}; use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs};
use crate::syntax::get_non_strict_def_target; use crate::syntax::classify_def_loosely;
use crate::ty::{DynTypeBounds, ParamAttrs}; use crate::ty::{DynTypeBounds, ParamAttrs};
use crate::ty::{InsTy, TyCtx}; use crate::ty::{InsTy, TyCtx};
use crate::upstream::truncated_repr; use crate::upstream::truncated_repr;
@ -205,7 +205,7 @@ fn analyze_type_signature(
SignatureTarget::Runtime(f) => { SignatureTarget::Runtime(f) => {
let source = ctx.source_by_id(f.span().id()?).ok()?; let source = ctx.source_by_id(f.span().id()?).ok()?;
let node = source.find(f.span())?; let node = source.find(f.span())?;
let def = get_non_strict_def_target(node.parent()?.clone())?; let def = classify_def_loosely(node.parent()?.clone())?;
let type_info = ctx.type_check(&source); let type_info = ctx.type_check(&source);
let ty = type_info.type_of_span(def.name()?.span())?; let ty = type_info.type_of_span(def.name()?.span())?;
Some((type_info, ty)) Some((type_info, ty))

View file

@ -9,7 +9,7 @@ use typst_shim::syntax::LinkedNodeExt;
use crate::{ use crate::{
analysis::{InsTy, Ty}, analysis::{InsTy, Ty},
prelude::*, prelude::*,
syntax::{is_ident_like, DerefTarget}, syntax::{is_ident_like, SyntaxClass},
upstream::{autocomplete, CompletionContext}, upstream::{autocomplete, CompletionContext},
StatefulRequest, StatefulRequest,
}; };
@ -71,7 +71,7 @@ impl StatefulRequest for CompletionRequest {
let doc = doc.as_ref().map(|doc| doc.document.as_ref()); let doc = doc.as_ref().map(|doc| doc.document.as_ref());
let source = ctx.source_by_path(&self.path).ok()?; let source = ctx.source_by_path(&self.path).ok()?;
let (cursor, deref_target) = ctx.deref_syntax_at_(&source, self.position, 0)?; let (cursor, syntax) = ctx.classify_pos_(&source, self.position, 0)?;
// Please see <https://github.com/nvarner/typst-lsp/commit/2d66f26fb96ceb8e485f492e5b81e9db25c3e8ec> // Please see <https://github.com/nvarner/typst-lsp/commit/2d66f26fb96ceb8e485f492e5b81e9db25c3e8ec>
// //
@ -89,7 +89,7 @@ impl StatefulRequest for CompletionRequest {
let explicit = false; let explicit = false;
// Skip if is the let binding item *directly* // Skip if is the let binding item *directly*
if let Some(DerefTarget::VarAccess(node)) = &deref_target { if let Some(SyntaxClass::VarAccess(node)) = &syntax {
match node.parent_kind() { match node.parent_kind() {
// complete the init part of the let binding // complete the init part of the let binding
Some(SyntaxKind::LetBinding) => { Some(SyntaxKind::LetBinding) => {
@ -110,8 +110,8 @@ impl StatefulRequest for CompletionRequest {
// Skip if an error node starts with number (e.g. `1pt`) // Skip if an error node starts with number (e.g. `1pt`)
if matches!( if matches!(
deref_target, syntax,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..) | DerefTarget::Normal(..)) Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..))
) { ) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if node.erroneous() { if node.erroneous() {
@ -157,10 +157,10 @@ impl StatefulRequest for CompletionRequest {
// Filter and determine range to replace // Filter and determine range to replace
let mut from_ident = None; let mut from_ident = None;
let is_callee = matches!(deref_target, Some(DerefTarget::Callee(..))); let is_callee = matches!(syntax, Some(SyntaxClass::Callee(..)));
if matches!( if matches!(
deref_target, syntax,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..)) Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..))
) { ) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if is_ident_like(&node) && node.offset() == offset { if is_ident_like(&node) && node.offset() == offset {

View file

@ -1,6 +1,6 @@
use std::ops::Range; use std::ops::Range;
use crate::{prelude::*, syntax::DerefTarget, SemanticRequest}; use crate::{prelude::*, syntax::SyntaxClass, SemanticRequest};
/// The [`textDocument/declaration`] request asks the server for the declaration /// The [`textDocument/declaration`] request asks the server for the declaration
/// location of a symbol at a given text document position. /// location of a symbol at a given text document position.
@ -39,7 +39,7 @@ impl SemanticRequest for GotoDeclarationRequest {
fn find_declarations( fn find_declarations(
_ctx: &LocalContext, _ctx: &LocalContext,
_expr_info: Arc<crate::syntax::ExprInfo>, _expr_info: Arc<crate::syntax::ExprInfo>,
_deref_target: DerefTarget<'_>, _syntax: SyntaxClass<'_>,
) -> Option<Vec<Range<usize>>> { ) -> Option<Vec<Range<usize>>> {
todo!() todo!()
} }

View file

@ -32,10 +32,10 @@ impl StatefulRequest for GotoDefinitionRequest {
doc: Option<VersionedDocument>, doc: Option<VersionedDocument>,
) -> Option<Self::Response> { ) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?; let source = ctx.source_by_path(&self.path).ok()?;
let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; let syntax = ctx.classify_pos(&source, self.position, 1)?;
let origin_selection_range = ctx.to_lsp_range(deref_target.node().range(), &source); let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target)?; let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax)?;
let (fid, def_range) = def.def_at(ctx.shared())?; let (fid, def_range) = def.def_at(ctx.shared())?;
let uri = ctx.uri_for_id(fid).ok()?; let uri = ctx.uri_for_id(fid).ok()?;

View file

@ -117,8 +117,8 @@ fn def_tooltip(
cursor: usize, cursor: usize,
) -> Option<HoverContents> { ) -> Option<HoverContents> {
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
let deref_target = get_deref_target(leaf.clone(), cursor)?; let syntax = classify_syntax(leaf.clone(), cursor)?;
let def = ctx.def_of_syntax(source, document, deref_target.clone())?; let def = ctx.def_of_syntax(source, document, syntax.clone())?;
let mut results = vec![]; let mut results = vec![];
let mut actions = vec![]; let mut actions = vec![];
@ -147,7 +147,7 @@ fn def_tooltip(
if matches!(def.decl.kind(), DefKind::Variable | DefKind::Constant) { if matches!(def.decl.kind(), DefKind::Variable | DefKind::Constant) {
// todo: check sensible length, value highlighting // todo: check sensible length, value highlighting
if let Some(values) = expr_tooltip(ctx.world(), deref_target.node()) { if let Some(values) = expr_tooltip(ctx.world(), syntax.node()) {
match values { match values {
Tooltip::Text(values) => { Tooltip::Text(values) => {
results.push(MarkedString::String(values.into())); results.push(MarkedString::String(values.into()));

View file

@ -33,6 +33,6 @@ pub use crate::lsp_typst_boundary::{
lsp_to_typst, path_to_url, typst_to_lsp, LspDiagnostic, LspRange, LspSeverity, lsp_to_typst, path_to_url, typst_to_lsp, LspDiagnostic, LspRange, LspSeverity,
PositionEncoding, TypstDiagnostic, TypstSeverity, TypstSpan, PositionEncoding, TypstDiagnostic, TypstSeverity, TypstSpan,
}; };
pub use crate::syntax::{get_deref_target, Decl, DefKind}; pub use crate::syntax::{classify_syntax, Decl, DefKind};
pub(crate) use crate::ty::PathPreference; pub(crate) use crate::ty::PathPreference;
pub use crate::{SemanticRequest, StatefulRequest, VersionedDocument}; pub use crate::{SemanticRequest, StatefulRequest, VersionedDocument};

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
analysis::Definition, analysis::Definition,
prelude::*, prelude::*,
syntax::{Decl, DerefTarget}, syntax::{Decl, SyntaxClass},
}; };
/// The [`textDocument/prepareRename`] request is sent from the client to the /// The [`textDocument/prepareRename`] request is sent from the client to the
@ -38,17 +38,17 @@ impl StatefulRequest for PrepareRenameRequest {
doc: Option<VersionedDocument>, doc: Option<VersionedDocument>,
) -> Option<Self::Response> { ) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?; let source = ctx.source_by_path(&self.path).ok()?;
let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; let syntax = ctx.classify_pos(&source, self.position, 1)?;
if matches!(deref_target.node().kind(), SyntaxKind::FieldAccess) { if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
// todo: rename field access // todo: rename field access
log::info!("prepare_rename: field access is not a definition site"); log::info!("prepare_rename: field access is not a definition site");
return None; return None;
} }
let origin_selection_range = ctx.to_lsp_range(deref_target.node().range(), &source); let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target.clone())?; let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
let (name, range) = prepare_renaming(ctx, &deref_target, &def)?; let (name, range) = prepare_renaming(ctx, &syntax, &def)?;
Some(PrepareRenameResponse::RangeWithPlaceholder { Some(PrepareRenameResponse::RangeWithPlaceholder {
range: range.unwrap_or(origin_selection_range), range: range.unwrap_or(origin_selection_range),
@ -59,7 +59,7 @@ impl StatefulRequest for PrepareRenameRequest {
pub(crate) fn prepare_renaming( pub(crate) fn prepare_renaming(
ctx: &mut LocalContext, ctx: &mut LocalContext,
deref_target: &DerefTarget, deref_target: &SyntaxClass,
def: &Definition, def: &Definition,
) -> Option<(String, Option<LspRange>)> { ) -> Option<(String, Option<LspRange>)> {
let name = def.name().clone(); let name = def.name().clone();

View file

@ -5,7 +5,7 @@ use typst::syntax::Span;
use crate::{ use crate::{
analysis::{Definition, SearchCtx}, analysis::{Definition, SearchCtx},
prelude::*, prelude::*,
syntax::{get_index_info, DerefTarget, RefExpr}, syntax::{get_index_info, RefExpr, SyntaxClass},
ty::Interned, ty::Interned,
}; };
@ -31,9 +31,9 @@ impl StatefulRequest for ReferencesRequest {
doc: Option<VersionedDocument>, doc: Option<VersionedDocument>,
) -> Option<Self::Response> { ) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?; let source = ctx.source_by_path(&self.path).ok()?;
let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; let syntax = ctx.classify_pos(&source, self.position, 1)?;
let locations = find_references(ctx, &source, doc.as_ref(), deref_target)?; let locations = find_references(ctx, &source, doc.as_ref(), syntax)?;
crate::log_debug_ct!("references: {locations:?}"); crate::log_debug_ct!("references: {locations:?}");
Some(locations) Some(locations)
@ -44,17 +44,17 @@ pub(crate) fn find_references(
ctx: &mut LocalContext, ctx: &mut LocalContext,
source: &Source, source: &Source,
doc: Option<&VersionedDocument>, doc: Option<&VersionedDocument>,
target: DerefTarget<'_>, syntax: SyntaxClass<'_>,
) -> Option<Vec<LspLocation>> { ) -> Option<Vec<LspLocation>> {
let finding_label = match target { let finding_label = match syntax {
DerefTarget::VarAccess(..) | DerefTarget::Callee(..) => false, SyntaxClass::VarAccess(..) | SyntaxClass::Callee(..) => false,
DerefTarget::Label(..) | DerefTarget::LabelError(..) | DerefTarget::Ref(..) => true, SyntaxClass::Label { .. } | SyntaxClass::Ref(..) => true,
DerefTarget::ImportPath(..) | DerefTarget::IncludePath(..) | DerefTarget::Normal(..) => { SyntaxClass::ImportPath(..) | SyntaxClass::IncludePath(..) | SyntaxClass::Normal(..) => {
return None; return None;
} }
}; };
let def = ctx.def_of_syntax(source, doc, target)?; let def = ctx.def_of_syntax(source, doc, syntax)?;
let worker = ReferencesWorker { let worker = ReferencesWorker {
ctx: ctx.fork_for_search(), ctx: ctx.fork_for_search(),

View file

@ -14,7 +14,7 @@ use crate::{
find_references, find_references,
prelude::*, prelude::*,
prepare_renaming, prepare_renaming,
syntax::{deref_expr, get_index_info, node_ancestors, Decl, DerefTarget, RefExpr}, syntax::{deref_expr, get_index_info, node_ancestors, Decl, RefExpr, SyntaxClass},
ty::Interned, ty::Interned,
}; };
@ -42,15 +42,15 @@ impl StatefulRequest for RenameRequest {
doc: Option<VersionedDocument>, doc: Option<VersionedDocument>,
) -> Option<Self::Response> { ) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?; let source = ctx.source_by_path(&self.path).ok()?;
let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; let syntax = ctx.classify_pos(&source, self.position, 1)?;
let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target.clone())?; let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
prepare_renaming(ctx, &deref_target, &def)?; prepare_renaming(ctx, &syntax, &def)?;
match deref_target { match syntax {
// todo: abs path // todo: abs path
DerefTarget::ImportPath(path) | DerefTarget::IncludePath(path) => { SyntaxClass::ImportPath(path) | SyntaxClass::IncludePath(path) => {
let ref_path_str = path.cast::<ast::Str>()?.get(); let ref_path_str = path.cast::<ast::Str>()?.get();
let new_path_str = if !self.new_name.ends_with(".typ") { let new_path_str = if !self.new_name.ends_with(".typ") {
self.new_name + ".typ" self.new_name + ".typ"
@ -94,7 +94,7 @@ impl StatefulRequest for RenameRequest {
}) })
} }
_ => { _ => {
let references = find_references(ctx, &source, doc.as_ref(), deref_target)?; let references = find_references(ctx, &source, doc.as_ref(), syntax)?;
let mut edits = HashMap::new(); let mut edits = HashMap::new();

View file

@ -4,7 +4,7 @@ use typst_shim::syntax::LinkedNodeExt;
use crate::{ use crate::{
adt::interner::Interned, adt::interner::Interned,
prelude::*, prelude::*,
syntax::{get_check_target, get_deref_target, CheckTarget, ParamTarget}, syntax::{classify_cursor, classify_syntax, ArgClass, CursorClass},
LspParamInfo, SemanticRequest, LspParamInfo, SemanticRequest,
}; };
@ -28,18 +28,18 @@ impl SemanticRequest for SignatureHelpRequest {
let cursor = ctx.to_typst_pos(self.position, &source)? + 1; let cursor = ctx.to_typst_pos(self.position, &source)? + 1;
let ast_node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let ast_node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
let CheckTarget::Param { let CursorClass::Arg {
callee, callee,
target, target,
is_set, is_set,
.. ..
} = get_check_target(ast_node)? } = classify_cursor(ast_node)?
else { else {
return None; return None;
}; };
let deref_target = get_deref_target(callee, cursor)?; let syntax = classify_syntax(callee, cursor)?;
let def = ctx.def_of_syntax(&source, None, deref_target)?; let def = ctx.def_of_syntax(&source, None, syntax)?;
let sig = ctx.sig_of_def(def.clone())?; let sig = ctx.sig_of_def(def.clone())?;
crate::log_debug_ct!("got signature {sig:?}"); crate::log_debug_ct!("got signature {sig:?}");
@ -59,13 +59,13 @@ impl SemanticRequest for SignatureHelpRequest {
} }
match &target { match &target {
ParamTarget::Positional { .. } if is_set => {} ArgClass::Positional { .. } if is_set => {}
ParamTarget::Positional { positional, .. } => { ArgClass::Positional { positional, .. } => {
if (*positional) + param_shift == i { if (*positional) + param_shift == i {
active_parameter = Some(real_offset); active_parameter = Some(real_offset);
} }
} }
ParamTarget::Named(name) => { ArgClass::Named(name) => {
let focus_name = focus_name let focus_name = focus_name
.get_or_init(|| Interned::new_str(&name.get().clone().into_text())); .get_or_init(|| Interned::new_str(&name.get().clone().into_text()));
if focus_name == &param.name { if focus_name == &param.name {
@ -106,7 +106,7 @@ impl SemanticRequest for SignatureHelpRequest {
label.push_str(ret_ty.describe().as_deref().unwrap_or("any")); label.push_str(ret_ty.describe().as_deref().unwrap_or("any"));
} }
if matches!(target, ParamTarget::Positional { .. }) { if matches!(target, ArgClass::Positional { .. }) {
active_parameter = active_parameter =
active_parameter.map(|x| x.min(sig.primary().pos_size().saturating_sub(1))); active_parameter.map(|x| x.min(sig.primary().pos_size().saturating_sub(1)));
} }

View file

@ -1,59 +1,46 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typst::foundations::{Func, ParamInfo};
use crate::prelude::*; use crate::prelude::*;
pub fn deref_expr(mut ancestor: LinkedNode) -> Option<LinkedNode> { /// Finds the ancestors of a node lazily.
while !ancestor.is::<ast::Expr>() {
ancestor = ancestor.parent()?.clone();
}
Some(ancestor)
}
pub fn deref_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
while let Some(e) = node.cast::<ast::Parenthesized>() {
node = node.find(e.expr().span())?;
}
if let Some(e) = node.parent() {
if let Some(f) = e.cast::<ast::FieldAccess>() {
if node.span() == f.field().span() {
return Some(e.clone());
}
}
}
Some(node)
}
pub fn node_ancestors<'a, 'b>( pub fn node_ancestors<'a, 'b>(
node: &'b LinkedNode<'a>, node: &'b LinkedNode<'a>,
) -> impl Iterator<Item = &'b LinkedNode<'a>> { ) -> impl Iterator<Item = &'b LinkedNode<'a>> {
std::iter::successors(Some(node), |node| node.parent()) std::iter::successors(Some(node), |node| node.parent())
} }
pub enum DecenderItem<'a> { /// Finds the expression target.
pub fn deref_expr(node: LinkedNode) -> Option<LinkedNode> {
node_ancestors(&node).find(|n| n.is::<ast::Expr>()).cloned()
}
/// A descent syntax item.
pub enum DescentItem<'a> {
/// When the iterator is on a sibling node.
Sibling(&'a LinkedNode<'a>), Sibling(&'a LinkedNode<'a>),
/// When the iterator is crossing a parent node.
Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>), Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
} }
impl<'a> DecenderItem<'a> { impl<'a> DescentItem<'a> {
pub fn node(&self) -> &'a LinkedNode<'a> { pub fn node(&self) -> &'a LinkedNode<'a> {
match self { match self {
DecenderItem::Sibling(node) => node, DescentItem::Sibling(node) => node,
DecenderItem::Parent(node, _) => node, DescentItem::Parent(node, _) => node,
} }
} }
} }
/// Find the decender nodes starting from the given position. /// Finds the descent items starting from the given position.
pub fn node_descenders<T>( pub fn descent_items<T>(
node: LinkedNode, node: LinkedNode,
mut recv: impl FnMut(DecenderItem) -> Option<T>, mut recv: impl FnMut(DescentItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
let mut ancestor = Some(node); let mut ancestor = Some(node);
while let Some(node) = &ancestor { while let Some(node) = &ancestor {
let mut sibling = Some(node.clone()); let mut sibling = Some(node.clone());
while let Some(node) = &sibling { while let Some(node) = &sibling {
if let Some(v) = recv(DecenderItem::Sibling(node)) { if let Some(v) = recv(DescentItem::Sibling(node)) {
return Some(v); return Some(v);
} }
@ -61,7 +48,7 @@ pub fn node_descenders<T>(
} }
if let Some(parent) = node.parent() { if let Some(parent) = node.parent() {
if let Some(v) = recv(DecenderItem::Parent(parent, node)) { if let Some(v) = recv(DescentItem::Parent(parent, node)) {
return Some(v); return Some(v);
} }
@ -81,21 +68,21 @@ pub enum DescentDecl<'a> {
ImportAll(ast::ModuleImport<'a>), ImportAll(ast::ModuleImport<'a>),
} }
/// Find the descending decls starting from the given position. /// Finds the descent decls starting from the given position.
pub fn descending_decls<T>( pub fn descent_decls<T>(
node: LinkedNode, node: LinkedNode,
mut recv: impl FnMut(DescentDecl) -> Option<T>, mut recv: impl FnMut(DescentDecl) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
node_descenders(node, |node| { descent_items(node, |node| {
match (&node, node.node().cast::<ast::Expr>()?) { match (&node, node.node().cast::<ast::Expr>()?) {
(DecenderItem::Sibling(..), ast::Expr::Let(lb)) => { (DescentItem::Sibling(..), ast::Expr::Let(lb)) => {
for ident in lb.kind().bindings() { for ident in lb.kind().bindings() {
if let Some(t) = recv(DescentDecl::Ident(ident)) { if let Some(t) = recv(DescentDecl::Ident(ident)) {
return Some(t); return Some(t);
} }
} }
} }
(DecenderItem::Sibling(..), ast::Expr::Import(mi)) => { (DescentItem::Sibling(..), ast::Expr::Import(mi)) => {
// import items // import items
match mi.imports() { match mi.imports() {
Some(ast::Imports::Wildcard) => { Some(ast::Imports::Wildcard) => {
@ -124,7 +111,7 @@ pub fn descending_decls<T>(
} }
} }
} }
(DecenderItem::Parent(node, child), ast::Expr::For(f)) => { (DescentItem::Parent(node, child), ast::Expr::For(f)) => {
let body = node.find(f.body().span()); let body = node.find(f.body().span());
let in_body = body.is_some_and(|n| n.find(child.span()).is_some()); let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
if !in_body { if !in_body {
@ -137,7 +124,7 @@ pub fn descending_decls<T>(
} }
} }
} }
(DecenderItem::Parent(node, child), ast::Expr::Closure(c)) => { (DescentItem::Parent(node, child), ast::Expr::Closure(c)) => {
let body = node.find(c.body().span()); let body = node.find(c.body().span());
let in_body = body.is_some_and(|n| n.find(child.span()).is_some()); let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
if !in_body { if !in_body {
@ -174,46 +161,25 @@ pub fn descending_decls<T>(
}) })
} }
/// Whether the node can be recognized as a mark.
fn is_mark(sk: SyntaxKind) -> bool { fn is_mark(sk: SyntaxKind) -> bool {
use SyntaxKind::*; use SyntaxKind::*;
matches!( #[allow(clippy::match_like_matches_macro)]
sk, match sk {
MathAlignPoint MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
| Plus Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
| Minus LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
| Slash Slash | Hat | Comma | Semicolon | Colon | Hash => true,
| Hat _ => false,
| Dot }
| Eq
| EqEq
| ExclEq
| Lt
| LtEq
| Gt
| GtEq
| PlusEq
| HyphEq
| StarEq
| SlashEq
| Dots
| Arrow
| Not
| And
| Or
| LeftBrace
| RightBrace
| LeftBracket
| RightBracket
| LeftParen
| RightParen
| Comma
| Semicolon
| Colon
| Hash
)
} }
/// Whether the node can be recognized as an identifier.
pub fn is_ident_like(node: &SyntaxNode) -> bool { pub fn is_ident_like(node: &SyntaxNode) -> bool {
fn can_be_ident(node: &SyntaxNode) -> bool {
typst::syntax::is_ident(node.text())
}
use SyntaxKind::*; use SyntaxKind::*;
let k = node.kind(); let k = node.kind();
matches!(k, Ident | MathIdent | Underscore) matches!(k, Ident | MathIdent | Underscore)
@ -221,10 +187,6 @@ pub fn is_ident_like(node: &SyntaxNode) -> bool {
|| k.is_keyword() || k.is_keyword()
} }
fn can_be_ident(node: &SyntaxNode) -> bool {
typst::syntax::is_ident(node.text())
}
/// A mode in which a text document is interpreted. /// A mode in which a text document is interpreted.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -243,6 +205,23 @@ pub enum InterpretMode {
Math, Math,
} }
/// Determine the interpretation mode at the given position (context-sensitive).
pub(crate) fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode {
loop {
crate::log_debug_ct!("leaf for context: {leaf:?}");
if let Some(t) = leaf {
if let Some(mode) = interpret_mode_at_kind(t.kind()) {
break mode;
}
leaf = t.parent();
} else {
break InterpretMode::Markup;
}
}
}
/// Determine the interpretation mode at the given kind (context-free).
pub(crate) fn interpret_mode_at_kind(k: SyntaxKind) -> Option<InterpretMode> { pub(crate) fn interpret_mode_at_kind(k: SyntaxKind) -> Option<InterpretMode> {
use SyntaxKind::*; use SyntaxKind::*;
Some(match k { Some(match k {
@ -273,51 +252,62 @@ pub(crate) fn interpret_mode_at_kind(k: SyntaxKind) -> Option<InterpretMode> {
}) })
} }
pub(crate) fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode { /// Classes of syntax that can be operated on by IDE functionality.
loop {
crate::log_debug_ct!("leaf for context: {leaf:?}");
if let Some(t) = leaf {
if let Some(mode) = interpret_mode_at_kind(t.kind()) {
break mode;
}
leaf = t.parent();
} else {
break InterpretMode::Markup;
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DerefTarget<'a> { pub enum SyntaxClass<'a> {
Label(LinkedNode<'a>), /// A variable access expression.
LabelError(LinkedNode<'a>), ///
Ref(LinkedNode<'a>), /// It can be either an identifier or a field access.
VarAccess(LinkedNode<'a>), VarAccess(LinkedNode<'a>),
/// A (content) label expression.
Label {
node: LinkedNode<'a>,
is_error: bool,
},
/// A (content) reference expression.
Ref(LinkedNode<'a>),
/// A callee expression.
Callee(LinkedNode<'a>), Callee(LinkedNode<'a>),
/// An import path expression.
ImportPath(LinkedNode<'a>), ImportPath(LinkedNode<'a>),
/// An include path expression.
IncludePath(LinkedNode<'a>), IncludePath(LinkedNode<'a>),
/// Rest kind of **expressions**.
Normal(SyntaxKind, LinkedNode<'a>), Normal(SyntaxKind, LinkedNode<'a>),
} }
impl<'a> DerefTarget<'a> { impl<'a> SyntaxClass<'a> {
pub fn node(&self) -> &LinkedNode<'a> { pub fn node(&self) -> &LinkedNode<'a> {
match self { match self {
DerefTarget::Label(node) SyntaxClass::Label { node, .. }
| DerefTarget::LabelError(node) | SyntaxClass::Ref(node)
| DerefTarget::Ref(node) | SyntaxClass::VarAccess(node)
| DerefTarget::VarAccess(node) | SyntaxClass::Callee(node)
| DerefTarget::Callee(node) | SyntaxClass::ImportPath(node)
| DerefTarget::ImportPath(node) | SyntaxClass::IncludePath(node)
| DerefTarget::IncludePath(node) | SyntaxClass::Normal(_, node) => node,
| DerefTarget::Normal(_, node) => node, }
}
pub fn label(node: LinkedNode<'a>) -> Self {
Self::Label {
node,
is_error: false,
}
}
pub fn error_as_label(node: LinkedNode<'a>) -> Self {
Self::Label {
node,
is_error: true,
} }
} }
} }
pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option<DerefTarget<'_>> { /// Classifies the syntax that can be operated on by IDE functionality.
pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option<SyntaxClass<'_>> {
if matches!(node.kind(), SyntaxKind::Error) && node.text().starts_with('<') { if matches!(node.kind(), SyntaxKind::Error) && node.text().starts_with('<') {
return Some(DerefTarget::LabelError(node)); return Some(SyntaxClass::error_as_label(node));
} }
/// Skips trivia nodes that are on the same line as the cursor. /// Skips trivia nodes that are on the same line as the cursor.
@ -353,49 +343,79 @@ pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option<DerefTarget<'
crate::log_debug_ct!("deref expr: {ancestor:?}"); crate::log_debug_ct!("deref expr: {ancestor:?}");
// Unwrap all parentheses to get the actual expression. // Unwrap all parentheses to get the actual expression.
let cano_expr = deref_lvalue(ancestor)?; let cano_expr = classify_lvalue(ancestor)?;
crate::log_debug_ct!("deref lvalue: {cano_expr:?}"); crate::log_debug_ct!("deref lvalue: {cano_expr:?}");
// Identify convenient expression kinds. // Identify convenient expression kinds.
let expr = cano_expr.cast::<ast::Expr>()?; let expr = cano_expr.cast::<ast::Expr>()?;
Some(match expr { Some(match expr {
ast::Expr::Label(..) => DerefTarget::Label(cano_expr), ast::Expr::Label(..) => SyntaxClass::label(cano_expr),
ast::Expr::Ref(..) => DerefTarget::Ref(cano_expr), ast::Expr::Ref(..) => SyntaxClass::Ref(cano_expr),
ast::Expr::FuncCall(call) => DerefTarget::Callee(cano_expr.find(call.callee().span())?), ast::Expr::FuncCall(call) => SyntaxClass::Callee(cano_expr.find(call.callee().span())?),
ast::Expr::Set(set) => DerefTarget::Callee(cano_expr.find(set.target().span())?), ast::Expr::Set(set) => SyntaxClass::Callee(cano_expr.find(set.target().span())?),
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => { ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
DerefTarget::VarAccess(cano_expr) SyntaxClass::VarAccess(cano_expr)
} }
ast::Expr::Str(..) => { ast::Expr::Str(..) => {
let parent = cano_expr.parent()?; let parent = cano_expr.parent()?;
if parent.kind() == SyntaxKind::ModuleImport { if parent.kind() == SyntaxKind::ModuleImport {
DerefTarget::ImportPath(cano_expr) SyntaxClass::ImportPath(cano_expr)
} else if parent.kind() == SyntaxKind::ModuleInclude { } else if parent.kind() == SyntaxKind::ModuleInclude {
DerefTarget::IncludePath(cano_expr) SyntaxClass::IncludePath(cano_expr)
} else { } else {
DerefTarget::Normal(cano_expr.kind(), cano_expr) SyntaxClass::Normal(cano_expr.kind(), cano_expr)
} }
} }
_ if expr.hash() _ if expr.hash()
|| matches!(cano_expr.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) => || matches!(cano_expr.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
{ {
DerefTarget::Normal(cano_expr.kind(), cano_expr) SyntaxClass::Normal(cano_expr.kind(), cano_expr)
} }
_ => return None, _ => return None,
}) })
} }
/// Whether the node might be in code trivia. This is a bit internal so please
/// check the caller to understand it.
fn possible_in_code_trivia(sk: SyntaxKind) -> bool {
!matches!(
interpret_mode_at_kind(sk),
Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
)
}
/// Finds a more canonical expression target.
/// It is not formal, but the following cases are forbidden:
/// - Parenthesized expression.
/// - Identifier on the right side of a dot operator (field access).
fn classify_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
while let Some(e) = node.cast::<ast::Parenthesized>() {
node = node.find(e.expr().span())?;
}
if let Some(e) = node.parent() {
if let Some(f) = e.cast::<ast::FieldAccess>() {
if node.span() == f.field().span() {
return Some(e.clone());
}
}
}
Some(node)
}
/// Classes of def items that can be operated on by IDE functionality.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DefTarget<'a> { pub enum DefClass<'a> {
/// A let binding item.
Let(LinkedNode<'a>), Let(LinkedNode<'a>),
/// A module import item.
Import(LinkedNode<'a>), Import(LinkedNode<'a>),
} }
impl DefTarget<'_> { impl DefClass<'_> {
pub fn node(&self) -> &LinkedNode { pub fn node(&self) -> &LinkedNode {
match self { match self {
DefTarget::Let(node) => node, DefClass::Let(node) => node,
DefTarget::Import(node) => node, DefClass::Import(node) => node,
} }
} }
@ -405,7 +425,7 @@ impl DefTarget<'_> {
pub fn name(&self) -> Option<LinkedNode> { pub fn name(&self) -> Option<LinkedNode> {
match self { match self {
DefTarget::Let(node) => { DefClass::Let(node) => {
let lb: ast::LetBinding<'_> = node.cast()?; let lb: ast::LetBinding<'_> = node.cast()?;
let names = match lb.kind() { let names = match lb.kind() {
ast::LetBindingKind::Closure(name) => node.find(name.span())?, ast::LetBindingKind::Closure(name) => node.find(name.span())?,
@ -417,7 +437,7 @@ impl DefTarget<'_> {
Some(names) Some(names)
} }
DefTarget::Import(_node) => { DefClass::Import(_node) => {
// let ident = node.cast::<ast::ImportItem>()?; // let ident = node.cast::<ast::ImportItem>()?;
// Some(ident.span().into()) // Some(ident.span().into())
// todo: implement this // todo: implement this
@ -427,16 +447,19 @@ impl DefTarget<'_> {
} }
} }
// todo: whether we should distinguish between strict and non-strict def targets // todo: whether we should distinguish between strict and loose def classes
pub fn get_non_strict_def_target(node: LinkedNode) -> Option<DefTarget<'_>> { /// Classifies a definition under cursor loosely.
get_def_target_(node, false) pub fn classify_def_loosely(node: LinkedNode) -> Option<DefClass<'_>> {
classify_def_(node, false)
} }
pub fn get_def_target(node: LinkedNode) -> Option<DefTarget<'_>> { /// Classifies a definition under cursor strictly.
get_def_target_(node, true) pub fn classify_def(node: LinkedNode) -> Option<DefClass<'_>> {
classify_def_(node, true)
} }
fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> { /// The internal implementation of classifying a definition.
fn classify_def_(node: LinkedNode, strict: bool) -> Option<DefClass<'_>> {
let mut ancestor = node; let mut ancestor = node;
if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) { if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
ancestor = ancestor.prev_sibling()?; ancestor = ancestor.prev_sibling()?;
@ -446,7 +469,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
ancestor = ancestor.parent()?.clone(); ancestor = ancestor.parent()?.clone();
} }
crate::log_debug_ct!("def expr: {ancestor:?}"); crate::log_debug_ct!("def expr: {ancestor:?}");
let ancestor = deref_lvalue(ancestor)?; let ancestor = classify_lvalue(ancestor)?;
crate::log_debug_ct!("def lvalue: {ancestor:?}"); crate::log_debug_ct!("def lvalue: {ancestor:?}");
let may_ident = ancestor.cast::<ast::Expr>()?; let may_ident = ancestor.cast::<ast::Expr>()?;
@ -460,8 +483,8 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
// todo: include // todo: include
ast::Expr::FuncCall(..) => return None, ast::Expr::FuncCall(..) => return None,
ast::Expr::Set(..) => return None, ast::Expr::Set(..) => return None,
ast::Expr::Let(..) => DefTarget::Let(ancestor), ast::Expr::Let(..) => DefClass::Let(ancestor),
ast::Expr::Import(..) => DefTarget::Import(ancestor), ast::Expr::Import(..) => DefClass::Import(ancestor),
// todo: parameter // todo: parameter
ast::Expr::Ident(..) ast::Expr::Ident(..)
| ast::Expr::MathIdent(..) | ast::Expr::MathIdent(..)
@ -472,7 +495,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
ancestor = ancestor.parent()?.clone(); ancestor = ancestor.parent()?.clone();
} }
DefTarget::Let(ancestor) DefClass::Let(ancestor)
} }
ast::Expr::Str(..) => { ast::Expr::Str(..) => {
let parent = ancestor.parent()?; let parent = ancestor.parent()?;
@ -480,7 +503,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
return None; return None;
} }
DefTarget::Import(parent.clone()) DefClass::Import(parent.clone())
} }
_ if may_ident.hash() => return None, _ if may_ident.hash() => return None,
_ => { _ => {
@ -490,18 +513,22 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
}) })
} }
/// Classes of arguments that can be operated on by IDE functionality.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ParamTarget<'a> { pub enum ArgClass<'a> {
/// A positional argument.
Positional { Positional {
spreads: EcoVec<LinkedNode<'a>>, spreads: EcoVec<LinkedNode<'a>>,
positional: usize, positional: usize,
is_spread: bool, is_spread: bool,
}, },
/// A named argument.
Named(LinkedNode<'a>), Named(LinkedNode<'a>),
} }
impl ParamTarget<'_> {
impl ArgClass<'_> {
pub(crate) fn positional_from_before(before: bool) -> Self { pub(crate) fn positional_from_before(before: bool) -> Self {
ParamTarget::Positional { ArgClass::Positional {
spreads: EcoVec::new(), spreads: EcoVec::new(),
positional: if before { 0 } else { 1 }, positional: if before { 0 } else { 1 },
is_spread: false, is_spread: false,
@ -509,68 +536,84 @@ impl ParamTarget<'_> {
} }
} }
/// Classes of syntax under cursor that are preferred by type checking.
///
/// A cursor class is either an [`SyntaxClass`] or other things under cursor.
/// One thing is not ncessary to refer to some exact node. For example, a cursor
/// moving after some comma in a function call is identified as a
/// [`CursorClass::Param`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CheckTarget<'a> { pub enum CursorClass<'a> {
Param { /// A cursor on an argument.
Arg {
callee: LinkedNode<'a>, callee: LinkedNode<'a>,
args: LinkedNode<'a>, args: LinkedNode<'a>,
target: ParamTarget<'a>, target: ArgClass<'a>,
is_set: bool, is_set: bool,
}, },
/// A cursor on an element in an array or dictionary literal.
Element { Element {
container: LinkedNode<'a>, container: LinkedNode<'a>,
target: ParamTarget<'a>, target: ArgClass<'a>,
}, },
/// A cursor on a parenthesized expression.
Paren { Paren {
container: LinkedNode<'a>, container: LinkedNode<'a>,
is_before: bool, is_before: bool,
}, },
/// A cursor on an import path.
ImportPath(LinkedNode<'a>), ImportPath(LinkedNode<'a>),
/// A cursor on an include path.
IncludePath(LinkedNode<'a>), IncludePath(LinkedNode<'a>),
Label(LinkedNode<'a>), /// A cursor on a label.
LabelError(LinkedNode<'a>), Label {
node: LinkedNode<'a>,
is_error: bool,
},
/// A cursor on a normal [`SyntaxClass`].
Normal(LinkedNode<'a>), Normal(LinkedNode<'a>),
} }
impl<'a> CheckTarget<'a> { impl<'a> CursorClass<'a> {
pub fn node(&self) -> Option<LinkedNode<'a>> { pub fn node(&self) -> Option<LinkedNode<'a>> {
Some(match self { Some(match self {
CheckTarget::Param { target, .. } | CheckTarget::Element { target, .. } => match target CursorClass::Arg { target, .. } | CursorClass::Element { target, .. } => match target {
{ ArgClass::Positional { .. } => return None,
ParamTarget::Positional { .. } => return None, ArgClass::Named(node) => node.clone(),
ParamTarget::Named(node) => node.clone(),
}, },
CheckTarget::Paren { container, .. } => container.clone(), CursorClass::Paren { container, .. } => container.clone(),
CheckTarget::Label(node) CursorClass::Label { node, .. }
| CheckTarget::LabelError(node) | CursorClass::ImportPath(node)
| CheckTarget::ImportPath(node) | CursorClass::IncludePath(node)
| CheckTarget::IncludePath(node) | CursorClass::Normal(node) => node.clone(),
| CheckTarget::Normal(node) => node.clone(),
}) })
} }
} }
/// Kind of argument source.
#[derive(Debug)] #[derive(Debug)]
enum ParamKind { enum ArgSourceKind {
/// An argument in a function call.
Call, Call,
/// An argument (element) in an array literal.
Array, Array,
/// An argument (element) in a dictionary literal.
Dict, Dict,
} }
pub fn get_check_target_by_context<'a>( /// Classifies a cursor expression by context.
pub fn classify_cursor_by_context<'a>(
context: LinkedNode<'a>, context: LinkedNode<'a>,
node: LinkedNode<'a>, node: LinkedNode<'a>,
) -> Option<CheckTarget<'a>> { ) -> Option<CursorClass<'a>> {
use DerefTarget::*; use SyntaxClass::*;
let context_deref_target = get_deref_target(context.clone(), node.offset())?; let context_syntax = classify_syntax(context.clone(), node.offset())?;
let node_deref_target = get_deref_target(node.clone(), node.offset())?; let inner_syntax = classify_syntax(node.clone(), node.offset())?;
match context_deref_target { match context_syntax {
Callee(callee) Callee(callee)
if matches!( if matches!(inner_syntax, Normal(..) | Label { .. } | Ref(..))
node_deref_target, && !matches!(inner_syntax, Callee(..)) =>
Normal(..) | Label(..) | LabelError(..) | Ref(..)
) && !matches!(node_deref_target, Callee(..)) =>
{ {
let parent = callee.parent()?; let parent = callee.parent()?;
let args = match parent.cast::<ast::Expr>() { let args = match parent.cast::<ast::Expr>() {
@ -581,11 +624,11 @@ pub fn get_check_target_by_context<'a>(
let args = parent.find(args.span())?; let args = parent.find(args.span())?;
let is_set = parent.kind() == SyntaxKind::SetRule; let is_set = parent.kind() == SyntaxKind::SetRule;
let target = get_param_target(args.clone(), node, ParamKind::Call)?; let arg_target = cursor_on_arg(args.clone(), node, ArgSourceKind::Call)?;
Some(CheckTarget::Param { Some(CursorClass::Arg {
callee, callee,
args, args,
target, target: arg_target,
is_set, is_set,
}) })
} }
@ -593,14 +636,8 @@ pub fn get_check_target_by_context<'a>(
} }
} }
fn possible_in_code_trivia(sk: SyntaxKind) -> bool { /// Classifies an expression under cursor that are preferred by type checking.
!matches!( pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
interpret_mode_at_kind(sk),
Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
)
}
pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
let mut node = node; let mut node = node;
if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) { if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
loop { loop {
@ -612,34 +649,31 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
} }
} }
let deref_target = get_deref_target(node.clone(), node.offset())?; let syntax = classify_syntax(node.clone(), node.offset())?;
let deref_node = match deref_target { let normal_syntax = match syntax {
DerefTarget::Callee(callee) => { SyntaxClass::Callee(callee) => {
return get_callee_target(callee, node); return cursor_on_callee(callee, node);
} }
DerefTarget::Label(node) => { SyntaxClass::Label { node, is_error } => {
return Some(CheckTarget::Label(node)); return Some(CursorClass::Label { node, is_error });
} }
DerefTarget::LabelError(node) => { SyntaxClass::ImportPath(node) => {
return Some(CheckTarget::LabelError(node)); return Some(CursorClass::ImportPath(node));
} }
DerefTarget::ImportPath(node) => { SyntaxClass::IncludePath(node) => {
return Some(CheckTarget::ImportPath(node)); return Some(CursorClass::IncludePath(node));
} }
DerefTarget::IncludePath(node) => { syntax => syntax.node().clone(),
return Some(CheckTarget::IncludePath(node));
}
deref_target => deref_target.node().clone(),
}; };
let Some(mut node_parent) = node.parent().cloned() else { let Some(mut node_parent) = node.parent().cloned() else {
return Some(CheckTarget::Normal(node)); return Some(CursorClass::Normal(node));
}; };
while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() { while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
let Some(p) = node_parent.parent() else { let Some(p) = node_parent.parent() else {
return Some(CheckTarget::Normal(node)); return Some(CursorClass::Normal(node));
}; };
node_parent = p.clone(); node_parent = p.clone();
} }
@ -655,7 +689,7 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
p.find(s) p.find(s)
})?; })?;
let node = match node.kind() { let param_node = match node.kind() {
SyntaxKind::Ident SyntaxKind::Ident
if matches!( if matches!(
node.parent_kind().zip(node.next_sibling_kind()), node.parent_kind().zip(node.next_sibling_kind()),
@ -670,35 +704,35 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
_ => node, _ => node,
}; };
get_callee_target(callee, node) cursor_on_callee(callee, param_node)
} }
SyntaxKind::Array | SyntaxKind::Dict => { SyntaxKind::Array | SyntaxKind::Dict => {
let target = get_param_target( let element_target = cursor_on_arg(
node_parent.clone(), node_parent.clone(),
node.clone(), node.clone(),
match node_parent.kind() { match node_parent.kind() {
SyntaxKind::Array => ParamKind::Array, SyntaxKind::Array => ArgSourceKind::Array,
SyntaxKind::Dict => ParamKind::Dict, SyntaxKind::Dict => ArgSourceKind::Dict,
_ => unreachable!(), _ => unreachable!(),
}, },
)?; )?;
Some(CheckTarget::Element { Some(CursorClass::Element {
container: node_parent.clone(), container: node_parent.clone(),
target, target: element_target,
}) })
} }
SyntaxKind::Parenthesized => { SyntaxKind::Parenthesized => {
let is_before = node.offset() <= node_parent.offset() + 1; let is_before = node.offset() <= node_parent.offset() + 1;
Some(CheckTarget::Paren { Some(CursorClass::Paren {
container: node_parent.clone(), container: node_parent.clone(),
is_before, is_before,
}) })
} }
_ => Some(CheckTarget::Normal(deref_node)), _ => Some(CursorClass::Normal(normal_syntax)),
} }
} }
fn get_callee_target<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<CheckTarget<'a>> { fn cursor_on_callee<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<CursorClass<'a>> {
let parent = callee.parent()?; let parent = callee.parent()?;
let args = match parent.cast::<ast::Expr>() { let args = match parent.cast::<ast::Expr>() {
Some(ast::Expr::FuncCall(call)) => call.args(), Some(ast::Expr::FuncCall(call)) => call.args(),
@ -708,8 +742,8 @@ fn get_callee_target<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option
let args = parent.find(args.span())?; let args = parent.find(args.span())?;
let is_set = parent.kind() == SyntaxKind::SetRule; let is_set = parent.kind() == SyntaxKind::SetRule;
let target = get_param_target(args.clone(), node, ParamKind::Call)?; let target = cursor_on_arg(args.clone(), node, ArgSourceKind::Call)?;
Some(CheckTarget::Param { Some(CursorClass::Arg {
callee, callee,
args, args,
target, target,
@ -717,23 +751,23 @@ fn get_callee_target<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option
}) })
} }
fn get_param_target<'a>( fn cursor_on_arg<'a>(
args_node: LinkedNode<'a>, args_node: LinkedNode<'a>,
mut node: LinkedNode<'a>, mut node: LinkedNode<'a>,
param_kind: ParamKind, param_kind: ArgSourceKind,
) -> Option<ParamTarget<'a>> { ) -> Option<ArgClass<'a>> {
if node.kind() == SyntaxKind::RightParen { if node.kind() == SyntaxKind::RightParen {
node = node.prev_sibling()?; node = node.prev_sibling()?;
} }
match node.kind() { match node.kind() {
SyntaxKind::Named => { SyntaxKind::Named => {
let param_ident = node.cast::<ast::Named>()?.name(); let param_ident = node.cast::<ast::Named>()?.name();
Some(ParamTarget::Named(args_node.find(param_ident.span())?)) Some(ArgClass::Named(args_node.find(param_ident.span())?))
} }
SyntaxKind::Colon => { SyntaxKind::Colon => {
let prev = node.prev_leaf()?; let prev = node.prev_leaf()?;
let param_ident = prev.cast::<ast::Ident>()?; let param_ident = prev.cast::<ast::Ident>()?;
Some(ParamTarget::Named(args_node.find(param_ident.span())?)) Some(ArgClass::Named(args_node.find(param_ident.span())?))
} }
_ => { _ => {
let mut spreads = EcoVec::new(); let mut spreads = EcoVec::new();
@ -744,7 +778,7 @@ fn get_param_target<'a>(
.children() .children()
.take_while(|arg| arg.range().end <= node.offset()); .take_while(|arg| arg.range().end <= node.offset());
match param_kind { match param_kind {
ParamKind::Call => { ArgSourceKind::Call => {
for ch in args_before { for ch in args_before {
match ch.cast::<ast::Arg>() { match ch.cast::<ast::Arg>() {
Some(ast::Arg::Pos(..)) => { Some(ast::Arg::Pos(..)) => {
@ -757,7 +791,7 @@ fn get_param_target<'a>(
} }
} }
} }
ParamKind::Array => { ArgSourceKind::Array => {
for ch in args_before { for ch in args_before {
match ch.cast::<ast::ArrayItem>() { match ch.cast::<ast::ArrayItem>() {
Some(ast::ArrayItem::Pos(..)) => { Some(ast::ArrayItem::Pos(..)) => {
@ -770,7 +804,7 @@ fn get_param_target<'a>(
} }
} }
} }
ParamKind::Dict => { ArgSourceKind::Dict => {
for ch in args_before { for ch in args_before {
if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() { if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
spreads.push(ch); spreads.push(ch);
@ -779,7 +813,7 @@ fn get_param_target<'a>(
} }
} }
Some(ParamTarget::Positional { Some(ArgClass::Positional {
spreads, spreads,
positional, positional,
is_spread, is_spread,
@ -788,65 +822,6 @@ fn get_param_target<'a>(
} }
} }
pub fn param_index_at_leaf(leaf: &LinkedNode, function: &Func, args: ast::Args) -> Option<usize> {
let deciding = deciding_syntax(leaf);
let params = function.params()?;
let param_index = find_param_index(&deciding, params, args)?;
log::trace!("got param index {param_index}");
Some(param_index)
}
/// Find the piece of syntax that decides what we're completing.
fn deciding_syntax<'b>(leaf: &'b LinkedNode) -> LinkedNode<'b> {
let mut deciding = leaf.clone();
while !matches!(
deciding.kind(),
SyntaxKind::LeftParen | SyntaxKind::Comma | SyntaxKind::Colon
) {
let Some(prev) = deciding.prev_leaf() else {
break;
};
deciding = prev;
}
deciding
}
fn find_param_index(deciding: &LinkedNode, params: &[ParamInfo], args: ast::Args) -> Option<usize> {
match deciding.kind() {
// After colon: "func(param:|)", "func(param: |)".
SyntaxKind::Colon => {
let prev = deciding.prev_leaf()?;
let param_ident = prev.cast::<ast::Ident>()?;
params
.iter()
.position(|param| param.name == param_ident.as_str())
}
// Before: "func(|)", "func(hi|)", "func(12,|)".
SyntaxKind::Comma | SyntaxKind::LeftParen => {
let next = deciding.next_leaf();
let following_param = next.as_ref().and_then(|next| next.cast::<ast::Ident>());
match following_param {
Some(next) => params
.iter()
.position(|param| param.named && param.name.starts_with(next.as_str())),
None => {
let positional_args_so_far = args
.items()
.filter(|arg| matches!(arg, ast::Arg::Pos(_)))
.count();
params
.iter()
.enumerate()
.filter(|(_, param)| param.positional)
.map(|(i, _)| i)
.nth(positional_args_so_far)
}
}
}
_ => None,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -881,15 +856,15 @@ mod tests {
fn map_deref(source: &str) -> String { fn map_deref(source: &str) -> String {
map_base(source, |root, cursor| { map_base(source, |root, cursor| {
let node = root.leaf_at_compat(cursor); let node = root.leaf_at_compat(cursor);
let kind = node.and_then(|node| get_deref_target(node, cursor)); let kind = node.and_then(|node| classify_syntax(node, cursor));
match kind { match kind {
Some(DerefTarget::VarAccess(..)) => 'v', Some(SyntaxClass::VarAccess(..)) => 'v',
Some(DerefTarget::Normal(..)) => 'n', Some(SyntaxClass::Normal(..)) => 'n',
Some(DerefTarget::Label(..) | DerefTarget::LabelError(..)) => 'l', Some(SyntaxClass::Label { .. }) => 'l',
Some(DerefTarget::Ref(..)) => 'r', Some(SyntaxClass::Ref(..)) => 'r',
Some(DerefTarget::Callee(..)) => 'c', Some(SyntaxClass::Callee(..)) => 'c',
Some(DerefTarget::ImportPath(..)) => 'i', Some(SyntaxClass::ImportPath(..)) => 'i',
Some(DerefTarget::IncludePath(..)) => 'I', Some(SyntaxClass::IncludePath(..)) => 'I',
None => ' ', None => ' ',
} }
}) })
@ -898,15 +873,15 @@ mod tests {
fn map_check(source: &str) -> String { fn map_check(source: &str) -> String {
map_base(source, |root, cursor| { map_base(source, |root, cursor| {
let node = root.leaf_at_compat(cursor); let node = root.leaf_at_compat(cursor);
let kind = node.and_then(|node| get_check_target(node)); let kind = node.and_then(|node| classify_cursor(node));
match kind { match kind {
Some(CheckTarget::Param { .. }) => 'p', Some(CursorClass::Arg { .. }) => 'p',
Some(CheckTarget::Element { .. }) => 'e', Some(CursorClass::Element { .. }) => 'e',
Some(CheckTarget::Paren { .. }) => 'P', Some(CursorClass::Paren { .. }) => 'P',
Some(CheckTarget::ImportPath(..)) => 'i', Some(CursorClass::ImportPath(..)) => 'i',
Some(CheckTarget::IncludePath(..)) => 'I', Some(CursorClass::IncludePath(..)) => 'I',
Some(CheckTarget::Label(..) | CheckTarget::LabelError(..)) => 'l', Some(CursorClass::Label { .. }) => 'l',
Some(CheckTarget::Normal(..)) => 'n', Some(CursorClass::Normal(..)) => 'n',
None => ' ', None => ' ',
} }
}) })

View file

@ -21,7 +21,7 @@ use crate::snippet::{
ParsedSnippet, PostfixSnippet, PostfixSnippetScope, SurroundingSyntax, DEFAULT_POSTFIX_SNIPPET, ParsedSnippet, PostfixSnippet, PostfixSnippetScope, SurroundingSyntax, DEFAULT_POSTFIX_SNIPPET,
}; };
use crate::syntax::{ use crate::syntax::{
descending_decls, interpret_mode_at, is_ident_like, CheckTarget, DescentDecl, InterpretMode, descent_decls, interpret_mode_at, is_ident_like, CursorClass, DescentDecl, InterpretMode,
}; };
use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeScheme, TypeVar}; use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeScheme, TypeVar};
use crate::upstream::complete::complete_code; use crate::upstream::complete::complete_code;
@ -119,7 +119,7 @@ impl CompletionContext<'_> {
.clone(); .clone();
defines.insert_scope(&scope); defines.insert_scope(&scope);
descending_decls(self.leaf.clone(), |node| -> Option<()> { descent_decls(self.leaf.clone(), |node| -> Option<()> {
match node { match node {
DescentDecl::Ident(ident) => { DescentDecl::Ident(ident) => {
let ty = self.ctx.type_of_span(ident.span()).unwrap_or(Ty::Any); let ty = self.ctx.type_of_span(ident.span()).unwrap_or(Ty::Any);
@ -1370,15 +1370,15 @@ impl TypeCompletionContext<'_, '_> {
/// Complete code by type or syntax. /// Complete code by type or syntax.
pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()> { pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()> {
use crate::syntax::get_check_target; use crate::syntax::classify_cursor;
use SurroundingSyntax::*; use SurroundingSyntax::*;
let check_target = get_check_target(ctx.leaf.clone()); let cursor_class = classify_cursor(ctx.leaf.clone());
crate::log_debug_ct!("complete_type: pos {:?} -> {check_target:#?}", ctx.leaf); crate::log_debug_ct!("complete_type: pos {:?} -> {cursor_class:#?}", ctx.leaf);
let mut args_node = None; let mut args_node = None;
match check_target { match cursor_class {
Some(CheckTarget::Element { container, .. }) => { Some(CursorClass::Element { container, .. }) => {
if let Some(container) = container.cast::<ast::Dict>() { if let Some(container) = container.cast::<ast::Dict>() {
for named in container.items() { for named in container.items() {
if let ast::DictItem::Named(named) = named { if let ast::DictItem::Named(named) = named {
@ -1387,7 +1387,7 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
} }
}; };
} }
Some(CheckTarget::Param { args, .. }) => { Some(CursorClass::Arg { args, .. }) => {
let args = args.cast::<ast::Args>()?; let args = args.cast::<ast::Args>()?;
for arg in args.items() { for arg in args.items() {
if let ast::Arg::Named(named) = arg { if let ast::Arg::Named(named) = arg {
@ -1396,7 +1396,7 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
} }
args_node = Some(args.to_untyped().clone()); args_node = Some(args.to_untyped().clone());
} }
Some(CheckTarget::ImportPath(path) | CheckTarget::IncludePath(path)) => { Some(CursorClass::ImportPath(path) | CursorClass::IncludePath(path)) => {
let Some(ast::Expr::Str(str)) = path.cast() else { let Some(ast::Expr::Str(str)) = path.cast() else {
return None; return None;
}; };
@ -1423,26 +1423,21 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
return Some(()); return Some(());
} }
Some(CheckTarget::Normal(e)) Some(CursorClass::Normal(node))
if (matches!(e.kind(), SyntaxKind::ContentBlock) if (matches!(node.kind(), SyntaxKind::ContentBlock)
&& matches!(ctx.leaf.kind(), SyntaxKind::LeftBracket)) => && matches!(ctx.leaf.kind(), SyntaxKind::LeftBracket)) =>
{ {
args_node = e.parent().map(|s| s.get().clone()); args_node = node.parent().map(|s| s.get().clone());
} }
// todo: complete type field // todo: complete type field
Some(CheckTarget::Normal(e)) if matches!(e.kind(), SyntaxKind::FieldAccess) => { Some(CursorClass::Normal(node)) if matches!(node.kind(), SyntaxKind::FieldAccess) => {
return None; return None;
} }
Some( Some(CursorClass::Paren { .. } | CursorClass::Label { .. } | CursorClass::Normal(..))
CheckTarget::Paren { .. }
| CheckTarget::Label(..)
| CheckTarget::LabelError(..)
| CheckTarget::Normal(..),
)
| None => {} | None => {}
} }
crate::log_debug_ct!("ctx.leaf {:?}", ctx.leaf.clone()); crate::log_debug_ct!("ctx.leaf {:?}", ctx.leaf);
let ty = ctx let ty = ctx
.ctx .ctx