diff --git a/crates/tinymist-query/src/analysis.rs b/crates/tinymist-query/src/analysis.rs index ccd4d3d8..7cfc9e95 100644 --- a/crates/tinymist-query/src/analysis.rs +++ b/crates/tinymist-query/src/analysis.rs @@ -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(""); 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( diff --git a/crates/tinymist-query/src/analysis/definition.rs b/crates/tinymist-query/src/analysis/definition.rs index 58a82f79..92b89d7f 100644 --- a/crates/tinymist-query/src/analysis/definition.rs +++ b/crates/tinymist-query/src/analysis/definition.rs @@ -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, source: &Source, document: Option<&VersionedDocument>, - deref_target: DerefTarget, + syntax: SyntaxClass, ) -> Option { - 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, } } diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 8b6fe180..350ea8f1 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -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> { + /// 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> { 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> { - let (_, deref_target) = self.deref_syntax_at_(source, position, shift)?; - deref_target + ) -> Option> { + 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>)> { + ) -> Option<(usize, Option>)> { 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 { - 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) -> Option { @@ -800,9 +803,9 @@ impl SharedContext { self: &Arc, source: &Source, doc: Option<&VersionedDocument>, - deref_target: DerefTarget, + syntax: SyntaxClass, ) -> Option { - definition(self, source, doc, deref_target) + definition(self, source, doc, syntax) } pub(crate) fn type_of_span(self: &Arc, s: Span) -> Option { diff --git a/crates/tinymist-query/src/analysis/post_tyck.rs b/crates/tinymist-query/src/analysis/post_tyck.rs index aceb8a71..56e76792 100644 --- a/crates/tinymist-query/src/analysis/post_tyck.rs +++ b/crates/tinymist-query/src/analysis/post_tyck.rs @@ -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], 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::()?; 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, context_ty: Option) -> Option { - let Some(node) = node else { + fn check_cursor(&mut self, cursor: Option, context_ty: Option) -> Option { + 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::()?.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, } } diff --git a/crates/tinymist-query/src/analysis/signature.rs b/crates/tinymist-query/src/analysis/signature.rs index c17ea7e4..d3961b24 100644 --- a/crates/tinymist-query/src/analysis/signature.rs +++ b/crates/tinymist-query/src/analysis/signature.rs @@ -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)) diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 0330e37b..3a69b5e2 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -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 // @@ -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 { diff --git a/crates/tinymist-query/src/goto_declaration.rs b/crates/tinymist-query/src/goto_declaration.rs index 31470d7d..859b4355 100644 --- a/crates/tinymist-query/src/goto_declaration.rs +++ b/crates/tinymist-query/src/goto_declaration.rs @@ -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, - _deref_target: DerefTarget<'_>, + _syntax: SyntaxClass<'_>, ) -> Option>> { todo!() } diff --git a/crates/tinymist-query/src/goto_definition.rs b/crates/tinymist-query/src/goto_definition.rs index c066d880..44e7de43 100644 --- a/crates/tinymist-query/src/goto_definition.rs +++ b/crates/tinymist-query/src/goto_definition.rs @@ -32,10 +32,10 @@ impl StatefulRequest for GotoDefinitionRequest { doc: Option, ) -> Option { 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()?; diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs index 85050457..cf2170a1 100644 --- a/crates/tinymist-query/src/hover.rs +++ b/crates/tinymist-query/src/hover.rs @@ -117,8 +117,8 @@ fn def_tooltip( cursor: usize, ) -> Option { 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())); diff --git a/crates/tinymist-query/src/prelude.rs b/crates/tinymist-query/src/prelude.rs index 86232f76..44d0f674 100644 --- a/crates/tinymist-query/src/prelude.rs +++ b/crates/tinymist-query/src/prelude.rs @@ -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}; diff --git a/crates/tinymist-query/src/prepare_rename.rs b/crates/tinymist-query/src/prepare_rename.rs index cc59ebd1..c7e9bec9 100644 --- a/crates/tinymist-query/src/prepare_rename.rs +++ b/crates/tinymist-query/src/prepare_rename.rs @@ -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, ) -> Option { 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)> { let name = def.name().clone(); diff --git a/crates/tinymist-query/src/references.rs b/crates/tinymist-query/src/references.rs index e0f3e8e8..eaedbf0a 100644 --- a/crates/tinymist-query/src/references.rs +++ b/crates/tinymist-query/src/references.rs @@ -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, ) -> Option { 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> { - 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(), diff --git a/crates/tinymist-query/src/rename.rs b/crates/tinymist-query/src/rename.rs index 302f529e..e9649d63 100644 --- a/crates/tinymist-query/src/rename.rs +++ b/crates/tinymist-query/src/rename.rs @@ -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, ) -> Option { 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::()?.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(); diff --git a/crates/tinymist-query/src/signature_help.rs b/crates/tinymist-query/src/signature_help.rs index d3102a5a..28ad15c3 100644 --- a/crates/tinymist-query/src/signature_help.rs +++ b/crates/tinymist-query/src/signature_help.rs @@ -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 == ¶m.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))); } diff --git a/crates/tinymist-query/src/syntax/matcher.rs b/crates/tinymist-query/src/syntax/matcher.rs index c0611f81..de758e59 100644 --- a/crates/tinymist-query/src/syntax/matcher.rs +++ b/crates/tinymist-query/src/syntax/matcher.rs @@ -1,59 +1,46 @@ use serde::{Deserialize, Serialize}; -use typst::foundations::{Func, ParamInfo}; use crate::prelude::*; -pub fn deref_expr(mut ancestor: LinkedNode) -> Option { - while !ancestor.is::() { - ancestor = ancestor.parent()?.clone(); - } - Some(ancestor) -} - -pub fn deref_lvalue(mut node: LinkedNode) -> Option { - while let Some(e) = node.cast::() { - node = node.find(e.expr().span())?; - } - if let Some(e) = node.parent() { - if let Some(f) = e.cast::() { - 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> { std::iter::successors(Some(node), |node| node.parent()) } -pub enum DecenderItem<'a> { +/// Finds the expression target. +pub fn deref_expr(node: LinkedNode) -> Option { + node_ancestors(&node).find(|n| n.is::()).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( +/// Finds the descent items starting from the given position. +pub fn descent_items( node: LinkedNode, - mut recv: impl FnMut(DecenderItem) -> Option, + mut recv: impl FnMut(DescentItem) -> Option, ) -> Option { 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( } 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( +/// Finds the descent decls starting from the given position. +pub fn descent_decls( node: LinkedNode, mut recv: impl FnMut(DescentDecl) -> Option, ) -> Option { - node_descenders(node, |node| { + descent_items(node, |node| { match (&node, node.node().cast::()?) { - (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( } } } - (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( } } } - (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( }) } +/// 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 { use SyntaxKind::*; Some(match k { @@ -273,51 +252,62 @@ pub(crate) fn interpret_mode_at_kind(k: SyntaxKind) -> Option { }) } -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> { +/// Classifies the syntax that can be operated on by IDE functionality. +pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option> { 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()?; 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 { + while let Some(e) = node.cast::() { + node = node.find(e.expr().span())?; + } + if let Some(e) = node.parent() { + if let Some(f) = e.cast::() { + 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 { 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::()?; // 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> { - 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> { + classify_def_(node, false) } -pub fn get_def_target(node: LinkedNode) -> Option> { - get_def_target_(node, true) +/// Classifies a definition under cursor strictly. +pub fn classify_def(node: LinkedNode) -> Option> { + classify_def_(node, true) } -fn get_def_target_(node: LinkedNode, strict: bool) -> Option> { +/// The internal implementation of classifying a definition. +fn classify_def_(node: LinkedNode, strict: bool) -> Option> { 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> { 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::()?; @@ -460,8 +483,8 @@ fn get_def_target_(node: LinkedNode, strict: bool) -> Option> { // 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> { 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> { 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> { }) } +/// 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>, 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> { 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> { - 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> { + 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::() { @@ -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> { +/// Classifies an expression under cursor that are preferred by type checking. +pub fn classify_cursor(node: LinkedNode) -> Option> { 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> { } } - 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> { 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> { _ => 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> { +fn cursor_on_callee<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option> { let parent = callee.parent()?; let args = match parent.cast::() { 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> { + param_kind: ArgSourceKind, +) -> Option> { if node.kind() == SyntaxKind::RightParen { node = node.prev_sibling()?; } match node.kind() { SyntaxKind::Named => { let param_ident = node.cast::()?.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::()?; - 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::() { 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::() { 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::() { 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 { - 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 { - match deciding.kind() { - // After colon: "func(param:|)", "func(param: |)". - SyntaxKind::Colon => { - let prev = deciding.prev_leaf()?; - let param_ident = prev.cast::()?; - 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::()); - 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 => ' ', } }) diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index 82473137..c428baf2 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -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::() { 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::()?; 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