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_shim::syntax::LinkedNodeExt;
use crate::{syntax::get_def_target, tests::*};
use crate::{syntax::classify_def, tests::*};
#[test]
fn test() {
@ -118,7 +118,7 @@ mod matcher_tests {
let root = LinkedNode::new(source.root());
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>");
assert_snapshot!(result);
@ -436,7 +436,7 @@ mod signature_tests {
use typst_shim::syntax::LinkedNodeExt;
use crate::analysis::{analyze_signature, Signature, SignatureTarget};
use crate::syntax::get_deref_target;
use crate::syntax::classify_syntax;
use crate::tests::*;
#[test]
@ -450,7 +450,7 @@ mod signature_tests {
let root = LinkedNode::new(source.root());
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 result = analyze_signature(

View file

@ -5,7 +5,7 @@ use typst::introspection::Introspector;
use typst::model::BibliographyElem;
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::VersionedDocument;
@ -60,17 +60,21 @@ pub fn definition(
ctx: &Arc<SharedContext>,
source: &Source,
document: Option<&VersionedDocument>,
deref_target: DerefTarget,
syntax: SyntaxClass,
) -> Option<Definition> {
match deref_target {
match syntax {
// todi: field access
DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => {
SyntaxClass::VarAccess(node) | SyntaxClass::Callee(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())
}
DerefTarget::Label(r) | DerefTarget::Ref(r) => {
SyntaxClass::Label {
node: r,
is_error: false,
}
| SyntaxClass::Ref(r) => {
let ref_expr: ast::Expr = r.cast()?;
let name = match ref_expr {
ast::Expr::Ref(r) => r.target(),
@ -82,7 +86,11 @@ pub fn definition(
find_bib_definition(ctx, introspector, name)
.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::syntax::{
construct_module_dependencies, get_deref_target, resolve_id_by_path, scan_workspace_files,
Decl, DefKind, DerefTarget, ExprInfo, ExprRoute, LexicalScope, ModuleDependency,
classify_syntax, construct_module_dependencies, resolve_id_by_path, scan_workspace_files, Decl,
DefKind, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, SyntaxClass,
};
use crate::upstream::{tooltip_, CompletionFeat, Tooltip};
use crate::{
@ -562,36 +562,39 @@ impl SharedContext {
self.source_by_id(self.file_id_by_path(p)?)
}
/// Get a syntax object at a position.
pub fn deref_syntax<'s>(&self, source: &'s Source, span: Span) -> Option<DerefTarget<'s>> {
/// Classifies the syntax under span that can be operated on by IDE
/// functionality.
pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
let node = LinkedNode::new(source.root()).find(span)?;
let cursor = node.offset() + 1;
get_deref_target(node, cursor)
classify_syntax(node, cursor)
}
/// Get a syntax object at a position.
pub fn deref_syntax_at<'s>(
/// Classifies the syntax under position that can be operated on by IDE
/// functionality.
pub fn classify_pos<'s>(
&self,
source: &'s Source,
position: LspPosition,
shift: usize,
) -> Option<DerefTarget<'s>> {
let (_, deref_target) = self.deref_syntax_at_(source, position, shift)?;
deref_target
) -> Option<SyntaxClass<'s>> {
let (_, expr) = self.classify_pos_(source, position, shift)?;
expr
}
/// Get a syntax object at a position.
pub fn deref_syntax_at_<'s>(
/// Classifies the syntax under position that can be operated on by IDE
/// functionality.
pub fn classify_pos_<'s>(
&self,
source: &'s Source,
position: LspPosition,
shift: usize,
) -> Option<(usize, Option<DerefTarget<'s>>)> {
) -> Option<(usize, Option<SyntaxClass<'s>>)> {
let offset = self.to_typst_pos(position, source)?;
let cursor = ceil_char_boundary(source.text(), offset + shift);
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.
@ -784,8 +787,8 @@ impl SharedContext {
doc: Option<&VersionedDocument>,
span: Span,
) -> Option<Definition> {
let target = self.deref_syntax(source, span)?;
definition(self, source, doc, target)
let expr = self.classify_span(source, span)?;
definition(self, source, doc, expr)
}
pub(crate) fn def_of_decl(&self, decl: &Interned<Decl>) -> Option<Definition> {
@ -800,9 +803,9 @@ impl SharedContext {
self: &Arc<Self>,
source: &Source,
doc: Option<&VersionedDocument>,
deref_target: DerefTarget,
syntax: SyntaxClass,
) -> 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> {

View file

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

View file

@ -10,7 +10,7 @@ use super::{
};
use crate::analysis::PostTypeChecker;
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::{InsTy, TyCtx};
use crate::upstream::truncated_repr;
@ -205,7 +205,7 @@ fn analyze_type_signature(
SignatureTarget::Runtime(f) => {
let source = ctx.source_by_id(f.span().id()?).ok()?;
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 ty = type_info.type_of_span(def.name()?.span())?;
Some((type_info, ty))

View file

@ -9,7 +9,7 @@ use typst_shim::syntax::LinkedNodeExt;
use crate::{
analysis::{InsTy, Ty},
prelude::*,
syntax::{is_ident_like, DerefTarget},
syntax::{is_ident_like, SyntaxClass},
upstream::{autocomplete, CompletionContext},
StatefulRequest,
};
@ -71,7 +71,7 @@ impl StatefulRequest for CompletionRequest {
let doc = doc.as_ref().map(|doc| doc.document.as_ref());
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>
//
@ -89,7 +89,7 @@ impl StatefulRequest for CompletionRequest {
let explicit = false;
// 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() {
// complete the init part of the let binding
Some(SyntaxKind::LetBinding) => {
@ -110,8 +110,8 @@ impl StatefulRequest for CompletionRequest {
// Skip if an error node starts with number (e.g. `1pt`)
if matches!(
deref_target,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..) | DerefTarget::Normal(..))
syntax,
Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..))
) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if node.erroneous() {
@ -157,10 +157,10 @@ impl StatefulRequest for CompletionRequest {
// Filter and determine range to replace
let mut from_ident = None;
let is_callee = matches!(deref_target, Some(DerefTarget::Callee(..)));
let is_callee = matches!(syntax, Some(SyntaxClass::Callee(..)));
if matches!(
deref_target,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..))
syntax,
Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..))
) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if is_ident_like(&node) && node.offset() == offset {

View file

@ -1,6 +1,6 @@
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
/// location of a symbol at a given text document position.
@ -39,7 +39,7 @@ impl SemanticRequest for GotoDeclarationRequest {
fn find_declarations(
_ctx: &LocalContext,
_expr_info: Arc<crate::syntax::ExprInfo>,
_deref_target: DerefTarget<'_>,
_syntax: SyntaxClass<'_>,
) -> Option<Vec<Range<usize>>> {
todo!()
}

View file

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

View file

@ -117,8 +117,8 @@ fn def_tooltip(
cursor: usize,
) -> Option<HoverContents> {
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
let deref_target = get_deref_target(leaf.clone(), cursor)?;
let def = ctx.def_of_syntax(source, document, deref_target.clone())?;
let syntax = classify_syntax(leaf.clone(), cursor)?;
let def = ctx.def_of_syntax(source, document, syntax.clone())?;
let mut results = vec![];
let mut actions = vec![];
@ -147,7 +147,7 @@ fn def_tooltip(
if matches!(def.decl.kind(), DefKind::Variable | DefKind::Constant) {
// 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 {
Tooltip::Text(values) => {
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,
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 use crate::{SemanticRequest, StatefulRequest, VersionedDocument};

View file

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

View file

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

View file

@ -14,7 +14,7 @@ use crate::{
find_references,
prelude::*,
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,
};
@ -42,15 +42,15 @@ impl StatefulRequest for RenameRequest {
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
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
DerefTarget::ImportPath(path) | DerefTarget::IncludePath(path) => {
SyntaxClass::ImportPath(path) | SyntaxClass::IncludePath(path) => {
let ref_path_str = path.cast::<ast::Str>()?.get();
let new_path_str = if !self.new_name.ends_with(".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();

View file

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

View file

@ -1,59 +1,46 @@
use serde::{Deserialize, Serialize};
use typst::foundations::{Func, ParamInfo};
use crate::prelude::*;
pub fn deref_expr(mut ancestor: LinkedNode) -> Option<LinkedNode> {
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)
}
/// Finds the ancestors of a node lazily.
pub fn node_ancestors<'a, 'b>(
node: &'b LinkedNode<'a>,
) -> impl Iterator<Item = &'b LinkedNode<'a>> {
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>),
/// When the iterator is crossing a parent node.
Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
}
impl<'a> DecenderItem<'a> {
impl<'a> DescentItem<'a> {
pub fn node(&self) -> &'a LinkedNode<'a> {
match self {
DecenderItem::Sibling(node) => node,
DecenderItem::Parent(node, _) => node,
DescentItem::Sibling(node) => node,
DescentItem::Parent(node, _) => node,
}
}
}
/// Find the decender nodes starting from the given position.
pub fn node_descenders<T>(
/// Finds the descent items starting from the given position.
pub fn descent_items<T>(
node: LinkedNode,
mut recv: impl FnMut(DecenderItem) -> Option<T>,
mut recv: impl FnMut(DescentItem) -> Option<T>,
) -> Option<T> {
let mut ancestor = Some(node);
while let Some(node) = &ancestor {
let mut sibling = Some(node.clone());
while let Some(node) = &sibling {
if let Some(v) = recv(DecenderItem::Sibling(node)) {
if let Some(v) = recv(DescentItem::Sibling(node)) {
return Some(v);
}
@ -61,7 +48,7 @@ pub fn node_descenders<T>(
}
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);
}
@ -81,21 +68,21 @@ pub enum DescentDecl<'a> {
ImportAll(ast::ModuleImport<'a>),
}
/// Find the descending decls starting from the given position.
pub fn descending_decls<T>(
/// Finds the descent decls starting from the given position.
pub fn descent_decls<T>(
node: LinkedNode,
mut recv: impl FnMut(DescentDecl) -> Option<T>,
) -> Option<T> {
node_descenders(node, |node| {
descent_items(node, |node| {
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() {
if let Some(t) = recv(DescentDecl::Ident(ident)) {
return Some(t);
}
}
}
(DecenderItem::Sibling(..), ast::Expr::Import(mi)) => {
(DescentItem::Sibling(..), ast::Expr::Import(mi)) => {
// import items
match mi.imports() {
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 in_body = body.is_some_and(|n| n.find(child.span()).is_some());
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 in_body = body.is_some_and(|n| n.find(child.span()).is_some());
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 {
use SyntaxKind::*;
matches!(
sk,
MathAlignPoint
| Plus
| Minus
| Slash
| Hat
| 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
)
#[allow(clippy::match_like_matches_macro)]
match sk {
MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
Slash | Hat | Comma | Semicolon | Colon | Hash => true,
_ => false,
}
}
/// Whether the node can be recognized as an identifier.
pub fn is_ident_like(node: &SyntaxNode) -> bool {
fn can_be_ident(node: &SyntaxNode) -> bool {
typst::syntax::is_ident(node.text())
}
use SyntaxKind::*;
let k = node.kind();
matches!(k, Ident | MathIdent | Underscore)
@ -221,10 +187,6 @@ pub fn is_ident_like(node: &SyntaxNode) -> bool {
|| 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.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
#[serde(rename_all = "camelCase")]
@ -243,6 +205,23 @@ pub enum InterpretMode {
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> {
use SyntaxKind::*;
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 {
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;
}
}
}
/// Classes of syntax that can be operated on by IDE functionality.
#[derive(Debug, Clone)]
pub enum DerefTarget<'a> {
Label(LinkedNode<'a>),
LabelError(LinkedNode<'a>),
Ref(LinkedNode<'a>),
pub enum SyntaxClass<'a> {
/// A variable access expression.
///
/// It can be either an identifier or a field access.
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>),
/// An import path expression.
ImportPath(LinkedNode<'a>),
/// An include path expression.
IncludePath(LinkedNode<'a>),
/// Rest kind of **expressions**.
Normal(SyntaxKind, LinkedNode<'a>),
}
impl<'a> DerefTarget<'a> {
impl<'a> SyntaxClass<'a> {
pub fn node(&self) -> &LinkedNode<'a> {
match self {
DerefTarget::Label(node)
| DerefTarget::LabelError(node)
| DerefTarget::Ref(node)
| DerefTarget::VarAccess(node)
| DerefTarget::Callee(node)
| DerefTarget::ImportPath(node)
| DerefTarget::IncludePath(node)
| DerefTarget::Normal(_, node) => node,
SyntaxClass::Label { node, .. }
| SyntaxClass::Ref(node)
| SyntaxClass::VarAccess(node)
| SyntaxClass::Callee(node)
| SyntaxClass::ImportPath(node)
| SyntaxClass::IncludePath(node)
| SyntaxClass::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('<') {
return Some(DerefTarget::LabelError(node));
return Some(SyntaxClass::error_as_label(node));
}
/// 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:?}");
// 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:?}");
// Identify convenient expression kinds.
let expr = cano_expr.cast::<ast::Expr>()?;
Some(match expr {
ast::Expr::Label(..) => DerefTarget::Label(cano_expr),
ast::Expr::Ref(..) => DerefTarget::Ref(cano_expr),
ast::Expr::FuncCall(call) => DerefTarget::Callee(cano_expr.find(call.callee().span())?),
ast::Expr::Set(set) => DerefTarget::Callee(cano_expr.find(set.target().span())?),
ast::Expr::Label(..) => SyntaxClass::label(cano_expr),
ast::Expr::Ref(..) => SyntaxClass::Ref(cano_expr),
ast::Expr::FuncCall(call) => SyntaxClass::Callee(cano_expr.find(call.callee().span())?),
ast::Expr::Set(set) => SyntaxClass::Callee(cano_expr.find(set.target().span())?),
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
DerefTarget::VarAccess(cano_expr)
SyntaxClass::VarAccess(cano_expr)
}
ast::Expr::Str(..) => {
let parent = cano_expr.parent()?;
if parent.kind() == SyntaxKind::ModuleImport {
DerefTarget::ImportPath(cano_expr)
SyntaxClass::ImportPath(cano_expr)
} else if parent.kind() == SyntaxKind::ModuleInclude {
DerefTarget::IncludePath(cano_expr)
SyntaxClass::IncludePath(cano_expr)
} else {
DerefTarget::Normal(cano_expr.kind(), cano_expr)
SyntaxClass::Normal(cano_expr.kind(), cano_expr)
}
}
_ if expr.hash()
|| matches!(cano_expr.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
{
DerefTarget::Normal(cano_expr.kind(), cano_expr)
SyntaxClass::Normal(cano_expr.kind(), cano_expr)
}
_ => 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)]
pub enum DefTarget<'a> {
pub enum DefClass<'a> {
/// A let binding item.
Let(LinkedNode<'a>),
/// A module import item.
Import(LinkedNode<'a>),
}
impl DefTarget<'_> {
impl DefClass<'_> {
pub fn node(&self) -> &LinkedNode {
match self {
DefTarget::Let(node) => node,
DefTarget::Import(node) => node,
DefClass::Let(node) => node,
DefClass::Import(node) => node,
}
}
@ -405,7 +425,7 @@ impl DefTarget<'_> {
pub fn name(&self) -> Option<LinkedNode> {
match self {
DefTarget::Let(node) => {
DefClass::Let(node) => {
let lb: ast::LetBinding<'_> = node.cast()?;
let names = match lb.kind() {
ast::LetBindingKind::Closure(name) => node.find(name.span())?,
@ -417,7 +437,7 @@ impl DefTarget<'_> {
Some(names)
}
DefTarget::Import(_node) => {
DefClass::Import(_node) => {
// let ident = node.cast::<ast::ImportItem>()?;
// Some(ident.span().into())
// todo: implement this
@ -427,16 +447,19 @@ impl DefTarget<'_> {
}
}
// todo: whether we should distinguish between strict and non-strict def targets
pub fn get_non_strict_def_target(node: LinkedNode) -> Option<DefTarget<'_>> {
get_def_target_(node, false)
// todo: whether we should distinguish between strict and loose def classes
/// Classifies a definition under cursor loosely.
pub fn classify_def_loosely(node: LinkedNode) -> Option<DefClass<'_>> {
classify_def_(node, false)
}
pub fn get_def_target(node: LinkedNode) -> Option<DefTarget<'_>> {
get_def_target_(node, true)
/// Classifies a definition under cursor strictly.
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;
if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
ancestor = ancestor.prev_sibling()?;
@ -446,7 +469,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
ancestor = ancestor.parent()?.clone();
}
crate::log_debug_ct!("def expr: {ancestor:?}");
let ancestor = deref_lvalue(ancestor)?;
let ancestor = classify_lvalue(ancestor)?;
crate::log_debug_ct!("def lvalue: {ancestor:?}");
let may_ident = ancestor.cast::<ast::Expr>()?;
@ -460,8 +483,8 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
// todo: include
ast::Expr::FuncCall(..) => return None,
ast::Expr::Set(..) => return None,
ast::Expr::Let(..) => DefTarget::Let(ancestor),
ast::Expr::Import(..) => DefTarget::Import(ancestor),
ast::Expr::Let(..) => DefClass::Let(ancestor),
ast::Expr::Import(..) => DefClass::Import(ancestor),
// todo: parameter
ast::Expr::Ident(..)
| ast::Expr::MathIdent(..)
@ -472,7 +495,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
ancestor = ancestor.parent()?.clone();
}
DefTarget::Let(ancestor)
DefClass::Let(ancestor)
}
ast::Expr::Str(..) => {
let parent = ancestor.parent()?;
@ -480,7 +503,7 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option<DefTarget<'_>> {
return None;
}
DefTarget::Import(parent.clone())
DefClass::Import(parent.clone())
}
_ 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)]
pub enum ParamTarget<'a> {
pub enum ArgClass<'a> {
/// A positional argument.
Positional {
spreads: EcoVec<LinkedNode<'a>>,
positional: usize,
is_spread: bool,
},
/// A named argument.
Named(LinkedNode<'a>),
}
impl ParamTarget<'_> {
impl ArgClass<'_> {
pub(crate) fn positional_from_before(before: bool) -> Self {
ParamTarget::Positional {
ArgClass::Positional {
spreads: EcoVec::new(),
positional: if before { 0 } else { 1 },
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)]
pub enum CheckTarget<'a> {
Param {
pub enum CursorClass<'a> {
/// A cursor on an argument.
Arg {
callee: LinkedNode<'a>,
args: LinkedNode<'a>,
target: ParamTarget<'a>,
target: ArgClass<'a>,
is_set: bool,
},
/// A cursor on an element in an array or dictionary literal.
Element {
container: LinkedNode<'a>,
target: ParamTarget<'a>,
target: ArgClass<'a>,
},
/// A cursor on a parenthesized expression.
Paren {
container: LinkedNode<'a>,
is_before: bool,
},
/// A cursor on an import path.
ImportPath(LinkedNode<'a>),
/// A cursor on an include path.
IncludePath(LinkedNode<'a>),
Label(LinkedNode<'a>),
LabelError(LinkedNode<'a>),
/// A cursor on a label.
Label {
node: LinkedNode<'a>,
is_error: bool,
},
/// A cursor on a normal [`SyntaxClass`].
Normal(LinkedNode<'a>),
}
impl<'a> CheckTarget<'a> {
impl<'a> CursorClass<'a> {
pub fn node(&self) -> Option<LinkedNode<'a>> {
Some(match self {
CheckTarget::Param { target, .. } | CheckTarget::Element { target, .. } => match target
{
ParamTarget::Positional { .. } => return None,
ParamTarget::Named(node) => node.clone(),
CursorClass::Arg { target, .. } | CursorClass::Element { target, .. } => match target {
ArgClass::Positional { .. } => return None,
ArgClass::Named(node) => node.clone(),
},
CheckTarget::Paren { container, .. } => container.clone(),
CheckTarget::Label(node)
| CheckTarget::LabelError(node)
| CheckTarget::ImportPath(node)
| CheckTarget::IncludePath(node)
| CheckTarget::Normal(node) => node.clone(),
CursorClass::Paren { container, .. } => container.clone(),
CursorClass::Label { node, .. }
| CursorClass::ImportPath(node)
| CursorClass::IncludePath(node)
| CursorClass::Normal(node) => node.clone(),
})
}
}
/// Kind of argument source.
#[derive(Debug)]
enum ParamKind {
enum ArgSourceKind {
/// An argument in a function call.
Call,
/// An argument (element) in an array literal.
Array,
/// An argument (element) in a dictionary literal.
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>,
node: LinkedNode<'a>,
) -> Option<CheckTarget<'a>> {
use DerefTarget::*;
let context_deref_target = get_deref_target(context.clone(), node.offset())?;
let node_deref_target = get_deref_target(node.clone(), node.offset())?;
) -> Option<CursorClass<'a>> {
use SyntaxClass::*;
let context_syntax = classify_syntax(context.clone(), node.offset())?;
let inner_syntax = classify_syntax(node.clone(), node.offset())?;
match context_deref_target {
match context_syntax {
Callee(callee)
if matches!(
node_deref_target,
Normal(..) | Label(..) | LabelError(..) | Ref(..)
) && !matches!(node_deref_target, Callee(..)) =>
if matches!(inner_syntax, Normal(..) | Label { .. } | Ref(..))
&& !matches!(inner_syntax, Callee(..)) =>
{
let parent = callee.parent()?;
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 is_set = parent.kind() == SyntaxKind::SetRule;
let target = get_param_target(args.clone(), node, ParamKind::Call)?;
Some(CheckTarget::Param {
let arg_target = cursor_on_arg(args.clone(), node, ArgSourceKind::Call)?;
Some(CursorClass::Arg {
callee,
args,
target,
target: arg_target,
is_set,
})
}
@ -593,14 +636,8 @@ pub fn get_check_target_by_context<'a>(
}
}
fn possible_in_code_trivia(sk: SyntaxKind) -> bool {
!matches!(
interpret_mode_at_kind(sk),
Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
)
}
pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
/// Classifies an expression under cursor that are preferred by type checking.
pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
let mut node = node;
if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
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 {
DerefTarget::Callee(callee) => {
return get_callee_target(callee, node);
let normal_syntax = match syntax {
SyntaxClass::Callee(callee) => {
return cursor_on_callee(callee, node);
}
DerefTarget::Label(node) => {
return Some(CheckTarget::Label(node));
SyntaxClass::Label { node, is_error } => {
return Some(CursorClass::Label { node, is_error });
}
DerefTarget::LabelError(node) => {
return Some(CheckTarget::LabelError(node));
SyntaxClass::ImportPath(node) => {
return Some(CursorClass::ImportPath(node));
}
DerefTarget::ImportPath(node) => {
return Some(CheckTarget::ImportPath(node));
SyntaxClass::IncludePath(node) => {
return Some(CursorClass::IncludePath(node));
}
DerefTarget::IncludePath(node) => {
return Some(CheckTarget::IncludePath(node));
}
deref_target => deref_target.node().clone(),
syntax => syntax.node().clone(),
};
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() {
let Some(p) = node_parent.parent() else {
return Some(CheckTarget::Normal(node));
return Some(CursorClass::Normal(node));
};
node_parent = p.clone();
}
@ -655,7 +689,7 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
p.find(s)
})?;
let node = match node.kind() {
let param_node = match node.kind() {
SyntaxKind::Ident
if matches!(
node.parent_kind().zip(node.next_sibling_kind()),
@ -670,35 +704,35 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
_ => node,
};
get_callee_target(callee, node)
cursor_on_callee(callee, param_node)
}
SyntaxKind::Array | SyntaxKind::Dict => {
let target = get_param_target(
let element_target = cursor_on_arg(
node_parent.clone(),
node.clone(),
match node_parent.kind() {
SyntaxKind::Array => ParamKind::Array,
SyntaxKind::Dict => ParamKind::Dict,
SyntaxKind::Array => ArgSourceKind::Array,
SyntaxKind::Dict => ArgSourceKind::Dict,
_ => unreachable!(),
},
)?;
Some(CheckTarget::Element {
Some(CursorClass::Element {
container: node_parent.clone(),
target,
target: element_target,
})
}
SyntaxKind::Parenthesized => {
let is_before = node.offset() <= node_parent.offset() + 1;
Some(CheckTarget::Paren {
Some(CursorClass::Paren {
container: node_parent.clone(),
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 args = match parent.cast::<ast::Expr>() {
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 is_set = parent.kind() == SyntaxKind::SetRule;
let target = get_param_target(args.clone(), node, ParamKind::Call)?;
Some(CheckTarget::Param {
let target = cursor_on_arg(args.clone(), node, ArgSourceKind::Call)?;
Some(CursorClass::Arg {
callee,
args,
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>,
mut node: LinkedNode<'a>,
param_kind: ParamKind,
) -> Option<ParamTarget<'a>> {
param_kind: ArgSourceKind,
) -> Option<ArgClass<'a>> {
if node.kind() == SyntaxKind::RightParen {
node = node.prev_sibling()?;
}
match node.kind() {
SyntaxKind::Named => {
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 => {
let prev = node.prev_leaf()?;
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();
@ -744,7 +778,7 @@ fn get_param_target<'a>(
.children()
.take_while(|arg| arg.range().end <= node.offset());
match param_kind {
ParamKind::Call => {
ArgSourceKind::Call => {
for ch in args_before {
match ch.cast::<ast::Arg>() {
Some(ast::Arg::Pos(..)) => {
@ -757,7 +791,7 @@ fn get_param_target<'a>(
}
}
}
ParamKind::Array => {
ArgSourceKind::Array => {
for ch in args_before {
match ch.cast::<ast::ArrayItem>() {
Some(ast::ArrayItem::Pos(..)) => {
@ -770,7 +804,7 @@ fn get_param_target<'a>(
}
}
}
ParamKind::Dict => {
ArgSourceKind::Dict => {
for ch in args_before {
if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
spreads.push(ch);
@ -779,7 +813,7 @@ fn get_param_target<'a>(
}
}
Some(ParamTarget::Positional {
Some(ArgClass::Positional {
spreads,
positional,
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)]
mod tests {
use super::*;
@ -881,15 +856,15 @@ mod tests {
fn map_deref(source: &str) -> String {
map_base(source, |root, 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 {
Some(DerefTarget::VarAccess(..)) => 'v',
Some(DerefTarget::Normal(..)) => 'n',
Some(DerefTarget::Label(..) | DerefTarget::LabelError(..)) => 'l',
Some(DerefTarget::Ref(..)) => 'r',
Some(DerefTarget::Callee(..)) => 'c',
Some(DerefTarget::ImportPath(..)) => 'i',
Some(DerefTarget::IncludePath(..)) => 'I',
Some(SyntaxClass::VarAccess(..)) => 'v',
Some(SyntaxClass::Normal(..)) => 'n',
Some(SyntaxClass::Label { .. }) => 'l',
Some(SyntaxClass::Ref(..)) => 'r',
Some(SyntaxClass::Callee(..)) => 'c',
Some(SyntaxClass::ImportPath(..)) => 'i',
Some(SyntaxClass::IncludePath(..)) => 'I',
None => ' ',
}
})
@ -898,15 +873,15 @@ mod tests {
fn map_check(source: &str) -> String {
map_base(source, |root, 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 {
Some(CheckTarget::Param { .. }) => 'p',
Some(CheckTarget::Element { .. }) => 'e',
Some(CheckTarget::Paren { .. }) => 'P',
Some(CheckTarget::ImportPath(..)) => 'i',
Some(CheckTarget::IncludePath(..)) => 'I',
Some(CheckTarget::Label(..) | CheckTarget::LabelError(..)) => 'l',
Some(CheckTarget::Normal(..)) => 'n',
Some(CursorClass::Arg { .. }) => 'p',
Some(CursorClass::Element { .. }) => 'e',
Some(CursorClass::Paren { .. }) => 'P',
Some(CursorClass::ImportPath(..)) => 'i',
Some(CursorClass::IncludePath(..)) => 'I',
Some(CursorClass::Label { .. }) => 'l',
Some(CursorClass::Normal(..)) => 'n',
None => ' ',
}
})

View file

@ -21,7 +21,7 @@ use crate::snippet::{
ParsedSnippet, PostfixSnippet, PostfixSnippetScope, SurroundingSyntax, DEFAULT_POSTFIX_SNIPPET,
};
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::upstream::complete::complete_code;
@ -119,7 +119,7 @@ impl CompletionContext<'_> {
.clone();
defines.insert_scope(&scope);
descending_decls(self.leaf.clone(), |node| -> Option<()> {
descent_decls(self.leaf.clone(), |node| -> Option<()> {
match node {
DescentDecl::Ident(ident) => {
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.
pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()> {
use crate::syntax::get_check_target;
use crate::syntax::classify_cursor;
use SurroundingSyntax::*;
let check_target = get_check_target(ctx.leaf.clone());
crate::log_debug_ct!("complete_type: pos {:?} -> {check_target:#?}", ctx.leaf);
let cursor_class = classify_cursor(ctx.leaf.clone());
crate::log_debug_ct!("complete_type: pos {:?} -> {cursor_class:#?}", ctx.leaf);
let mut args_node = None;
match check_target {
Some(CheckTarget::Element { container, .. }) => {
match cursor_class {
Some(CursorClass::Element { container, .. }) => {
if let Some(container) = container.cast::<ast::Dict>() {
for named in container.items() {
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>()?;
for arg in args.items() {
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());
}
Some(CheckTarget::ImportPath(path) | CheckTarget::IncludePath(path)) => {
Some(CursorClass::ImportPath(path) | CursorClass::IncludePath(path)) => {
let Some(ast::Expr::Str(str)) = path.cast() else {
return None;
};
@ -1423,26 +1423,21 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
return Some(());
}
Some(CheckTarget::Normal(e))
if (matches!(e.kind(), SyntaxKind::ContentBlock)
Some(CursorClass::Normal(node))
if (matches!(node.kind(), SyntaxKind::ContentBlock)
&& 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
Some(CheckTarget::Normal(e)) if matches!(e.kind(), SyntaxKind::FieldAccess) => {
Some(CursorClass::Normal(node)) if matches!(node.kind(), SyntaxKind::FieldAccess) => {
return None;
}
Some(
CheckTarget::Paren { .. }
| CheckTarget::Label(..)
| CheckTarget::LabelError(..)
| CheckTarget::Normal(..),
)
Some(CursorClass::Paren { .. } | CursorClass::Label { .. } | CursorClass::Normal(..))
| None => {}
}
crate::log_debug_ct!("ctx.leaf {:?}", ctx.leaf.clone());
crate::log_debug_ct!("ctx.leaf {:?}", ctx.leaf);
let ty = ctx
.ctx