1use serde::{Deserialize, Serialize};
60use tinymist_world::debug_loc::SourceSpanOffset;
61use typst::syntax::Span;
62
63use crate::prelude::*;
64
65pub fn node_ancestors<'a, 'b>(
67 node: &'b LinkedNode<'a>,
68) -> impl Iterator<Item = &'b LinkedNode<'a>> {
69 std::iter::successors(Some(node), |node| node.parent())
70}
71
72pub fn first_ancestor_expr(node: LinkedNode) -> Option<LinkedNode> {
74 node_ancestors(&node)
75 .find(|n| n.is::<ast::Expr>())
76 .map(|mut node| {
77 while matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) {
78 let Some(parent) = node.parent() else {
79 return node;
80 };
81
82 let Some(field_access) = parent.cast::<ast::FieldAccess>() else {
83 return node;
84 };
85
86 let dot = parent
87 .children()
88 .find(|n| matches!(n.kind(), SyntaxKind::Dot));
89
90 if dot.is_some_and(|dot| {
94 dot.offset() <= node.offset() && field_access.field().span() == node.span()
95 }) {
96 node = parent;
97 } else {
98 return node;
99 }
100 }
101
102 node
103 })
104 .cloned()
105}
106
107pub enum PreviousItem<'a> {
110 Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
112 Sibling(&'a LinkedNode<'a>),
114}
115
116impl<'a> PreviousItem<'a> {
117 pub fn node(&self) -> &'a LinkedNode<'a> {
119 match self {
120 PreviousItem::Sibling(node) => node,
121 PreviousItem::Parent(node, _) => node,
122 }
123 }
124}
125
126pub fn previous_items<T>(
129 node: LinkedNode,
130 mut recv: impl FnMut(PreviousItem) -> Option<T>,
131) -> Option<T> {
132 let mut ancestor = Some(node);
133 while let Some(node) = &ancestor {
134 let mut sibling = Some(node.clone());
135 while let Some(node) = &sibling {
136 if let Some(v) = recv(PreviousItem::Sibling(node)) {
137 return Some(v);
138 }
139
140 sibling = node.prev_sibling();
141 }
142
143 if let Some(parent) = node.parent() {
144 if let Some(v) = recv(PreviousItem::Parent(parent, node)) {
145 return Some(v);
146 }
147
148 ancestor = Some(parent.clone());
149 continue;
150 }
151
152 break;
153 }
154
155 None
156}
157
158pub enum PreviousDecl<'a> {
161 Ident(ast::Ident<'a>),
171 ImportSource(ast::Expr<'a>),
181 ImportAll(ast::ModuleImport<'a>),
191}
192
193pub fn previous_decls<T>(
196 node: LinkedNode,
197 mut recv: impl FnMut(PreviousDecl) -> Option<T>,
198) -> Option<T> {
199 previous_items(node, |item| {
200 match (&item, item.node().cast::<ast::Expr>()?) {
201 (PreviousItem::Sibling(..), ast::Expr::Let(lb)) => {
202 for ident in lb.kind().bindings() {
203 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
204 return Some(t);
205 }
206 }
207 }
208 (PreviousItem::Sibling(..), ast::Expr::Import(import)) => {
209 match import.imports() {
211 Some(ast::Imports::Wildcard) => {
212 if let Some(t) = recv(PreviousDecl::ImportAll(import)) {
213 return Some(t);
214 }
215 }
216 Some(ast::Imports::Items(items)) => {
217 for item in items.iter() {
218 if let Some(t) = recv(PreviousDecl::Ident(item.bound_name())) {
219 return Some(t);
220 }
221 }
222 }
223 _ => {}
224 }
225
226 if let Some(new_name) = import.new_name() {
228 if let Some(t) = recv(PreviousDecl::Ident(new_name)) {
229 return Some(t);
230 }
231 } else if import.imports().is_none()
232 && let Some(t) = recv(PreviousDecl::ImportSource(import.source()))
233 {
234 return Some(t);
235 }
236 }
237 (PreviousItem::Parent(parent, child), ast::Expr::For(for_expr)) => {
238 let body = parent.find(for_expr.body().span());
239 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
240 if !in_body {
241 return None;
242 }
243
244 for ident in for_expr.pattern().bindings() {
245 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
246 return Some(t);
247 }
248 }
249 }
250 (PreviousItem::Parent(parent, child), ast::Expr::Closure(closure)) => {
251 let body = parent.find(closure.body().span());
252 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
253 if !in_body {
254 return None;
255 }
256
257 for param in closure.params().children() {
258 match param {
259 ast::Param::Pos(pos) => {
260 for ident in pos.bindings() {
261 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
262 return Some(t);
263 }
264 }
265 }
266 ast::Param::Named(named) => {
267 if let Some(t) = recv(PreviousDecl::Ident(named.name())) {
268 return Some(t);
269 }
270 }
271 ast::Param::Spread(spread) => {
272 if let Some(sink_ident) = spread.sink_ident()
273 && let Some(t) = recv(PreviousDecl::Ident(sink_ident))
274 {
275 return Some(t);
276 }
277 }
278 }
279 }
280 }
281 _ => {}
282 };
283 None
284 })
285}
286
287pub fn is_mark(sk: SyntaxKind) -> bool {
289 use SyntaxKind::*;
290 #[allow(clippy::match_like_matches_macro)]
291 match sk {
292 MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
293 Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
294 LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
295 Slash | Hat | Comma | Semicolon | Colon | Hash => true,
296 _ => false,
297 }
298}
299
300pub fn is_ident_like(node: &SyntaxNode) -> bool {
302 fn can_be_ident(node: &SyntaxNode) -> bool {
303 typst::syntax::is_ident(node.text())
304 }
305
306 use SyntaxKind::*;
307 let kind = node.kind();
308 matches!(kind, Ident | MathIdent | Underscore)
309 || (matches!(kind, Error) && can_be_ident(node))
310 || kind.is_keyword()
311}
312
313#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
315#[serde(rename_all = "camelCase")]
316pub enum InterpretMode {
317 Comment,
319 String,
321 Raw,
323 Markup,
325 Code,
327 Math,
329}
330
331pub fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode {
334 loop {
335 crate::log_debug_ct!("leaf for mode: {leaf:?}");
336 if let Some(t) = leaf {
337 if let Some(mode) = interpret_mode_at_kind(t.kind()) {
338 break mode;
339 }
340
341 if !t.kind().is_trivia() && {
342 t.prev_leaf().is_some_and(|n| n.kind() == SyntaxKind::Hash)
344 } {
345 return InterpretMode::Code;
346 }
347
348 leaf = t.parent();
349 } else {
350 break InterpretMode::Markup;
351 }
352 }
353}
354
355pub(crate) fn interpret_mode_at_kind(kind: SyntaxKind) -> Option<InterpretMode> {
357 use SyntaxKind::*;
358 Some(match kind {
359 LineComment | BlockComment | Shebang => InterpretMode::Comment,
360 Raw => InterpretMode::Raw,
361 Str => InterpretMode::String,
362 CodeBlock | Code => InterpretMode::Code,
363 ContentBlock | Markup => InterpretMode::Markup,
364 Equation | Math => InterpretMode::Math,
365 Hash => InterpretMode::Code,
366 Label | Text | Ident | Args | FuncCall | FieldAccess | Bool | Int | Float | Numeric
367 | Space | Linebreak | Parbreak | Escape | Shorthand | SmartQuote | RawLang | RawDelim
368 | RawTrimmed | LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen
369 | RightParen | Comma | Semicolon | Colon | Star | Underscore | Dollar | Plus | Minus
370 | Slash | Hat | Prime | Dot | Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq
371 | HyphEq | StarEq | SlashEq | Dots | Arrow | Root | Not | And | Or | None | Auto | As
372 | Named | Keyed | Spread | Error | End => return Option::None,
373 Strong | Emph | Link | Ref | RefMarker | Heading | HeadingMarker | ListItem
374 | ListMarker | EnumItem | EnumMarker | TermItem | TermMarker => InterpretMode::Markup,
375 MathIdent | MathAlignPoint | MathDelimited | MathAttach | MathPrimes | MathFrac
376 | MathRoot | MathShorthand | MathText => InterpretMode::Math,
377 Let | Set | Show | Context | If | Else | For | In | While | Break | Continue | Return
378 | Import | Include | Closure | Params | LetBinding | SetRule | ShowRule | Contextual
379 | Conditional | WhileLoop | ForLoop | LoopBreak | ModuleImport | ImportItems
380 | ImportItemPath | RenamedImportItem | ModuleInclude | LoopContinue | FuncReturn
381 | Unary | Binary | Parenthesized | Dict | Array | Destructuring | DestructAssignment => {
382 InterpretMode::Code
383 }
384 })
385}
386
387#[derive(Debug, Clone)]
389pub enum DefClass<'a> {
390 Let(LinkedNode<'a>),
392 Import(LinkedNode<'a>),
394}
395
396impl DefClass<'_> {
397 pub fn node(&self) -> &LinkedNode<'_> {
399 match self {
400 DefClass::Let(node) => node,
401 DefClass::Import(node) => node,
402 }
403 }
404
405 pub fn name(&self) -> Option<LinkedNode<'_>> {
407 match self {
408 DefClass::Let(node) => {
409 let lb: ast::LetBinding<'_> = node.cast()?;
410 let names = match lb.kind() {
411 ast::LetBindingKind::Closure(name) => node.find(name.span())?,
412 ast::LetBindingKind::Normal(ast::Pattern::Normal(name)) => {
413 node.find(name.span())?
414 }
415 _ => return None,
416 };
417
418 Some(names)
419 }
420 DefClass::Import(_node) => {
421 None
425 }
426 }
427 }
428
429 pub fn name_range(&self) -> Option<Range<usize>> {
431 self.name().map(|node| node.range())
432 }
433}
434
435pub fn classify_def_loosely(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
438 classify_def_(node, false)
439}
440
441pub fn classify_def(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
443 classify_def_(node, true)
444}
445
446fn classify_def_(node: LinkedNode<'_>, strict: bool) -> Option<DefClass<'_>> {
448 let mut ancestor = node;
449 if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
450 ancestor = ancestor.prev_sibling()?;
451 }
452
453 while !ancestor.is::<ast::Expr>() {
454 ancestor = ancestor.parent()?.clone();
455 }
456 crate::log_debug_ct!("ancestor: {ancestor:?}");
457 let adjusted = adjust_expr(ancestor)?;
458 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
459
460 let may_ident = adjusted.cast::<ast::Expr>()?;
461 if strict && !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
462 return None;
463 }
464
465 let expr = may_ident;
466 Some(match expr {
467 ast::Expr::FuncCall(..) => return None,
470 ast::Expr::Set(..) => return None,
471 ast::Expr::Let(..) => DefClass::Let(adjusted),
472 ast::Expr::Import(..) => DefClass::Import(adjusted),
473 ast::Expr::Ident(..)
475 | ast::Expr::MathIdent(..)
476 | ast::Expr::FieldAccess(..)
477 | ast::Expr::Closure(..) => {
478 let mut ancestor = adjusted;
479 while !ancestor.is::<ast::LetBinding>() {
480 ancestor = ancestor.parent()?.clone();
481 }
482
483 DefClass::Let(ancestor)
484 }
485 ast::Expr::Str(..) => {
486 let parent = adjusted.parent()?;
487 if parent.kind() != SyntaxKind::ModuleImport {
488 return None;
489 }
490
491 DefClass::Import(parent.clone())
492 }
493 _ if expr.hash() => return None,
494 _ => {
495 crate::log_debug_ct!("unsupported kind {:?}", adjusted.kind());
496 return None;
497 }
498 })
499}
500
501pub fn adjust_expr(mut node: LinkedNode) -> Option<LinkedNode> {
506 while let Some(paren_expr) = node.cast::<ast::Parenthesized>() {
507 node = node.find(paren_expr.expr().span())?;
508 }
509 if let Some(parent) = node.parent()
510 && let Some(field_access) = parent.cast::<ast::FieldAccess>()
511 && node.span() == field_access.field().span()
512 {
513 return Some(parent.clone());
514 }
515 Some(node)
516}
517
518#[derive(Debug, Clone)]
520pub enum FieldClass<'a> {
521 Field(LinkedNode<'a>),
531
532 DotSuffix(SourceSpanOffset),
542}
543
544impl FieldClass<'_> {
545 pub fn offset(&self, source: &Source) -> Option<usize> {
547 Some(match self {
548 Self::Field(node) => node.offset(),
549 Self::DotSuffix(span_offset) => {
550 source.find(span_offset.span)?.offset() + span_offset.offset
551 }
552 })
553 }
554}
555
556#[derive(Debug, Clone)]
559pub enum VarClass<'a> {
560 Ident(LinkedNode<'a>),
562 FieldAccess(LinkedNode<'a>),
564 DotAccess(LinkedNode<'a>),
568}
569
570impl<'a> VarClass<'a> {
571 pub fn node(&self) -> &LinkedNode<'a> {
573 match self {
574 Self::Ident(node) | Self::FieldAccess(node) | Self::DotAccess(node) => node,
575 }
576 }
577
578 pub fn accessed_node(&self) -> Option<LinkedNode<'a>> {
580 Some(match self {
581 Self::Ident(node) => node.clone(),
582 Self::FieldAccess(node) => {
583 let field_access = node.cast::<ast::FieldAccess>()?;
584 node.find(field_access.target().span())?
585 }
586 Self::DotAccess(node) => node.clone(),
587 })
588 }
589
590 pub fn accessing_field(&self) -> Option<FieldClass<'a>> {
592 match self {
593 Self::FieldAccess(node) => {
594 let dot = node
595 .children()
596 .find(|n| matches!(n.kind(), SyntaxKind::Dot))?;
597 let mut iter_after_dot =
598 node.children().skip_while(|n| n.kind() != SyntaxKind::Dot);
599 let ident = iter_after_dot.find(|n| {
600 matches!(
601 n.kind(),
602 SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::Error
603 )
604 });
605
606 let ident_case = ident.map(|ident| {
607 if ident.text().is_empty() {
608 FieldClass::DotSuffix(SourceSpanOffset {
609 span: ident.span(),
610 offset: 0,
611 })
612 } else {
613 FieldClass::Field(ident)
614 }
615 });
616
617 ident_case.or_else(|| {
618 Some(FieldClass::DotSuffix(SourceSpanOffset {
619 span: dot.span(),
620 offset: 1,
621 }))
622 })
623 }
624 Self::DotAccess(node) => Some(FieldClass::DotSuffix(SourceSpanOffset {
625 span: node.span(),
626 offset: node.range().len() + 1,
627 })),
628 Self::Ident(..) => None,
629 }
630 }
631}
632
633#[derive(Debug, Clone)]
635pub enum SyntaxClass<'a> {
636 VarAccess(VarClass<'a>),
640 Label {
642 node: LinkedNode<'a>,
644 is_error: bool,
646 },
647 Ref {
649 node: LinkedNode<'a>,
651 suffix_colon: bool,
654 },
655 Callee(LinkedNode<'a>),
657 ImportPath(LinkedNode<'a>),
659 IncludePath(LinkedNode<'a>),
661 Normal(SyntaxKind, LinkedNode<'a>),
663}
664
665impl<'a> SyntaxClass<'a> {
666 pub fn label(node: LinkedNode<'a>) -> Self {
668 Self::Label {
669 node,
670 is_error: false,
671 }
672 }
673
674 pub fn error_as_label(node: LinkedNode<'a>) -> Self {
676 Self::Label {
677 node,
678 is_error: true,
679 }
680 }
681
682 pub fn node(&self) -> &LinkedNode<'a> {
684 match self {
685 SyntaxClass::VarAccess(cls) => cls.node(),
686 SyntaxClass::Label { node, .. }
687 | SyntaxClass::Ref { node, .. }
688 | SyntaxClass::Callee(node)
689 | SyntaxClass::ImportPath(node)
690 | SyntaxClass::IncludePath(node)
691 | SyntaxClass::Normal(_, node) => node,
692 }
693 }
694
695 pub fn complete_offset(&self) -> Option<usize> {
697 match self {
698 SyntaxClass::Label { node, .. } => Some(node.offset() + 1),
701 _ => None,
702 }
703 }
704}
705
706pub fn classify_syntax(node: LinkedNode<'_>, cursor: usize) -> Option<SyntaxClass<'_>> {
709 if matches!(node.kind(), SyntaxKind::Error) && node.text().starts_with('<') {
710 return Some(SyntaxClass::error_as_label(node));
711 }
712
713 fn can_skip_trivia(node: &LinkedNode, cursor: usize) -> bool {
715 if !node.kind().is_trivia() || !node.parent_kind().is_some_and(possible_in_code_trivia) {
717 return false;
718 }
719
720 let previous_text = node.text().as_bytes();
722 let previous_text = if node.range().contains(&cursor) {
723 &previous_text[..cursor - node.offset()]
724 } else {
725 previous_text
726 };
727
728 !previous_text.contains(&b'\n')
733 }
734
735 let mut node = node;
737 if can_skip_trivia(&node, cursor) {
738 node = node.prev_sibling()?;
739 }
740
741 fn classify_dot_access<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
747 let prev_leaf = node.prev_leaf();
748 let mode = interpret_mode_at(Some(node));
749
750 if matches!(mode, InterpretMode::Markup | InterpretMode::Math)
752 && prev_leaf
753 .as_ref()
754 .is_some_and(|leaf| leaf.range().end < node.offset())
755 {
756 return None;
757 }
758
759 if matches!(mode, InterpretMode::Math)
760 && prev_leaf.as_ref().is_some_and(|leaf| {
761 node_ancestors(leaf)
763 .find(|t| matches!(t.kind(), SyntaxKind::Equation))
764 .is_some_and(|parent| parent.offset() == leaf.offset())
765 })
766 {
767 return None;
768 }
769
770 let dot_target = prev_leaf.and_then(first_ancestor_expr)?;
771
772 if matches!(mode, InterpretMode::Math | InterpretMode::Code) || {
773 matches!(mode, InterpretMode::Markup)
774 && (matches!(
775 dot_target.kind(),
776 SyntaxKind::Ident
777 | SyntaxKind::MathIdent
778 | SyntaxKind::FieldAccess
779 | SyntaxKind::FuncCall
780 ) || (matches!(
781 dot_target.prev_leaf().as_deref().map(SyntaxNode::kind),
782 Some(SyntaxKind::Hash)
783 )))
784 } {
785 return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
786 }
787
788 None
789 }
790
791 if node.offset() + 1 == cursor
792 && {
793 matches!(node.kind(), SyntaxKind::Dot)
795 || (matches!(
796 node.kind(),
797 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
798 ) && node.text().starts_with("."))
799 }
800 && let Some(dot_access) = classify_dot_access(&node)
801 {
802 return Some(dot_access);
803 }
804
805 if node.offset() + 1 == cursor
806 && matches!(node.kind(), SyntaxKind::Dots)
807 && matches!(node.parent_kind(), Some(SyntaxKind::Spread))
808 && let Some(dot_access) = classify_dot_access(&node)
809 {
810 return Some(dot_access);
811 }
812
813 fn classify_ref<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
818 let prev_leaf = node.prev_leaf()?;
819
820 if matches!(prev_leaf.kind(), SyntaxKind::RefMarker)
821 && prev_leaf.range().end == node.offset()
822 {
823 return Some(SyntaxClass::Ref {
824 node: prev_leaf,
825 suffix_colon: true,
826 });
827 }
828
829 None
830 }
831
832 if node.offset() + 1 == cursor
833 && {
834 matches!(node.kind(), SyntaxKind::Colon)
836 || (matches!(
837 node.kind(),
838 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
839 ) && node.text().starts_with(":"))
840 }
841 && let Some(ref_syntax) = classify_ref(&node)
842 {
843 return Some(ref_syntax);
844 }
845
846 if matches!(node.kind(), SyntaxKind::Text | SyntaxKind::MathText) {
848 let mode = interpret_mode_at(Some(&node));
849 if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
850 return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
851 }
852 }
853
854 let ancestor = first_ancestor_expr(node)?;
856 crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
857
858 let adjusted = adjust_expr(ancestor)?;
860 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
861
862 let expr = adjusted.cast::<ast::Expr>()?;
864 Some(match expr {
865 ast::Expr::Label(..) => SyntaxClass::label(adjusted),
866 ast::Expr::Ref(..) => SyntaxClass::Ref {
867 node: adjusted,
868 suffix_colon: false,
869 },
870 ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
871 ast::Expr::Set(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
872 ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
873 SyntaxClass::VarAccess(VarClass::Ident(adjusted))
874 }
875 ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
876 ast::Expr::Str(..) => {
877 let parent = adjusted.parent()?;
878 if parent.kind() == SyntaxKind::ModuleImport {
879 SyntaxClass::ImportPath(adjusted)
880 } else if parent.kind() == SyntaxKind::ModuleInclude {
881 SyntaxClass::IncludePath(adjusted)
882 } else {
883 SyntaxClass::Normal(adjusted.kind(), adjusted)
884 }
885 }
886 _ if expr.hash()
887 || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
888 {
889 SyntaxClass::Normal(adjusted.kind(), adjusted)
890 }
891 _ => return None,
892 })
893}
894
895fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
898 !matches!(
899 interpret_mode_at_kind(kind),
900 Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
901 )
902}
903
904#[derive(Debug, Clone)]
906pub enum ArgClass<'a> {
907 Positional {
909 spreads: EcoVec<LinkedNode<'a>>,
911 positional: usize,
913 is_spread: bool,
915 },
916 Named(LinkedNode<'a>),
918}
919
920impl ArgClass<'_> {
921 pub fn first_positional() -> Self {
923 ArgClass::Positional {
924 spreads: EcoVec::new(),
925 positional: 0,
926 is_spread: false,
927 }
928 }
929}
930
931#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
934pub enum SurroundingSyntax {
935 Regular,
937 StringContent,
939 Selector,
941 ShowTransform,
943 ImportList,
945 SetRule,
947 ParamList,
949}
950
951pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
953 check_previous_syntax(node)
954 .or_else(|| check_surrounding_syntax(node))
955 .unwrap_or(SurroundingSyntax::Regular)
956}
957
958fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
959 use SurroundingSyntax::*;
960 let mut met_args = false;
961
962 if matches!(leaf.kind(), SyntaxKind::Str) {
963 return Some(StringContent);
964 }
965
966 while let Some(parent) = leaf.parent() {
967 crate::log_debug_ct!(
968 "check_surrounding_syntax: {:?}::{:?}",
969 parent.kind(),
970 leaf.kind()
971 );
972 match parent.kind() {
973 SyntaxKind::CodeBlock
974 | SyntaxKind::ContentBlock
975 | SyntaxKind::Equation
976 | SyntaxKind::Closure => {
977 return Some(Regular);
978 }
979 SyntaxKind::ImportItemPath
980 | SyntaxKind::ImportItems
981 | SyntaxKind::RenamedImportItem => {
982 return Some(ImportList);
983 }
984 SyntaxKind::ModuleImport => {
985 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
986 let Some(colon) = colon else {
987 return Some(Regular);
988 };
989
990 if leaf.offset() >= colon.offset() {
991 return Some(ImportList);
992 } else {
993 return Some(Regular);
994 }
995 }
996 SyntaxKind::Named => {
997 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
998 let Some(colon) = colon else {
999 return Some(Regular);
1000 };
1001
1002 return if leaf.offset() >= colon.offset() {
1003 Some(Regular)
1004 } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
1005 Some(ParamList)
1006 } else {
1007 Some(Regular)
1008 };
1009 }
1010 SyntaxKind::Params => {
1011 return Some(ParamList);
1012 }
1013 SyntaxKind::Args => {
1014 met_args = true;
1015 }
1016 SyntaxKind::SetRule => {
1017 let rule = parent.get().cast::<ast::SetRule>()?;
1018 if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
1019 return Some(Regular);
1020 } else {
1021 return Some(SetRule);
1022 }
1023 }
1024 SyntaxKind::ShowRule => {
1025 if met_args {
1026 return Some(Regular);
1027 }
1028
1029 let rule = parent.get().cast::<ast::ShowRule>()?;
1030 let colon = rule
1031 .to_untyped()
1032 .children()
1033 .find(|s| s.kind() == SyntaxKind::Colon);
1034 let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
1035 return Some(Selector);
1037 };
1038
1039 if leaf.offset() >= colon.offset() {
1040 return Some(ShowTransform);
1041 } else {
1042 return Some(Selector); }
1044 }
1045 _ => {}
1046 }
1047
1048 leaf = parent;
1049 }
1050
1051 None
1052}
1053
1054fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
1056 let mut leaf = leaf.clone();
1057 if leaf.kind().is_trivia() {
1058 leaf = leaf.prev_sibling()?;
1059 }
1060 if matches!(
1061 leaf.kind(),
1062 SyntaxKind::ShowRule
1063 | SyntaxKind::SetRule
1064 | SyntaxKind::ModuleImport
1065 | SyntaxKind::ModuleInclude
1066 ) {
1067 return check_surrounding_syntax(&leaf.rightmost_leaf()?);
1068 }
1069
1070 if matches!(leaf.kind(), SyntaxKind::Show) {
1071 return Some(SurroundingSyntax::Selector);
1072 }
1073 if matches!(leaf.kind(), SyntaxKind::Set) {
1074 return Some(SurroundingSyntax::SetRule);
1075 }
1076
1077 None
1078}
1079
1080fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
1082 s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
1083}
1084
1085#[derive(Debug, Clone)]
1093pub enum SyntaxContext<'a> {
1094 Arg {
1096 callee: LinkedNode<'a>,
1098 args: LinkedNode<'a>,
1100 target: ArgClass<'a>,
1102 is_set: bool,
1104 },
1105 Element {
1107 container: LinkedNode<'a>,
1109 target: ArgClass<'a>,
1111 },
1112 Paren {
1114 container: LinkedNode<'a>,
1116 is_before: bool,
1119 },
1120 VarAccess(VarClass<'a>),
1124 ImportPath(LinkedNode<'a>),
1126 IncludePath(LinkedNode<'a>),
1128 Label {
1130 node: LinkedNode<'a>,
1132 is_error: bool,
1134 },
1135 Ref {
1137 node: LinkedNode<'a>,
1139 suffix_colon: bool,
1142 },
1143 Normal(LinkedNode<'a>),
1145}
1146
1147impl<'a> SyntaxContext<'a> {
1148 pub fn node(&self) -> Option<LinkedNode<'a>> {
1150 Some(match self {
1151 SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1152 match target {
1153 ArgClass::Positional { .. } => return None,
1154 ArgClass::Named(node) => node.clone(),
1155 }
1156 }
1157 SyntaxContext::VarAccess(cls) => cls.node().clone(),
1158 SyntaxContext::Paren { container, .. } => container.clone(),
1159 SyntaxContext::Label { node, .. }
1160 | SyntaxContext::Ref { node, .. }
1161 | SyntaxContext::ImportPath(node)
1162 | SyntaxContext::IncludePath(node)
1163 | SyntaxContext::Normal(node) => node.clone(),
1164 })
1165 }
1166
1167 pub fn arg_container(&self) -> Option<&LinkedNode<'a>> {
1169 match self {
1170 Self::Arg { args, .. }
1171 | Self::Element {
1172 container: args, ..
1173 } => Some(args),
1174 Self::Paren { container, .. } => Some(container),
1175 _ => None,
1176 }
1177 }
1178}
1179
1180#[derive(Debug)]
1182enum ArgSourceKind {
1183 Call,
1185 Array,
1187 Dict,
1189}
1190
1191pub fn classify_context_outer<'a>(
1194 outer: LinkedNode<'a>,
1195 node: LinkedNode<'a>,
1196) -> Option<SyntaxContext<'a>> {
1197 use SyntaxClass::*;
1198 let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1199 let node_syntax = classify_syntax(node.clone(), node.offset())?;
1200
1201 match context_syntax {
1202 Callee(callee)
1203 if matches!(node_syntax, Normal(..) | Label { .. } | Ref { .. })
1204 && !matches!(node_syntax, Callee(..)) =>
1205 {
1206 let parent = callee.parent()?;
1207 let args = match parent.cast::<ast::Expr>() {
1208 Some(ast::Expr::FuncCall(call)) => call.args(),
1209 Some(ast::Expr::Set(set)) => set.args(),
1210 _ => return None,
1211 };
1212 let args = parent.find(args.span())?;
1213
1214 let is_set = parent.kind() == SyntaxKind::SetRule;
1215 let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1216 Some(SyntaxContext::Arg {
1217 callee,
1218 args,
1219 target: arg_target,
1220 is_set,
1221 })
1222 }
1223 _ => None,
1224 }
1225}
1226
1227pub fn classify_context(node: LinkedNode<'_>, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1230 let mut node = node;
1231 if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1232 loop {
1233 node = node.prev_sibling()?;
1234
1235 if !node.kind().is_trivia() {
1236 break;
1237 }
1238 }
1239 }
1240
1241 let cursor = cursor.unwrap_or_else(|| node.offset());
1242 let syntax = classify_syntax(node.clone(), cursor)?;
1243
1244 let normal_syntax = match syntax {
1245 SyntaxClass::Callee(callee) => {
1246 return callee_context(callee, node);
1247 }
1248 SyntaxClass::Label { node, is_error } => {
1249 return Some(SyntaxContext::Label { node, is_error });
1250 }
1251 SyntaxClass::Ref { node, suffix_colon } => {
1252 return Some(SyntaxContext::Ref { node, suffix_colon });
1253 }
1254 SyntaxClass::ImportPath(node) => {
1255 return Some(SyntaxContext::ImportPath(node));
1256 }
1257 SyntaxClass::IncludePath(node) => {
1258 return Some(SyntaxContext::IncludePath(node));
1259 }
1260 syntax => syntax,
1261 };
1262
1263 let Some(mut node_parent) = node.parent().cloned() else {
1264 return Some(SyntaxContext::Normal(node));
1265 };
1266
1267 while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1268 let Some(parent) = node_parent.parent() else {
1269 return Some(SyntaxContext::Normal(node));
1270 };
1271 node_parent = parent.clone();
1272 }
1273
1274 match node_parent.kind() {
1275 SyntaxKind::Args => {
1276 let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1277 let span = match ancestor.cast::<ast::Expr>()? {
1278 ast::Expr::FuncCall(call) => call.callee().span(),
1279 ast::Expr::Set(set) => set.target().span(),
1280 _ => return None,
1281 };
1282 ancestor.find(span)
1283 })?;
1284
1285 let param_node = match node.kind() {
1286 SyntaxKind::Ident
1287 if matches!(
1288 node.parent_kind().zip(node.next_sibling_kind()),
1289 Some((SyntaxKind::Named, SyntaxKind::Colon))
1290 ) =>
1291 {
1292 node
1293 }
1294 _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1295 node.parent().cloned()?
1296 }
1297 _ => node,
1298 };
1299
1300 callee_context(callee, param_node)
1301 }
1302 SyntaxKind::Array | SyntaxKind::Dict => {
1303 let element_target = arg_context(
1304 node_parent.clone(),
1305 node.clone(),
1306 match node_parent.kind() {
1307 SyntaxKind::Array => ArgSourceKind::Array,
1308 SyntaxKind::Dict => ArgSourceKind::Dict,
1309 _ => unreachable!(),
1310 },
1311 )?;
1312 Some(SyntaxContext::Element {
1313 container: node_parent.clone(),
1314 target: element_target,
1315 })
1316 }
1317 SyntaxKind::Parenthesized => {
1318 let is_before = node.offset() <= node_parent.offset() + 1;
1319 Some(SyntaxContext::Paren {
1320 container: node_parent.clone(),
1321 is_before,
1322 })
1323 }
1324 _ => Some(match normal_syntax {
1325 SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1326 normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1327 }),
1328 }
1329}
1330
1331fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
1333 let parent = callee.parent()?;
1334 let args = match parent.cast::<ast::Expr>() {
1335 Some(ast::Expr::FuncCall(call)) => call.args(),
1336 Some(ast::Expr::Set(set)) => set.args(),
1337 _ => return None,
1338 };
1339 let args = parent.find(args.span())?;
1340
1341 let mut parent = &node;
1342 loop {
1343 use SyntaxKind::*;
1344 match parent.kind() {
1345 ContentBlock | CodeBlock | Str | Raw | LineComment | BlockComment => {
1346 return Option::None;
1347 }
1348 Args if parent.range() == args.range() => {
1349 break;
1350 }
1351 _ => {}
1352 }
1353
1354 parent = parent.parent()?;
1355 }
1356
1357 let is_set = parent.kind() == SyntaxKind::SetRule;
1358 let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1359 Some(SyntaxContext::Arg {
1360 callee,
1361 args,
1362 target,
1363 is_set,
1364 })
1365}
1366
1367fn arg_context<'a>(
1369 args_node: LinkedNode<'a>,
1370 mut node: LinkedNode<'a>,
1371 param_kind: ArgSourceKind,
1372) -> Option<ArgClass<'a>> {
1373 if node.kind() == SyntaxKind::RightParen {
1374 node = node.prev_sibling()?;
1375 }
1376 match node.kind() {
1377 SyntaxKind::Named => {
1378 let param_ident = node.cast::<ast::Named>()?.name();
1379 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1380 }
1381 SyntaxKind::Colon => {
1382 let prev = node.prev_leaf()?;
1383 let param_ident = prev.cast::<ast::Ident>()?;
1384 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1385 }
1386 _ => {
1387 let parent = node.parent();
1388 if let Some(parent) = parent
1389 && parent.kind() == SyntaxKind::Named
1390 {
1391 let param_ident = parent.cast::<ast::Named>()?;
1392 let name = param_ident.name();
1393 let init = param_ident.expr();
1394 let init = parent.find(init.span())?;
1395 if init.range().contains(&node.offset()) {
1396 let name = args_node.find(name.span())?;
1397 return Some(ArgClass::Named(name));
1398 }
1399 }
1400
1401 let mut spreads = EcoVec::new();
1402 let mut positional = 0;
1403 let is_spread = node.kind() == SyntaxKind::Spread;
1404
1405 let args_before = args_node
1406 .children()
1407 .take_while(|arg| arg.range().end <= node.offset());
1408 match param_kind {
1409 ArgSourceKind::Call => {
1410 for ch in args_before {
1411 match ch.cast::<ast::Arg>() {
1412 Some(ast::Arg::Pos(..)) => {
1413 positional += 1;
1414 }
1415 Some(ast::Arg::Spread(..)) => {
1416 spreads.push(ch);
1417 }
1418 Some(ast::Arg::Named(..)) | None => {}
1419 }
1420 }
1421 }
1422 ArgSourceKind::Array => {
1423 for ch in args_before {
1424 match ch.cast::<ast::ArrayItem>() {
1425 Some(ast::ArrayItem::Pos(..)) => {
1426 positional += 1;
1427 }
1428 Some(ast::ArrayItem::Spread(..)) => {
1429 spreads.push(ch);
1430 }
1431 _ => {}
1432 }
1433 }
1434 }
1435 ArgSourceKind::Dict => {
1436 for ch in args_before {
1437 if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1438 spreads.push(ch);
1439 }
1440 }
1441 }
1442 }
1443
1444 Some(ArgClass::Positional {
1445 spreads,
1446 positional,
1447 is_spread,
1448 })
1449 }
1450 }
1451}
1452
1453pub enum BadCompletionCursor {
1455 ArgListPos,
1457}
1458
1459pub fn bad_completion_cursor(
1461 syntax: Option<&SyntaxClass>,
1462 syntax_context: Option<&SyntaxContext>,
1463 leaf: &LinkedNode,
1464) -> Option<BadCompletionCursor> {
1465 if (matches!(syntax, Some(SyntaxClass::Callee(..))) && {
1467 syntax_context
1468 .and_then(SyntaxContext::arg_container)
1469 .is_some_and(|container| {
1470 container.rightmost_leaf().map(|s| s.offset()) == Some(leaf.offset())
1471 })
1472 }) || (matches!(
1474 syntax,
1475 Some(SyntaxClass::Normal(SyntaxKind::ContentBlock, _))
1476 ) && matches!(leaf.kind(), SyntaxKind::RightBracket))
1477 {
1478 return Some(BadCompletionCursor::ArgListPos);
1479 }
1480
1481 None
1482}
1483
1484#[cfg(test)]
1485mod tests {
1486 use super::*;
1487 use insta::assert_snapshot;
1488 use typst::syntax::{Side, Source, is_newline};
1489
1490 fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1491 let source = Source::detached(source.to_owned());
1492 let root = LinkedNode::new(source.root());
1493 let mut output_mapping = String::new();
1494
1495 let mut cursor = 0;
1496 for ch in source.text().chars() {
1497 cursor += ch.len_utf8();
1498 if is_newline(ch) {
1499 output_mapping.push(ch);
1500 continue;
1501 }
1502
1503 output_mapping.push(mapper(&root, cursor));
1504 }
1505
1506 source
1507 .text()
1508 .lines()
1509 .zip(output_mapping.lines())
1510 .flat_map(|(a, b)| [a, "\n", b, "\n"])
1511 .collect::<String>()
1512 }
1513
1514 fn map_syntax(source: &str) -> String {
1515 map_node(source, |root, cursor| {
1516 let node = root.leaf_at(cursor, Side::Before);
1517 let kind = node.and_then(|node| classify_syntax(node, cursor));
1518 match kind {
1519 Some(SyntaxClass::VarAccess(..)) => 'v',
1520 Some(SyntaxClass::Normal(..)) => 'n',
1521 Some(SyntaxClass::Label { .. }) => 'l',
1522 Some(SyntaxClass::Ref { .. }) => 'r',
1523 Some(SyntaxClass::Callee(..)) => 'c',
1524 Some(SyntaxClass::ImportPath(..)) => 'i',
1525 Some(SyntaxClass::IncludePath(..)) => 'I',
1526 None => ' ',
1527 }
1528 })
1529 }
1530
1531 fn map_context(source: &str) -> String {
1532 map_node(source, |root, cursor| {
1533 let node = root.leaf_at(cursor, Side::Before);
1534 let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1535 match kind {
1536 Some(SyntaxContext::Arg { .. }) => 'p',
1537 Some(SyntaxContext::Element { .. }) => 'e',
1538 Some(SyntaxContext::Paren { .. }) => 'P',
1539 Some(SyntaxContext::VarAccess { .. }) => 'v',
1540 Some(SyntaxContext::ImportPath(..)) => 'i',
1541 Some(SyntaxContext::IncludePath(..)) => 'I',
1542 Some(SyntaxContext::Label { .. }) => 'l',
1543 Some(SyntaxContext::Ref { .. }) => 'r',
1544 Some(SyntaxContext::Normal(..)) => 'n',
1545 None => ' ',
1546 }
1547 })
1548 }
1549
1550 #[test]
1551 fn test_get_syntax() {
1552 assert_snapshot!(map_syntax(r#"#let x = 1
1553Text
1554= Heading #let y = 2;
1555== Heading"#).trim(), @r"
1556 #let x = 1
1557 nnnnvvnnn
1558 Text
1559
1560 = Heading #let y = 2;
1561 nnnnvvnnn
1562 == Heading
1563 ");
1564 assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @r"
1565 #let f(x);
1566 nnnnv v
1567 ");
1568 assert_snapshot!(map_syntax(r#"#{
1569 calc.
1570}"#).trim(), @r"
1571 #{
1572 n
1573 calc.
1574 nnvvvvvnn
1575 }
1576 n
1577 ");
1578 }
1579
1580 #[test]
1581 fn test_get_context() {
1582 assert_snapshot!(map_context(r#"#let x = 1
1583Text
1584= Heading #let y = 2;
1585== Heading"#).trim(), @r"
1586 #let x = 1
1587 nnnnvvnnn
1588 Text
1589
1590 = Heading #let y = 2;
1591 nnnnvvnnn
1592 == Heading
1593 ");
1594 assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @r"
1595 #let f(x);
1596 nnnnv v
1597 ");
1598 assert_snapshot!(map_context(r#"#f(1, 2) Test"#).trim(), @r"
1599 #f(1, 2) Test
1600 vpppppp
1601 ");
1602 assert_snapshot!(map_context(r#"#() Test"#).trim(), @r"
1603 #() Test
1604 ee
1605 ");
1606 assert_snapshot!(map_context(r#"#(1) Test"#).trim(), @r"
1607 #(1) Test
1608 PPP
1609 ");
1610 assert_snapshot!(map_context(r#"#(a: 1) Test"#).trim(), @r"
1611 #(a: 1) Test
1612 eeeeee
1613 ");
1614 assert_snapshot!(map_context(r#"#(1, 2) Test"#).trim(), @r"
1615 #(1, 2) Test
1616 eeeeee
1617 ");
1618 assert_snapshot!(map_context(r#"#(1, 2)
1619 Test"#).trim(), @r"
1620 #(1, 2)
1621 eeeeee
1622 Test
1623 ");
1624 }
1625
1626 #[test]
1627 fn ref_syntxax() {
1628 assert_snapshot!(map_syntax("@ab:"), @r###"
1629 @ab:
1630 rrrr
1631 "###);
1632 }
1633
1634 #[test]
1635 fn ref_syntax() {
1636 assert_snapshot!(map_syntax("@ab"), @r###"
1637 @ab
1638 rrr
1639 "###);
1640 assert_snapshot!(map_syntax("@ab:"), @r###"
1641 @ab:
1642 rrrr
1643 "###);
1644 assert_snapshot!(map_syntax("@ab:ab"), @r###"
1645 @ab:ab
1646 rrrrrr
1647 "###);
1648 assert_snapshot!(map_syntax("@ab:ab:"), @r###"
1649 @ab:ab:
1650 rrrrrrr
1651 "###);
1652 assert_snapshot!(map_syntax("@ab:ab:ab"), @r###"
1653 @ab:ab:ab
1654 rrrrrrrrr
1655 "###);
1656 assert_snapshot!(map_syntax("@ab[]:"), @r###"
1657 @ab[]:
1658 rrrnn
1659 "###);
1660 assert_snapshot!(map_syntax("@ab[ab]:"), @r###"
1661 @ab[ab]:
1662 rrrn n
1663 "###);
1664 assert_snapshot!(map_syntax("@ab :ab: ab"), @r###"
1665 @ab :ab: ab
1666 rrr
1667 "###);
1668 assert_snapshot!(map_syntax("@ab :ab:ab"), @r###"
1669 @ab :ab:ab
1670 rrr
1671 "###);
1672 }
1673
1674 fn access_node(s: &str, cursor: i32) -> String {
1675 access_node_(s, cursor).unwrap_or_default()
1676 }
1677
1678 fn access_node_(s: &str, cursor: i32) -> Option<String> {
1679 access_var(s, cursor, |_source, var| {
1680 Some(var.accessed_node()?.get().clone().into_text().into())
1681 })
1682 }
1683
1684 fn access_field(s: &str, cursor: i32) -> String {
1685 access_field_(s, cursor).unwrap_or_default()
1686 }
1687
1688 fn access_field_(s: &str, cursor: i32) -> Option<String> {
1689 access_var(s, cursor, |source, var| {
1690 let field = var.accessing_field()?;
1691 Some(match field {
1692 FieldClass::Field(ident) => format!("Field: {}", ident.text()),
1693 FieldClass::DotSuffix(span_offset) => {
1694 let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1695 format!("DotSuffix: {offset:?}")
1696 }
1697 })
1698 })
1699 }
1700
1701 fn access_var(
1702 s: &str,
1703 cursor: i32,
1704 f: impl FnOnce(&Source, VarClass) -> Option<String>,
1705 ) -> Option<String> {
1706 let cursor = if cursor < 0 {
1707 s.len() as i32 + cursor
1708 } else {
1709 cursor
1710 };
1711 let source = Source::detached(s.to_owned());
1712 let root = LinkedNode::new(source.root());
1713 let node = root.leaf_at(cursor as usize, Side::Before)?;
1714 let syntax = classify_syntax(node, cursor as usize)?;
1715 let SyntaxClass::VarAccess(var) = syntax else {
1716 return None;
1717 };
1718 f(&source, var)
1719 }
1720
1721 #[test]
1722 fn test_access_field() {
1723 assert_snapshot!(access_field("#(a.b)", 5), @r"Field: b");
1724 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1725 assert_snapshot!(access_field("$a.$", 3), @"DotSuffix: 3");
1726 assert_snapshot!(access_field("#(a.)", 4), @"DotSuffix: 4");
1727 assert_snapshot!(access_node("#(a..b)", 4), @"a");
1728 assert_snapshot!(access_field("#(a..b)", 4), @"DotSuffix: 4");
1729 assert_snapshot!(access_node("#(a..b())", 4), @"a");
1730 assert_snapshot!(access_field("#(a..b())", 4), @"DotSuffix: 4");
1731 }
1732
1733 #[test]
1734 fn test_code_access() {
1735 assert_snapshot!(access_node("#{`a`.}", 6), @"`a`");
1736 assert_snapshot!(access_field("#{`a`.}", 6), @"DotSuffix: 6");
1737 assert_snapshot!(access_node("#{$a$.}", 6), @"$a$");
1738 assert_snapshot!(access_field("#{$a$.}", 6), @"DotSuffix: 6");
1739 assert_snapshot!(access_node("#{\"a\".}", 6), @"\"a\"");
1740 assert_snapshot!(access_field("#{\"a\".}", 6), @"DotSuffix: 6");
1741 assert_snapshot!(access_node("#{<a>.}", 6), @"<a>");
1742 assert_snapshot!(access_field("#{<a>.}", 6), @"DotSuffix: 6");
1743 }
1744
1745 #[test]
1746 fn test_markup_access() {
1747 assert_snapshot!(access_field("_a_.", 4), @"");
1748 assert_snapshot!(access_field("*a*.", 4), @"");
1749 assert_snapshot!(access_field("`a`.", 4), @"");
1750 assert_snapshot!(access_field("$a$.", 4), @"");
1751 assert_snapshot!(access_field("\"a\".", 4), @"");
1752 assert_snapshot!(access_field("@a.", 3), @"");
1753 assert_snapshot!(access_field("<a>.", 4), @"");
1754 }
1755
1756 #[test]
1757 fn test_markup_chain_access() {
1758 assert_snapshot!(access_node("#a.b.", 5), @"a.b");
1759 assert_snapshot!(access_field("#a.b.", 5), @"DotSuffix: 5");
1760 assert_snapshot!(access_node("#a.b.c.", 7), @"a.b.c");
1761 assert_snapshot!(access_field("#a.b.c.", 7), @"DotSuffix: 7");
1762 assert_snapshot!(access_node("#context a.", 11), @"a");
1763 assert_snapshot!(access_field("#context a.", 11), @"DotSuffix: 11");
1764 assert_snapshot!(access_node("#context a.b.", 13), @"a.b");
1765 assert_snapshot!(access_field("#context a.b.", 13), @"DotSuffix: 13");
1766
1767 assert_snapshot!(access_node("#a.at(1).", 9), @"a.at(1)");
1768 assert_snapshot!(access_field("#a.at(1).", 9), @"DotSuffix: 9");
1769 assert_snapshot!(access_node("#context a.at(1).", 17), @"a.at(1)");
1770 assert_snapshot!(access_field("#context a.at(1).", 17), @"DotSuffix: 17");
1771
1772 assert_snapshot!(access_node("#a.at(1).c.", 11), @"a.at(1).c");
1773 assert_snapshot!(access_field("#a.at(1).c.", 11), @"DotSuffix: 11");
1774 assert_snapshot!(access_node("#context a.at(1).c.", 19), @"a.at(1).c");
1775 assert_snapshot!(access_field("#context a.at(1).c.", 19), @"DotSuffix: 19");
1776 }
1777
1778 #[test]
1779 fn test_hash_access() {
1780 assert_snapshot!(access_node("#a.", 3), @"a");
1781 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1782 assert_snapshot!(access_node("#(a).", 5), @"(a)");
1783 assert_snapshot!(access_field("#(a).", 5), @"DotSuffix: 5");
1784 assert_snapshot!(access_node("#`a`.", 5), @"`a`");
1785 assert_snapshot!(access_field("#`a`.", 5), @"DotSuffix: 5");
1786 assert_snapshot!(access_node("#$a$.", 5), @"$a$");
1787 assert_snapshot!(access_field("#$a$.", 5), @"DotSuffix: 5");
1788 assert_snapshot!(access_node("#(a,).", 6), @"(a,)");
1789 assert_snapshot!(access_field("#(a,).", 6), @"DotSuffix: 6");
1790 }
1791}