mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
refactor: improving names of matched structs and documenting matchers (#1022)
This commit is contained in:
parent
902bd17cba
commit
ad0c1e8aca
16 changed files with 391 additions and 412 deletions
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 == ¶m.name {
|
if focus_name == ¶m.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)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => ' ',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue