mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
feat: classify field accesses for ide functions (#1034)
* feat: classify field accesses for ide functions * test: update snapshot
This commit is contained in:
parent
7dbaca8851
commit
39243ba626
9 changed files with 216 additions and 81 deletions
|
@ -5,7 +5,7 @@ use typst::introspection::Introspector;
|
|||
use typst::model::BibliographyElem;
|
||||
|
||||
use super::{prelude::*, InsTy, SharedContext};
|
||||
use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass};
|
||||
use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass, VarClass};
|
||||
use crate::ty::DocSource;
|
||||
use crate::VersionedDocument;
|
||||
|
||||
|
@ -63,10 +63,9 @@ pub fn definition(
|
|||
syntax: SyntaxClass,
|
||||
) -> Option<Definition> {
|
||||
match syntax {
|
||||
// todi: field access
|
||||
SyntaxClass::VarAccess(node) | SyntaxClass::Callee(node) => {
|
||||
find_ident_definition(ctx, source, node)
|
||||
}
|
||||
// todo: field access
|
||||
SyntaxClass::VarAccess(node) => find_ident_definition(ctx, source, node),
|
||||
SyntaxClass::Callee(node) => find_ident_definition(ctx, source, VarClass::Ident(node)),
|
||||
SyntaxClass::ImportPath(path) | SyntaxClass::IncludePath(path) => {
|
||||
DefResolver::new(ctx, source)?.of_span(path.span())
|
||||
}
|
||||
|
@ -97,16 +96,16 @@ pub fn definition(
|
|||
fn find_ident_definition(
|
||||
ctx: &Arc<SharedContext>,
|
||||
source: &Source,
|
||||
use_site: LinkedNode,
|
||||
use_site: VarClass,
|
||||
) -> Option<Definition> {
|
||||
// Lexical reference
|
||||
let ident_store = use_site.clone();
|
||||
let ident_ref = match ident_store.cast::<ast::Expr>()? {
|
||||
let ident_ref = match ident_store.node().cast::<ast::Expr>()? {
|
||||
ast::Expr::Ident(ident) => ident.span(),
|
||||
ast::Expr::MathIdent(ident) => ident.span(),
|
||||
ast::Expr::FieldAccess(field_access) => return field_definition(ctx, field_access),
|
||||
_ => {
|
||||
crate::log_debug_ct!("unsupported kind {kind:?}", kind = use_site.kind());
|
||||
crate::log_debug_ct!("unsupported kind {kind:?}", kind = use_site.node().kind());
|
||||
Span::detached()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::{
|
|||
ArgsTy, Sig, SigChecker, SigShape, SigSurfaceKind, SigTy, Ty, TyCtx, TyCtxMut, TypeBounds,
|
||||
TypeInfo, TypeVar,
|
||||
};
|
||||
use crate::syntax::{classify_cursor, classify_cursor_by_context, ArgClass, CursorClass};
|
||||
use crate::syntax::{classify_cursor, classify_cursor_by_context, ArgClass, CursorClass, VarClass};
|
||||
use crate::ty::BuiltinTy;
|
||||
|
||||
/// With given type information, check the type of a literal expression again by
|
||||
|
@ -182,7 +182,7 @@ impl<'a> PostTypeChecker<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
let contextual_self_ty = self.check_cursor(classify_cursor(node.clone()), context_ty);
|
||||
let contextual_self_ty = self.check_cursor(classify_cursor(node.clone(), None), context_ty);
|
||||
crate::log_debug_ct!(
|
||||
"post check(res): {:?}::{:?} -> {self_ty:?}, {contextual_self_ty:?}",
|
||||
context.kind(),
|
||||
|
@ -303,10 +303,14 @@ impl<'a> PostTypeChecker<'a> {
|
|||
allow_package: true,
|
||||
}),
|
||||
)),
|
||||
CursorClass::Label { node: target, .. } | CursorClass::Normal(target) => {
|
||||
CursorClass::VarAccess(VarClass::Ident(node))
|
||||
| CursorClass::VarAccess(VarClass::FieldAccess(node))
|
||||
| CursorClass::VarAccess(VarClass::DotAccess(node))
|
||||
| CursorClass::Label { node, .. }
|
||||
| CursorClass::Normal(node) => {
|
||||
let label_ty = matches!(cursor, CursorClass::Label { is_error: true, .. })
|
||||
.then_some(Ty::Builtin(BuiltinTy::Label));
|
||||
let ty = self.check_or(target, context_ty);
|
||||
let ty = self.check_or(node, context_ty);
|
||||
crate::log_debug_ct!("post check target normal: {ty:?} {label_ty:?}");
|
||||
ty.or(label_ty)
|
||||
}
|
||||
|
@ -335,7 +339,7 @@ impl<'a> PostTypeChecker<'a> {
|
|||
None,
|
||||
),
|
||||
// todo: constraint node
|
||||
SyntaxKind::Named => self.check_cursor(classify_cursor(context.clone()), None),
|
||||
SyntaxKind::Named => self.check_cursor(classify_cursor(context.clone(), None), None),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,8 @@ impl StatefulRequest for CompletionRequest {
|
|||
let explicit = false;
|
||||
|
||||
// Skip if is the let binding item *directly*
|
||||
if let Some(SyntaxClass::VarAccess(node)) = &syntax {
|
||||
if let Some(SyntaxClass::VarAccess(var)) = &syntax {
|
||||
let node = var.node();
|
||||
match node.parent_kind() {
|
||||
// complete the init part of the let binding
|
||||
Some(SyntaxKind::LetBinding) => {
|
||||
|
|
|
@ -8,28 +8,6 @@ snapshot_kind: text
|
|||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 5,
|
||||
"label": "mode",
|
||||
"labelDetails": {
|
||||
"description": "\"code\" | \"markup\" | \"math\""
|
||||
},
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
"newText": "mode: ${1:}",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 69,
|
||||
"line": 2
|
||||
},
|
||||
"start": {
|
||||
"character": 69,
|
||||
"line": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"items": []
|
||||
}
|
||||
]
|
||||
|
|
|
@ -8,28 +8,6 @@ snapshot_kind: text
|
|||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 5,
|
||||
"label": "mode",
|
||||
"labelDetails": {
|
||||
"description": "\"code\" | \"markup\" | \"math\""
|
||||
},
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
"newText": "mode: ${1:}",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 69,
|
||||
"line": 2
|
||||
},
|
||||
"start": {
|
||||
"character": 69,
|
||||
"line": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"items": []
|
||||
}
|
||||
]
|
||||
|
|
|
@ -33,7 +33,7 @@ impl SemanticRequest for SignatureHelpRequest {
|
|||
target,
|
||||
is_set,
|
||||
..
|
||||
} = classify_cursor(ast_node)?
|
||||
} = classify_cursor(ast_node, Some(cursor))?
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use reflexo_typst::debug_loc::SourceSpanOffset;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
@ -255,13 +256,109 @@ pub(crate) fn interpret_mode_at_kind(kind: SyntaxKind) -> Option<InterpretMode>
|
|||
})
|
||||
}
|
||||
|
||||
/// Classes of field syntax that can be operated on by IDE functionality.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FieldClass<'a> {
|
||||
Field(LinkedNode<'a>),
|
||||
DotSuffix(SourceSpanOffset),
|
||||
}
|
||||
|
||||
impl FieldClass<'_> {
|
||||
/// Gets the node of the field class.
|
||||
pub fn offset(&self, source: &Source) -> Option<usize> {
|
||||
Some(match self {
|
||||
Self::Field(node) => node.offset(),
|
||||
Self::DotSuffix(span_offset) => {
|
||||
source.find(span_offset.span)?.offset() + span_offset.offset
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Classes of variable (access) syntax that can be operated on by IDE
|
||||
/// functionality.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum VarClass<'a> {
|
||||
/// An identifier expression.
|
||||
Ident(LinkedNode<'a>),
|
||||
/// A field access expression.
|
||||
FieldAccess(LinkedNode<'a>),
|
||||
/// A dot access expression, for example, `#a.|`, `$a.|$`, or `x.|.y`.
|
||||
/// Note the cursor of the last example is on the middle of the spread
|
||||
/// operator.
|
||||
DotAccess(LinkedNode<'a>),
|
||||
}
|
||||
|
||||
impl<'a> VarClass<'a> {
|
||||
/// Gets the node of the var (access) class.
|
||||
pub fn node(&self) -> &LinkedNode<'a> {
|
||||
match self {
|
||||
Self::Ident(node) | Self::FieldAccess(node) | Self::DotAccess(node) => node,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the accessed node of the var (access) class.
|
||||
pub fn accessed_node(&self) -> Option<LinkedNode<'a>> {
|
||||
Some(match self {
|
||||
Self::Ident(node) => node.clone(),
|
||||
Self::FieldAccess(node) => {
|
||||
let field_access = node.cast::<ast::FieldAccess>()?;
|
||||
node.find(field_access.target().span())?
|
||||
}
|
||||
Self::DotAccess(node) => node.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the accessing field of the var (access) class.
|
||||
pub fn accessing_field(&self) -> Option<FieldClass<'a>> {
|
||||
match self {
|
||||
Self::FieldAccess(node) => {
|
||||
let dot = node
|
||||
.children()
|
||||
.find(|n| matches!(n.kind(), SyntaxKind::Dot))?;
|
||||
let mut iter_after_dot =
|
||||
node.children().skip_while(|n| n.kind() != SyntaxKind::Dot);
|
||||
let ident = iter_after_dot.find(|n| {
|
||||
matches!(
|
||||
n.kind(),
|
||||
SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::Error
|
||||
)
|
||||
});
|
||||
|
||||
let ident_case = ident.map(|ident| {
|
||||
if ident.text().is_empty() {
|
||||
FieldClass::DotSuffix(SourceSpanOffset {
|
||||
span: ident.span(),
|
||||
offset: 0,
|
||||
})
|
||||
} else {
|
||||
FieldClass::Field(ident)
|
||||
}
|
||||
});
|
||||
|
||||
ident_case.or_else(|| {
|
||||
Some(FieldClass::DotSuffix(SourceSpanOffset {
|
||||
span: dot.span(),
|
||||
offset: 1,
|
||||
}))
|
||||
})
|
||||
}
|
||||
Self::DotAccess(node) => Some(FieldClass::DotSuffix(SourceSpanOffset {
|
||||
span: node.span(),
|
||||
offset: node.range().len() + 1,
|
||||
})),
|
||||
Self::Ident(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Classes of syntax that can be operated on by IDE functionality.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SyntaxClass<'a> {
|
||||
/// A variable access expression.
|
||||
///
|
||||
/// It can be either an identifier or a field access.
|
||||
VarAccess(LinkedNode<'a>),
|
||||
VarAccess(VarClass<'a>),
|
||||
/// A (content) label expression.
|
||||
Label {
|
||||
node: LinkedNode<'a>,
|
||||
|
@ -299,9 +396,9 @@ impl<'a> SyntaxClass<'a> {
|
|||
/// Gets the node of the syntax class.
|
||||
pub fn node(&self) -> &LinkedNode<'a> {
|
||||
match self {
|
||||
SyntaxClass::VarAccess(cls) => cls.node(),
|
||||
SyntaxClass::Label { node, .. }
|
||||
| SyntaxClass::Ref(node)
|
||||
| SyntaxClass::VarAccess(node)
|
||||
| SyntaxClass::Callee(node)
|
||||
| SyntaxClass::ImportPath(node)
|
||||
| SyntaxClass::IncludePath(node)
|
||||
|
@ -344,6 +441,28 @@ pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option<SyntaxClass<'_
|
|||
node = node.prev_sibling()?;
|
||||
}
|
||||
|
||||
let is_dot = matches!(node.kind(), SyntaxKind::Dot)
|
||||
|| (matches!(node.kind(), SyntaxKind::Text | SyntaxKind::Error) && node.text() == ".");
|
||||
|
||||
if is_dot && node.offset() + 1 == cursor {
|
||||
let dot_target = node.clone().prev_leaf().and_then(first_ancestor_expr);
|
||||
|
||||
if let Some(dots_target) = dot_target {
|
||||
return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dots_target)));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(node.kind(), SyntaxKind::Dots) && node.offset() + 1 == cursor {
|
||||
let dot_target = node.parent()?;
|
||||
if dot_target.kind() == SyntaxKind::Spread {
|
||||
let dot_target = dot_target.prev_leaf().and_then(first_ancestor_expr);
|
||||
|
||||
if let Some(dot_target) = dot_target {
|
||||
return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the first ancestor that is an expression.
|
||||
let ancestor = first_ancestor_expr(node)?;
|
||||
crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
|
||||
|
@ -359,9 +478,10 @@ pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option<SyntaxClass<'_
|
|||
ast::Expr::Ref(..) => SyntaxClass::Ref(adjusted),
|
||||
ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
|
||||
ast::Expr::Set(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
|
||||
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
|
||||
SyntaxClass::VarAccess(adjusted)
|
||||
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
|
||||
SyntaxClass::VarAccess(VarClass::Ident(adjusted))
|
||||
}
|
||||
ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
|
||||
ast::Expr::Str(..) => {
|
||||
let parent = adjusted.parent()?;
|
||||
if parent.kind() == SyntaxKind::ModuleImport {
|
||||
|
@ -571,6 +691,10 @@ pub enum CursorClass<'a> {
|
|||
container: LinkedNode<'a>,
|
||||
is_before: bool,
|
||||
},
|
||||
/// A variable access expression.
|
||||
///
|
||||
/// It can be either an identifier or a field access.
|
||||
VarAccess(VarClass<'a>),
|
||||
/// A cursor on an import path.
|
||||
ImportPath(LinkedNode<'a>),
|
||||
/// A cursor on an include path.
|
||||
|
@ -592,6 +716,7 @@ impl<'a> CursorClass<'a> {
|
|||
ArgClass::Positional { .. } => return None,
|
||||
ArgClass::Named(node) => node.clone(),
|
||||
},
|
||||
CursorClass::VarAccess(cls) => cls.node().clone(),
|
||||
CursorClass::Paren { container, .. } => container.clone(),
|
||||
CursorClass::Label { node, .. }
|
||||
| CursorClass::ImportPath(node)
|
||||
|
@ -648,7 +773,7 @@ pub fn classify_cursor_by_context<'a>(
|
|||
}
|
||||
|
||||
/// Classifies a cursor syntax that are preferred by type checking.
|
||||
pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
|
||||
pub fn classify_cursor(node: LinkedNode, cursor: Option<usize>) -> Option<CursorClass<'_>> {
|
||||
let mut node = node;
|
||||
if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
|
||||
loop {
|
||||
|
@ -660,7 +785,8 @@ pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
|
|||
}
|
||||
}
|
||||
|
||||
let syntax = classify_syntax(node.clone(), node.offset())?;
|
||||
let cursor = cursor.unwrap_or_else(|| node.offset());
|
||||
let syntax = classify_syntax(node.clone(), cursor)?;
|
||||
|
||||
let normal_syntax = match syntax {
|
||||
SyntaxClass::Callee(callee) => {
|
||||
|
@ -675,7 +801,7 @@ pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
|
|||
SyntaxClass::IncludePath(node) => {
|
||||
return Some(CursorClass::IncludePath(node));
|
||||
}
|
||||
syntax => syntax.node().clone(),
|
||||
syntax => syntax,
|
||||
};
|
||||
|
||||
let Some(mut node_parent) = node.parent().cloned() else {
|
||||
|
@ -739,7 +865,10 @@ pub fn classify_cursor(node: LinkedNode) -> Option<CursorClass<'_>> {
|
|||
is_before,
|
||||
})
|
||||
}
|
||||
_ => Some(CursorClass::Normal(normal_syntax)),
|
||||
_ => Some(match normal_syntax {
|
||||
SyntaxClass::VarAccess(v) => CursorClass::VarAccess(v),
|
||||
normal_syntax => CursorClass::Normal(normal_syntax.node().clone()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,11 +1013,12 @@ mod tests {
|
|||
fn map_cursor(source: &str) -> String {
|
||||
map_node(source, |root, cursor| {
|
||||
let node = root.leaf_at_compat(cursor);
|
||||
let kind = node.and_then(|node| classify_cursor(node));
|
||||
let kind = node.and_then(|node| classify_cursor(node, Some(cursor)));
|
||||
match kind {
|
||||
Some(CursorClass::Arg { .. }) => 'p',
|
||||
Some(CursorClass::Element { .. }) => 'e',
|
||||
Some(CursorClass::Paren { .. }) => 'P',
|
||||
Some(CursorClass::VarAccess { .. }) => 'v',
|
||||
Some(CursorClass::ImportPath(..)) => 'i',
|
||||
Some(CursorClass::IncludePath(..)) => 'I',
|
||||
Some(CursorClass::Label { .. }) => 'l',
|
||||
|
@ -935,20 +1065,20 @@ Text
|
|||
= Heading #let y = 2;
|
||||
== Heading"#).trim(), @r"
|
||||
#let x = 1
|
||||
nnnnnnnnn
|
||||
nnnnvvnnn
|
||||
Text
|
||||
|
||||
= Heading #let y = 2;
|
||||
nnnnnnnnn
|
||||
nnnnvvnnn
|
||||
== Heading
|
||||
");
|
||||
assert_snapshot!(map_cursor(r#"#let f(x);"#).trim(), @r"
|
||||
#let f(x);
|
||||
nnnnn n
|
||||
nnnnv v
|
||||
");
|
||||
assert_snapshot!(map_cursor(r#"#f(1, 2) Test"#).trim(), @r"
|
||||
#f(1, 2) Test
|
||||
npppppp
|
||||
vpppppp
|
||||
");
|
||||
assert_snapshot!(map_cursor(r#"#() Test"#).trim(), @r"
|
||||
#() Test
|
||||
|
@ -973,4 +1103,42 @@ Text
|
|||
Test
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_field() {
|
||||
fn test_fn(s: &str, cursor: i32) -> String {
|
||||
test_fn_(s, cursor).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn test_fn_(s: &str, cursor: i32) -> Option<String> {
|
||||
let cursor = if cursor < 0 {
|
||||
s.len() as i32 + cursor
|
||||
} else {
|
||||
cursor
|
||||
};
|
||||
let source = Source::detached(s.to_owned());
|
||||
let root = LinkedNode::new(source.root());
|
||||
let node = root.leaf_at_compat(cursor as usize)?;
|
||||
let syntax = classify_syntax(node, cursor as usize)?;
|
||||
let SyntaxClass::VarAccess(var) = syntax else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let field = var.accessing_field()?;
|
||||
Some(match field {
|
||||
FieldClass::Field(ident) => format!("Field: {}", ident.text()),
|
||||
FieldClass::DotSuffix(span_offset) => {
|
||||
let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
|
||||
format!("DotSuffix: {offset:?}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
assert_snapshot!(test_fn("#(a.b)", 5), @r"Field: b");
|
||||
assert_snapshot!(test_fn("#a.", 3), @"DotSuffix: 3");
|
||||
assert_snapshot!(test_fn("$a.$", 3), @"DotSuffix: 3");
|
||||
assert_snapshot!(test_fn("#(a.)", 4), @"DotSuffix: 4");
|
||||
assert_snapshot!(test_fn("#(a..b)", 4), @"DotSuffix: 4");
|
||||
assert_snapshot!(test_fn("#(a..b())", 4), @"DotSuffix: 4");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use crate::snippet::{
|
|||
};
|
||||
use crate::syntax::{
|
||||
interpret_mode_at, is_ident_like, previous_decls, CursorClass, InterpretMode, PreviousDecl,
|
||||
VarClass,
|
||||
};
|
||||
use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeInfo, TypeVar};
|
||||
use crate::upstream::complete::complete_code;
|
||||
|
@ -1373,7 +1374,7 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
|
|||
use crate::syntax::classify_cursor;
|
||||
use SurroundingSyntax::*;
|
||||
|
||||
let cursor_class = classify_cursor(ctx.leaf.clone());
|
||||
let cursor_class = classify_cursor(ctx.leaf.clone(), Some(ctx.cursor));
|
||||
crate::log_debug_ct!("complete_type: pos {:?} -> {cursor_class:#?}", ctx.leaf);
|
||||
let mut args_node = None;
|
||||
|
||||
|
@ -1396,6 +1397,11 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
|
|||
}
|
||||
args_node = Some(args.to_untyped().clone());
|
||||
}
|
||||
// todo: complete field by types
|
||||
Some(CursorClass::VarAccess(VarClass::FieldAccess { .. }))
|
||||
| Some(CursorClass::VarAccess(VarClass::DotAccess { .. })) => {
|
||||
return None;
|
||||
}
|
||||
Some(CursorClass::ImportPath(path) | CursorClass::IncludePath(path)) => {
|
||||
let Some(ast::Expr::Str(str)) = path.cast() else {
|
||||
return None;
|
||||
|
@ -1429,11 +1435,12 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
|
|||
{
|
||||
args_node = node.parent().map(|s| s.get().clone());
|
||||
}
|
||||
// todo: complete type field
|
||||
Some(CursorClass::Normal(node)) if matches!(node.kind(), SyntaxKind::FieldAccess) => {
|
||||
return None;
|
||||
}
|
||||
Some(CursorClass::Paren { .. } | CursorClass::Label { .. } | CursorClass::Normal(..))
|
||||
Some(
|
||||
CursorClass::VarAccess(VarClass::Ident { .. })
|
||||
| CursorClass::Paren { .. }
|
||||
| CursorClass::Label { .. }
|
||||
| CursorClass::Normal(..),
|
||||
)
|
||||
| None => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -385,7 +385,7 @@ fn e2e() {
|
|||
});
|
||||
|
||||
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:ac449ba75867cd79a6135b0285c0cf47");
|
||||
insta::assert_snapshot!(hash, @"siphash128_13:12bc49a33793e415352373ce601c715f");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue