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 field_span = parent
83 .cast::<ast::FieldAccess>()
84 .map(|field_access| field_access.field().span())
85 .or_else(|| {
86 parent
87 .cast::<ast::MathFieldAccess>()
88 .map(|field_access| field_access.field().span())
89 });
90 let Some(field_span) = field_span else {
91 return node;
92 };
93
94 let dot = parent
95 .children()
96 .find(|n| matches!(n.kind(), SyntaxKind::Dot));
97
98 if dot.is_some_and(|dot| dot.offset() <= node.offset() && field_span == node.span())
102 {
103 node = parent;
104 } else {
105 return node;
106 }
107 }
108
109 node
110 })
111 .cloned()
112}
113
114pub enum PreviousItem<'a> {
117 Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
119 Sibling(&'a LinkedNode<'a>),
121}
122
123impl<'a> PreviousItem<'a> {
124 pub fn node(&self) -> &'a LinkedNode<'a> {
126 match self {
127 PreviousItem::Sibling(node) => node,
128 PreviousItem::Parent(node, _) => node,
129 }
130 }
131}
132
133pub fn previous_items<T>(
136 node: LinkedNode,
137 mut recv: impl FnMut(PreviousItem) -> Option<T>,
138) -> Option<T> {
139 let mut ancestor = Some(node);
140 while let Some(node) = &ancestor {
141 let mut sibling = Some(node.clone());
142 while let Some(node) = &sibling {
143 if let Some(v) = recv(PreviousItem::Sibling(node)) {
144 return Some(v);
145 }
146
147 sibling = node.prev_sibling();
148 }
149
150 if let Some(parent) = node.parent() {
151 if let Some(v) = recv(PreviousItem::Parent(parent, node)) {
152 return Some(v);
153 }
154
155 ancestor = Some(parent.clone());
156 continue;
157 }
158
159 break;
160 }
161
162 None
163}
164
165pub enum PreviousDecl<'a> {
168 Ident(ast::Ident<'a>),
178 ImportSource(ast::Expr<'a>),
188 ImportAll(ast::ModuleImport<'a>),
198}
199
200pub fn previous_decls<T>(
203 node: LinkedNode,
204 mut recv: impl FnMut(PreviousDecl) -> Option<T>,
205) -> Option<T> {
206 previous_items(node, |item| {
207 match (&item, item.node().cast::<ast::Expr>()?) {
208 (PreviousItem::Sibling(..), ast::Expr::LetBinding(lb)) => {
209 for ident in lb.kind().bindings() {
210 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
211 return Some(t);
212 }
213 }
214 }
215 (PreviousItem::Sibling(..), ast::Expr::ModuleImport(import)) => {
216 match import.imports() {
218 Some(ast::Imports::Wildcard) => {
219 if let Some(t) = recv(PreviousDecl::ImportAll(import)) {
220 return Some(t);
221 }
222 }
223 Some(ast::Imports::Items(items)) => {
224 for item in items.iter() {
225 if let Some(t) = recv(PreviousDecl::Ident(item.bound_name())) {
226 return Some(t);
227 }
228 }
229 }
230 _ => {}
231 }
232
233 if let Some(new_name) = import.new_name() {
235 if let Some(t) = recv(PreviousDecl::Ident(new_name)) {
236 return Some(t);
237 }
238 } else if import.imports().is_none()
239 && let Some(t) = recv(PreviousDecl::ImportSource(import.source()))
240 {
241 return Some(t);
242 }
243 }
244 (PreviousItem::Parent(parent, child), ast::Expr::ForLoop(for_expr)) => {
245 let body = parent.find(for_expr.body().span());
246 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
247 if !in_body {
248 return None;
249 }
250
251 for ident in for_expr.pattern().bindings() {
252 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
253 return Some(t);
254 }
255 }
256 }
257 (PreviousItem::Parent(parent, child), ast::Expr::Closure(closure)) => {
258 let body = parent.find(closure.body().span());
259 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
260 if !in_body {
261 return None;
262 }
263
264 for param in closure.params().children() {
265 match param {
266 ast::Param::Pos(pos) => {
267 for ident in pos.bindings() {
268 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
269 return Some(t);
270 }
271 }
272 }
273 ast::Param::Named(named) => {
274 if let Some(t) = recv(PreviousDecl::Ident(named.name())) {
275 return Some(t);
276 }
277 }
278 ast::Param::Spread(spread) => {
279 if let Some(sink_ident) = spread.sink_ident()
280 && let Some(t) = recv(PreviousDecl::Ident(sink_ident))
281 {
282 return Some(t);
283 }
284 }
285 }
286 }
287 }
288 _ => {}
289 };
290 None
291 })
292}
293
294pub fn is_mark(sk: SyntaxKind) -> bool {
296 use SyntaxKind::*;
297 #[allow(clippy::match_like_matches_macro)]
298 match sk {
299 MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
300 Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
301 LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
302 Slash | Hat | Comma | Semicolon | Colon | Hash => true,
303 _ => false,
304 }
305}
306
307pub fn is_ident_like(node: &SyntaxNode) -> bool {
309 fn can_be_ident(node: &SyntaxNode) -> bool {
310 typst::syntax::is_ident(node.leaf_text())
311 }
312
313 use SyntaxKind::*;
314 let kind = node.kind();
315 matches!(kind, Ident | MathIdent | Underscore)
316 || (matches!(kind, Error) && can_be_ident(node))
317 || kind.is_keyword()
318}
319
320#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
322#[serde(rename_all = "camelCase")]
323pub enum InterpretMode {
324 Comment,
326 String,
328 Raw,
330 Markup,
332 Code,
334 Math,
336}
337
338pub fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode {
341 loop {
342 crate::log_debug_ct!("leaf for mode: {leaf:?}");
343 if let Some(t) = leaf {
344 if let Some(mode) = interpret_mode_at_kind(t.kind()) {
345 break mode;
346 }
347
348 if !t.kind().is_trivia() && {
349 t.prev_leaf().is_some_and(|n| n.kind() == SyntaxKind::Hash)
351 } {
352 return InterpretMode::Code;
353 }
354
355 leaf = t.parent();
356 } else {
357 break InterpretMode::Markup;
358 }
359 }
360}
361
362pub(crate) fn interpret_mode_at_kind(kind: SyntaxKind) -> Option<InterpretMode> {
364 use SyntaxKind::*;
365 Some(match kind {
366 LineComment | BlockComment | Shebang => InterpretMode::Comment,
367 Raw => InterpretMode::Raw,
368 Str => InterpretMode::String,
369 CodeBlock | Code => InterpretMode::Code,
370 ContentBlock | Markup => InterpretMode::Markup,
371 Equation | Math => InterpretMode::Math,
372 Hash => InterpretMode::Code,
373 Label | Text | Ident | Args | FuncCall | FieldAccess | Bool | Int | Float | Numeric
374 | Space | Linebreak | Parbreak | Escape | Shorthand | SmartQuote | RawLang | RawDelim
375 | RawTrimmed | LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen
376 | RightParen | Comma | Semicolon | Colon | Star | Underscore | Dollar | Plus | Minus
377 | Slash | Hat | Dot | Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq
378 | StarEq | SlashEq | Dots | Arrow | Root | Bang | Not | And | Or | None | Auto | As
379 | Named | Keyed | Spread | Error | End => return Option::None,
380 Strong | Emph | Link | Ref | RefMarker | Heading | HeadingMarker | ListItem
381 | ListMarker | EnumItem | EnumMarker | TermItem | TermMarker => InterpretMode::Markup,
382 MathIdent | MathFieldAccess | MathAlignPoint | MathCall | MathArgs | MathDelimited
383 | MathAttach | MathPrimes | MathFrac | MathRoot | MathShorthand | MathText => {
384 InterpretMode::Math
385 }
386 Let | Set | Show | Context | If | Else | For | In | While | Break | Continue | Return
387 | Import | Include | Closure | Params | LetBinding | SetRule | ShowRule | Contextual
388 | Conditional | WhileLoop | ForLoop | LoopBreak | ModuleImport | ImportItems
389 | ImportItemPath | RenamedImportItem | ModuleInclude | LoopContinue | FuncReturn
390 | Unary | Binary | Parenthesized | Dict | Array | Destructuring | DestructAssignment => {
391 InterpretMode::Code
392 }
393 })
394}
395
396#[derive(Debug, Clone)]
398pub enum DefClass<'a> {
399 Let(LinkedNode<'a>),
401 Import(LinkedNode<'a>),
403}
404
405impl DefClass<'_> {
406 pub fn node(&self) -> &LinkedNode<'_> {
408 match self {
409 DefClass::Let(node) => node,
410 DefClass::Import(node) => node,
411 }
412 }
413
414 pub fn name(&self) -> Option<LinkedNode<'_>> {
416 match self {
417 DefClass::Let(node) => {
418 let lb: ast::LetBinding<'_> = node.cast()?;
419 let names = match lb.kind() {
420 ast::LetBindingKind::Closure(name) => node.find(name.span())?,
421 ast::LetBindingKind::Normal(ast::Pattern::Normal(name)) => {
422 node.find(name.span())?
423 }
424 _ => return None,
425 };
426
427 Some(names)
428 }
429 DefClass::Import(_node) => {
430 None
434 }
435 }
436 }
437
438 pub fn name_range(&self) -> Option<Range<usize>> {
440 self.name().map(|node| node.range())
441 }
442}
443
444pub fn classify_def_loosely(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
447 classify_def_(node, false)
448}
449
450pub fn classify_def(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
452 classify_def_(node, true)
453}
454
455fn classify_def_(node: LinkedNode<'_>, strict: bool) -> Option<DefClass<'_>> {
457 let mut ancestor = node;
458 if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
459 ancestor = ancestor.prev_sibling()?;
460 }
461
462 while !ancestor.is::<ast::Expr>() {
463 ancestor = ancestor.parent()?.clone();
464 }
465 crate::log_debug_ct!("ancestor: {ancestor:?}");
466 let adjusted = adjust_expr(ancestor)?;
467 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
468
469 let may_ident = adjusted.cast::<ast::Expr>()?;
470 if strict && !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
471 return None;
472 }
473
474 let expr = may_ident;
475 Some(match expr {
476 ast::Expr::FuncCall(..) => return None,
479 ast::Expr::SetRule(..) => return None,
480 ast::Expr::LetBinding(..) => DefClass::Let(adjusted),
481 ast::Expr::ModuleImport(..) => DefClass::Import(adjusted),
482 ast::Expr::Ident(..)
484 | ast::Expr::MathIdent(..)
485 | ast::Expr::FieldAccess(..)
486 | ast::Expr::Closure(..) => {
487 let mut ancestor = adjusted;
488 while !ancestor.is::<ast::LetBinding>() {
489 ancestor = ancestor.parent()?.clone();
490 }
491
492 DefClass::Let(ancestor)
493 }
494 ast::Expr::Str(..) => {
495 let parent = adjusted.parent()?;
496 if parent.kind() != SyntaxKind::ModuleImport {
497 return None;
498 }
499
500 DefClass::Import(parent.clone())
501 }
502 _ if expr.hash() => return None,
503 _ => {
504 crate::log_debug_ct!("unsupported kind {:?}", adjusted.kind());
505 return None;
506 }
507 })
508}
509
510pub fn adjust_expr(mut node: LinkedNode) -> Option<LinkedNode> {
515 while let Some(paren_expr) = node.cast::<ast::Parenthesized>() {
516 node = node.find(paren_expr.expr().span())?;
517 }
518 if let Some(parent) = node.parent()
519 && {
520 parent
521 .cast::<ast::FieldAccess>()
522 .map(|field_access| node.span() == field_access.field().span())
523 .unwrap_or(false)
524 || parent
525 .cast::<ast::MathFieldAccess>()
526 .map(|field_access| node.span() == field_access.field().span())
527 .unwrap_or(false)
528 }
529 {
530 return Some(parent.clone());
531 }
532 Some(node)
533}
534
535#[derive(Debug, Clone)]
537pub enum FieldClass<'a> {
538 Field(LinkedNode<'a>),
548
549 DotSuffix(SourceSpanOffset),
559}
560
561impl FieldClass<'_> {
562 pub fn offset(&self, source: &Source) -> Option<usize> {
564 Some(match self {
565 Self::Field(node) => node.offset(),
566 Self::DotSuffix(span_offset) => {
567 source.find(span_offset.span)?.offset() + span_offset.offset
568 }
569 })
570 }
571}
572
573#[derive(Debug, Clone)]
576pub enum VarClass<'a> {
577 Ident(LinkedNode<'a>),
579 FieldAccess(LinkedNode<'a>),
581 DotAccess(LinkedNode<'a>),
585}
586
587impl<'a> VarClass<'a> {
588 pub fn node(&self) -> &LinkedNode<'a> {
590 match self {
591 Self::Ident(node) | Self::FieldAccess(node) | Self::DotAccess(node) => node,
592 }
593 }
594
595 pub fn accessed_node(&self) -> Option<LinkedNode<'a>> {
597 Some(match self {
598 Self::Ident(node) => node.clone(),
599 Self::FieldAccess(node) => {
600 if let Some(field_access) = node.cast::<ast::FieldAccess>() {
601 node.find(field_access.target().span())?
602 } else {
603 let field_access = node.cast::<ast::MathFieldAccess>()?;
604 node.find(field_access.target().to_untyped().span())?
605 }
606 }
607 Self::DotAccess(node) => node.clone(),
608 })
609 }
610
611 pub fn accessing_field(&self) -> Option<FieldClass<'a>> {
613 match self {
614 Self::FieldAccess(node) => {
615 let dot = node
616 .children()
617 .find(|n| matches!(n.kind(), SyntaxKind::Dot))?;
618 let mut iter_after_dot =
619 node.children().skip_while(|n| n.kind() != SyntaxKind::Dot);
620 let ident = iter_after_dot.find(|n| {
621 matches!(
622 n.kind(),
623 SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::Error
624 )
625 });
626
627 let ident_case = ident.map(|ident| {
628 if ident.leaf_text().is_empty() {
629 FieldClass::DotSuffix(SourceSpanOffset {
630 span: ident.span(),
631 offset: 0,
632 })
633 } else {
634 FieldClass::Field(ident)
635 }
636 });
637
638 ident_case.or_else(|| {
639 Some(FieldClass::DotSuffix(SourceSpanOffset {
640 span: dot.span(),
641 offset: 1,
642 }))
643 })
644 }
645 Self::DotAccess(node) => Some(FieldClass::DotSuffix(SourceSpanOffset {
646 span: node.span(),
647 offset: node.range().len() + 1,
648 })),
649 Self::Ident(..) => None,
650 }
651 }
652}
653
654#[derive(Debug, Clone)]
656pub enum SyntaxClass<'a> {
657 VarAccess(VarClass<'a>),
661 Label {
663 node: LinkedNode<'a>,
665 is_error: bool,
667 },
668 Ref {
670 node: LinkedNode<'a>,
672 suffix_colon: bool,
675 },
676 At {
678 node: LinkedNode<'a>,
680 },
681 Callee(LinkedNode<'a>),
683 ImportPath(LinkedNode<'a>),
685 IncludePath(LinkedNode<'a>),
687 Normal(SyntaxKind, LinkedNode<'a>),
689}
690
691impl<'a> SyntaxClass<'a> {
692 pub fn label(node: LinkedNode<'a>) -> Self {
694 Self::Label {
695 node,
696 is_error: false,
697 }
698 }
699
700 pub fn error_as_label(node: LinkedNode<'a>) -> Self {
702 Self::Label {
703 node,
704 is_error: true,
705 }
706 }
707
708 pub fn node(&self) -> &LinkedNode<'a> {
710 match self {
711 SyntaxClass::VarAccess(cls) => cls.node(),
712 SyntaxClass::Label { node, .. }
713 | SyntaxClass::Ref { node, .. }
714 | SyntaxClass::At { node, .. }
715 | SyntaxClass::Callee(node)
716 | SyntaxClass::ImportPath(node)
717 | SyntaxClass::IncludePath(node)
718 | SyntaxClass::Normal(_, node) => node,
719 }
720 }
721
722 pub fn complete_offset(&self) -> Option<usize> {
724 match self {
725 SyntaxClass::Label { node, .. } => Some(node.offset() + 1),
728 _ => None,
729 }
730 }
731
732 pub fn erroneous(&self) -> bool {
734 use SyntaxClass::*;
735 match self {
736 Label { .. } => false,
737 VarAccess(cls) => cls.node().diagnosis().errors,
738 Normal(_, node)
739 | Callee(node)
740 | At { node }
741 | Ref { node, .. }
742 | ImportPath(node)
743 | IncludePath(node) => node.diagnosis().errors,
744 }
745 }
746}
747
748pub fn classify_syntax(node: LinkedNode<'_>, cursor: usize) -> Option<SyntaxClass<'_>> {
751 if matches!(node.kind(), SyntaxKind::Error) && node.leaf_text().starts_with('<') {
752 return Some(SyntaxClass::error_as_label(node));
753 }
754
755 fn can_skip_trivia(node: &LinkedNode, cursor: usize) -> bool {
757 if !node.kind().is_trivia() || !node.parent_kind().is_some_and(possible_in_code_trivia) {
759 return false;
760 }
761
762 let previous_text = node.leaf_text().as_bytes();
764 let previous_text = if node.range().contains(&cursor) {
765 &previous_text[..cursor - node.offset()]
766 } else {
767 previous_text
768 };
769
770 !previous_text.contains(&b'\n')
775 }
776
777 let mut node = node;
779 if can_skip_trivia(&node, cursor) {
780 node = node.prev_sibling()?;
781 }
782
783 fn classify_dot_access<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
789 let prev_leaf = node.prev_leaf();
790 let mode = interpret_mode_at(Some(node));
791
792 if matches!(mode, InterpretMode::Markup | InterpretMode::Math)
794 && prev_leaf
795 .as_ref()
796 .is_some_and(|leaf| leaf.range().end < node.offset())
797 {
798 return None;
799 }
800
801 if matches!(mode, InterpretMode::Math)
802 && prev_leaf.as_ref().is_some_and(|leaf| {
803 node_ancestors(leaf)
805 .find(|t| matches!(t.kind(), SyntaxKind::Equation))
806 .is_some_and(|parent| parent.offset() == leaf.offset())
807 })
808 {
809 return None;
810 }
811
812 let dot_target = prev_leaf.and_then(first_ancestor_expr)?;
813
814 if matches!(mode, InterpretMode::Math | InterpretMode::Code) || {
815 matches!(mode, InterpretMode::Markup)
816 && (matches!(
817 dot_target.kind(),
818 SyntaxKind::Ident
819 | SyntaxKind::MathIdent
820 | SyntaxKind::FieldAccess
821 | SyntaxKind::MathFieldAccess
822 | SyntaxKind::FuncCall
823 | SyntaxKind::MathCall
824 ) || (matches!(
825 dot_target.prev_leaf().as_deref().map(SyntaxNode::kind),
826 Some(SyntaxKind::Hash)
827 )))
828 } {
829 return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
830 }
831
832 None
833 }
834
835 if node.offset() + 1 == cursor
836 && {
837 matches!(node.kind(), SyntaxKind::Dot)
839 || (matches!(
840 node.kind(),
841 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
842 ) && node.leaf_text().starts_with("."))
843 }
844 && let Some(dot_access) = classify_dot_access(&node)
845 {
846 return Some(dot_access);
847 }
848
849 if node.offset() + 1 == cursor
850 && matches!(node.kind(), SyntaxKind::Dots)
851 && matches!(node.parent_kind(), Some(SyntaxKind::Spread))
852 && let Some(dot_access) = classify_dot_access(&node)
853 {
854 return Some(dot_access);
855 }
856
857 fn classify_ref<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
862 let prev_leaf = node.prev_leaf()?;
863
864 if matches!(prev_leaf.kind(), SyntaxKind::RefMarker)
865 && prev_leaf.range().end == node.offset()
866 {
867 return Some(SyntaxClass::Ref {
868 node: prev_leaf,
869 suffix_colon: true,
870 });
871 }
872
873 None
874 }
875
876 if node.offset() + 1 == cursor
877 && {
878 matches!(node.kind(), SyntaxKind::Colon)
880 || (matches!(
881 node.kind(),
882 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
883 ) && node.leaf_text().starts_with(":"))
884 }
885 && let Some(ref_syntax) = classify_ref(&node)
886 {
887 return Some(ref_syntax);
888 }
889
890 if node.kind() == SyntaxKind::Text
891 && node.offset() + 1 == cursor
892 && node.leaf_text().starts_with('@')
893 && matches!(interpret_mode_at(Some(&node)), InterpretMode::Markup)
894 {
895 return Some(SyntaxClass::At { node });
896 }
897
898 if matches!(node.kind(), SyntaxKind::Text | SyntaxKind::MathText) {
900 let mode = interpret_mode_at(Some(&node));
901 if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
902 return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
903 }
904 }
905
906 let ancestor = first_ancestor_expr(node)?;
908 crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
909
910 let adjusted = adjust_expr(ancestor)?;
912 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
913
914 let expr = adjusted.cast::<ast::Expr>()?;
916 Some(match expr {
917 ast::Expr::Label(..) => SyntaxClass::label(adjusted),
918 ast::Expr::Ref(..) => SyntaxClass::Ref {
919 node: adjusted,
920 suffix_colon: false,
921 },
922 ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
923 ast::Expr::MathCall(call) => {
924 SyntaxClass::Callee(adjusted.find(call.callee().to_untyped().span())?)
925 }
926 ast::Expr::SetRule(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
927 ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
928 SyntaxClass::VarAccess(VarClass::Ident(adjusted))
929 }
930 ast::Expr::FieldAccess(..) | ast::Expr::MathFieldAccess(..) => {
931 SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted))
932 }
933 ast::Expr::Str(..) => {
934 let parent = adjusted.parent()?;
935 if parent.kind() == SyntaxKind::ModuleImport {
936 SyntaxClass::ImportPath(adjusted)
937 } else if parent.kind() == SyntaxKind::ModuleInclude {
938 SyntaxClass::IncludePath(adjusted)
939 } else {
940 SyntaxClass::Normal(adjusted.kind(), adjusted)
941 }
942 }
943 _ if expr.hash()
944 || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
945 {
946 SyntaxClass::Normal(adjusted.kind(), adjusted)
947 }
948 _ => return None,
949 })
950}
951
952fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
955 if matches!(kind, SyntaxKind::MathArgs) {
957 return true;
958 }
959
960 !matches!(
961 interpret_mode_at_kind(kind),
962 Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
963 )
964}
965
966#[derive(Debug, Clone)]
968pub enum ArgClass<'a> {
969 Positional {
971 spreads: EcoVec<LinkedNode<'a>>,
973 positional: usize,
975 is_spread: bool,
977 },
978 Named(LinkedNode<'a>),
980}
981
982impl ArgClass<'_> {
983 pub fn first_positional() -> Self {
985 ArgClass::Positional {
986 spreads: EcoVec::new(),
987 positional: 0,
988 is_spread: false,
989 }
990 }
991}
992
993#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
996pub enum SurroundingSyntax {
997 Regular,
999 StringContent,
1001 Selector,
1003 ShowTransform,
1005 ImportList,
1007 SetRule,
1009 ParamList,
1011}
1012
1013pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
1015 check_previous_syntax(node)
1016 .or_else(|| check_surrounding_syntax(node))
1017 .unwrap_or(SurroundingSyntax::Regular)
1018}
1019
1020fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
1021 use SurroundingSyntax::*;
1022 let mut met_args = false;
1023
1024 if matches!(leaf.kind(), SyntaxKind::Str) {
1025 return Some(StringContent);
1026 }
1027
1028 while let Some(parent) = leaf.parent() {
1029 crate::log_debug_ct!(
1030 "check_surrounding_syntax: {:?}::{:?}",
1031 parent.kind(),
1032 leaf.kind()
1033 );
1034 match parent.kind() {
1035 SyntaxKind::CodeBlock
1036 | SyntaxKind::ContentBlock
1037 | SyntaxKind::Equation
1038 | SyntaxKind::Closure => {
1039 return Some(Regular);
1040 }
1041 SyntaxKind::ImportItemPath
1042 | SyntaxKind::ImportItems
1043 | SyntaxKind::RenamedImportItem => {
1044 return Some(ImportList);
1045 }
1046 SyntaxKind::ModuleImport => {
1047 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
1048 let Some(colon) = colon else {
1049 return Some(Regular);
1050 };
1051
1052 if leaf.offset() >= colon.offset() {
1053 return Some(ImportList);
1054 } else {
1055 return Some(Regular);
1056 }
1057 }
1058 SyntaxKind::Named => {
1059 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
1060 let Some(colon) = colon else {
1061 return Some(Regular);
1062 };
1063
1064 return if leaf.offset() >= colon.offset() {
1065 Some(Regular)
1066 } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
1067 Some(ParamList)
1068 } else {
1069 Some(Regular)
1070 };
1071 }
1072 SyntaxKind::Params => {
1073 return Some(ParamList);
1074 }
1075 SyntaxKind::Args => {
1076 met_args = true;
1077 }
1078 SyntaxKind::SetRule => {
1079 let rule = parent.get().cast::<ast::SetRule>()?;
1080 if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
1081 return Some(Regular);
1082 } else {
1083 return Some(SetRule);
1084 }
1085 }
1086 SyntaxKind::ShowRule => {
1087 if met_args {
1088 return Some(Regular);
1089 }
1090
1091 let rule = parent.get().cast::<ast::ShowRule>()?;
1092 let colon = rule
1093 .to_untyped()
1094 .children()
1095 .find(|s| s.kind() == SyntaxKind::Colon);
1096 let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
1097 return Some(Selector);
1099 };
1100
1101 if leaf.offset() >= colon.offset() {
1102 return Some(ShowTransform);
1103 } else {
1104 return Some(Selector); }
1106 }
1107 _ => {}
1108 }
1109
1110 leaf = parent;
1111 }
1112
1113 None
1114}
1115
1116fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
1118 let mut leaf = leaf.clone();
1119 if leaf.kind().is_trivia() {
1120 leaf = leaf.prev_sibling()?;
1121 }
1122 if matches!(
1123 leaf.kind(),
1124 SyntaxKind::ShowRule
1125 | SyntaxKind::SetRule
1126 | SyntaxKind::ModuleImport
1127 | SyntaxKind::ModuleInclude
1128 ) {
1129 return check_surrounding_syntax(&leaf.rightmost_leaf()?);
1130 }
1131
1132 if matches!(leaf.kind(), SyntaxKind::Show) {
1133 return Some(SurroundingSyntax::Selector);
1134 }
1135 if matches!(leaf.kind(), SyntaxKind::Set) {
1136 return Some(SurroundingSyntax::SetRule);
1137 }
1138
1139 None
1140}
1141
1142fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
1144 s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
1145}
1146
1147#[derive(Debug, Clone)]
1155pub enum SyntaxContext<'a> {
1156 Arg {
1158 callee: LinkedNode<'a>,
1160 args: LinkedNode<'a>,
1162 target: ArgClass<'a>,
1164 is_set: bool,
1166 },
1167 Element {
1169 container: LinkedNode<'a>,
1171 target: ArgClass<'a>,
1173 },
1174 Paren {
1176 container: LinkedNode<'a>,
1178 is_before: bool,
1181 },
1182 VarAccess(VarClass<'a>),
1186 ImportPath(LinkedNode<'a>),
1188 IncludePath(LinkedNode<'a>),
1190 Label {
1192 node: LinkedNode<'a>,
1194 is_error: bool,
1196 },
1197 Ref {
1199 node: LinkedNode<'a>,
1201 suffix_colon: bool,
1204 },
1205 At {
1207 node: LinkedNode<'a>,
1209 },
1210 Normal(LinkedNode<'a>),
1212}
1213
1214impl<'a> SyntaxContext<'a> {
1215 pub fn node(&self) -> Option<LinkedNode<'a>> {
1217 Some(match self {
1218 SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1219 match target {
1220 ArgClass::Positional { .. } => return None,
1221 ArgClass::Named(node) => node.clone(),
1222 }
1223 }
1224 SyntaxContext::VarAccess(cls) => cls.node().clone(),
1225 SyntaxContext::Paren { container, .. } => container.clone(),
1226 SyntaxContext::Label { node, .. }
1227 | SyntaxContext::Ref { node, .. }
1228 | SyntaxContext::At { node, .. }
1229 | SyntaxContext::ImportPath(node)
1230 | SyntaxContext::IncludePath(node)
1231 | SyntaxContext::Normal(node) => node.clone(),
1232 })
1233 }
1234
1235 pub fn arg_container(&self) -> Option<&LinkedNode<'a>> {
1237 match self {
1238 Self::Arg { args, .. }
1239 | Self::Element {
1240 container: args, ..
1241 } => Some(args),
1242 Self::Paren { container, .. } => Some(container),
1243 _ => None,
1244 }
1245 }
1246}
1247
1248#[derive(Debug)]
1250enum ArgSourceKind {
1251 Call,
1253 Array,
1255 Dict,
1257}
1258
1259pub fn classify_context_outer<'a>(
1262 outer: LinkedNode<'a>,
1263 node: LinkedNode<'a>,
1264) -> Option<SyntaxContext<'a>> {
1265 use SyntaxClass::*;
1266 let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1267 let node_syntax = classify_syntax(node.clone(), node.offset())?;
1268
1269 match context_syntax {
1270 Callee(callee)
1271 if matches!(node_syntax, Normal(..) | Label { .. } | Ref { .. })
1272 && !matches!(node_syntax, Callee(..)) =>
1273 {
1274 let parent = callee.parent()?;
1275 let args_span = match parent.cast::<ast::Expr>() {
1276 Some(ast::Expr::FuncCall(call)) => call.args().span(),
1277 Some(ast::Expr::MathCall(call)) => call.args().span(),
1278 Some(ast::Expr::SetRule(set)) => set.args().span(),
1279 _ => return None,
1280 };
1281 let args = parent.find(args_span)?;
1282
1283 let is_set = parent.kind() == SyntaxKind::SetRule;
1284 let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1285 Some(SyntaxContext::Arg {
1286 callee,
1287 args,
1288 target: arg_target,
1289 is_set,
1290 })
1291 }
1292 _ => None,
1293 }
1294}
1295
1296pub fn classify_context(node: LinkedNode<'_>, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1299 let mut node = node;
1300 if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1301 loop {
1302 node = node.prev_sibling()?;
1303
1304 if !node.kind().is_trivia() {
1305 break;
1306 }
1307 }
1308 }
1309
1310 let cursor = cursor.unwrap_or_else(|| node.offset());
1311 let syntax = classify_syntax(node.clone(), cursor)?;
1312
1313 let normal_syntax = match syntax {
1314 SyntaxClass::Callee(callee) => {
1315 return callee_context(callee, node);
1316 }
1317 SyntaxClass::Label { node, is_error } => {
1318 return Some(SyntaxContext::Label { node, is_error });
1319 }
1320 SyntaxClass::Ref { node, suffix_colon } => {
1321 return Some(SyntaxContext::Ref { node, suffix_colon });
1322 }
1323 SyntaxClass::At { node } => {
1324 return Some(SyntaxContext::At { node });
1325 }
1326 SyntaxClass::ImportPath(node) => {
1327 return Some(SyntaxContext::ImportPath(node));
1328 }
1329 SyntaxClass::IncludePath(node) => {
1330 return Some(SyntaxContext::IncludePath(node));
1331 }
1332 syntax => syntax,
1333 };
1334
1335 let Some(mut node_parent) = node.parent().cloned() else {
1336 return Some(SyntaxContext::Normal(node));
1337 };
1338
1339 while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1340 let Some(parent) = node_parent.parent() else {
1341 return Some(SyntaxContext::Normal(node));
1342 };
1343 node_parent = parent.clone();
1344 }
1345
1346 match node_parent.kind() {
1347 SyntaxKind::Args | SyntaxKind::MathArgs => {
1348 let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1349 let span = match ancestor.cast::<ast::Expr>()? {
1350 ast::Expr::FuncCall(call) => call.callee().span(),
1351 ast::Expr::MathCall(call) => call.callee().to_untyped().span(),
1352 ast::Expr::SetRule(set) => set.target().span(),
1353 _ => return None,
1354 };
1355 ancestor.find(span)
1356 })?;
1357
1358 let param_node = match node.kind() {
1359 SyntaxKind::Ident
1360 if matches!(
1361 node.parent_kind().zip(node.next_sibling_kind()),
1362 Some((SyntaxKind::Named, SyntaxKind::Colon))
1363 ) =>
1364 {
1365 node
1366 }
1367 _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1368 node.parent().cloned()?
1369 }
1370 _ => node,
1371 };
1372
1373 callee_context(callee, param_node)
1374 }
1375 SyntaxKind::Array | SyntaxKind::Dict => {
1376 let element_target = arg_context(
1377 node_parent.clone(),
1378 node.clone(),
1379 match node_parent.kind() {
1380 SyntaxKind::Array => ArgSourceKind::Array,
1381 SyntaxKind::Dict => ArgSourceKind::Dict,
1382 _ => unreachable!(),
1383 },
1384 )?;
1385 Some(SyntaxContext::Element {
1386 container: node_parent.clone(),
1387 target: element_target,
1388 })
1389 }
1390 SyntaxKind::Parenthesized => {
1391 let is_before = node.offset() <= node_parent.offset() + 1;
1392 Some(SyntaxContext::Paren {
1393 container: node_parent.clone(),
1394 is_before,
1395 })
1396 }
1397 _ => Some(match normal_syntax {
1398 SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1399 normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1400 }),
1401 }
1402}
1403
1404fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
1406 let parent = callee.parent()?;
1407 let args_span = match parent.cast::<ast::Expr>() {
1408 Some(ast::Expr::FuncCall(call)) => call.args().span(),
1409 Some(ast::Expr::MathCall(call)) => call.args().span(),
1410 Some(ast::Expr::SetRule(set)) => set.args().span(),
1411 _ => return None,
1412 };
1413 let args = parent.find(args_span)?;
1414
1415 let mut parent = &node;
1416 loop {
1417 use SyntaxKind::*;
1418 match parent.kind() {
1419 ContentBlock | CodeBlock | Str | Raw | LineComment | BlockComment => {
1420 return Option::None;
1421 }
1422 Args | MathArgs if parent.range() == args.range() => {
1423 break;
1424 }
1425 _ => {}
1426 }
1427
1428 parent = parent.parent()?;
1429 }
1430
1431 let is_set = parent.kind() == SyntaxKind::SetRule;
1432 let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1433 Some(SyntaxContext::Arg {
1434 callee,
1435 args,
1436 target,
1437 is_set,
1438 })
1439}
1440
1441fn arg_context<'a>(
1443 args_node: LinkedNode<'a>,
1444 mut node: LinkedNode<'a>,
1445 param_kind: ArgSourceKind,
1446) -> Option<ArgClass<'a>> {
1447 if node.kind() == SyntaxKind::RightParen {
1448 node = node.prev_sibling()?;
1449 }
1450 match node.kind() {
1451 SyntaxKind::Named => {
1452 let param_ident = node.cast::<ast::Named>()?.name();
1453 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1454 }
1455 SyntaxKind::Colon => {
1456 let prev = node.prev_leaf()?;
1457 let param_ident = prev.cast::<ast::Ident>()?;
1458 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1459 }
1460 _ => {
1461 let parent = node.parent();
1462 if let Some(parent) = parent
1463 && parent.kind() == SyntaxKind::Named
1464 {
1465 let param_ident = parent.cast::<ast::Named>()?;
1466 let name = param_ident.name();
1467 let init = param_ident.expr();
1468 let init = parent.find(init.span())?;
1469 if init.range().contains(&node.offset()) {
1470 let name = args_node.find(name.span())?;
1471 return Some(ArgClass::Named(name));
1472 }
1473 }
1474
1475 let mut spreads = EcoVec::new();
1476 let mut positional = 0;
1477 let is_spread = node.kind() == SyntaxKind::Spread;
1478
1479 let args_before = args_node
1480 .children()
1481 .take_while(|arg| arg.range().end <= node.offset());
1482 match param_kind {
1483 ArgSourceKind::Call => {
1484 for ch in args_before {
1485 match ch.cast::<ast::Arg>() {
1486 Some(ast::Arg::Pos(..)) => {
1487 positional += 1;
1488 }
1489 Some(ast::Arg::Spread(..)) => {
1490 spreads.push(ch);
1491 }
1492 Some(ast::Arg::Named(..)) | None => {}
1493 }
1494 }
1495 }
1496 ArgSourceKind::Array => {
1497 for ch in args_before {
1498 match ch.cast::<ast::ArrayItem>() {
1499 Some(ast::ArrayItem::Pos(..)) => {
1500 positional += 1;
1501 }
1502 Some(ast::ArrayItem::Spread(..)) => {
1503 spreads.push(ch);
1504 }
1505 _ => {}
1506 }
1507 }
1508 }
1509 ArgSourceKind::Dict => {
1510 for ch in args_before {
1511 if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1512 spreads.push(ch);
1513 }
1514 }
1515 }
1516 }
1517
1518 Some(ArgClass::Positional {
1519 spreads,
1520 positional,
1521 is_spread,
1522 })
1523 }
1524 }
1525}
1526
1527pub enum BadCompletionCursor {
1529 ArgListPos,
1531}
1532
1533pub fn bad_completion_cursor(
1535 syntax: Option<&SyntaxClass>,
1536 syntax_context: Option<&SyntaxContext>,
1537 leaf: &LinkedNode,
1538) -> Option<BadCompletionCursor> {
1539 if (matches!(syntax, Some(SyntaxClass::Callee(..))) && {
1541 syntax_context
1542 .and_then(SyntaxContext::arg_container)
1543 .is_some_and(|container| {
1544 container.rightmost_leaf().map(|s| s.offset()) == Some(leaf.offset())
1545 })
1546 }) || (matches!(
1548 syntax,
1549 Some(SyntaxClass::Normal(SyntaxKind::ContentBlock, _))
1550 ) && matches!(leaf.kind(), SyntaxKind::RightBracket))
1551 {
1552 return Some(BadCompletionCursor::ArgListPos);
1553 }
1554
1555 None
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560 use super::*;
1561 use insta::assert_snapshot;
1562 use typst::syntax::{Side, Source, is_newline};
1563
1564 fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1565 let source = Source::detached(source.to_owned());
1566 let root = LinkedNode::new(source.root());
1567 let mut output_mapping = String::new();
1568
1569 let mut cursor = 0;
1570 for ch in source.text().chars() {
1571 cursor += ch.len_utf8();
1572 if is_newline(ch) {
1573 output_mapping.push(ch);
1574 continue;
1575 }
1576
1577 output_mapping.push(mapper(&root, cursor));
1578 }
1579
1580 source
1581 .text()
1582 .lines()
1583 .zip(output_mapping.lines())
1584 .flat_map(|(a, b)| [a, "\n", b, "\n"])
1585 .collect::<String>()
1586 }
1587
1588 fn map_syntax(source: &str) -> String {
1589 map_node(source, |root, cursor| {
1590 let node = root.leaf_at(cursor, Side::Before);
1591 let kind = node.and_then(|node| classify_syntax(node, cursor));
1592 match kind {
1593 Some(SyntaxClass::VarAccess(..)) => 'v',
1594 Some(SyntaxClass::Normal(..)) => 'n',
1595 Some(SyntaxClass::Label { .. }) => 'l',
1596 Some(SyntaxClass::Ref { .. }) => 'r',
1597 Some(SyntaxClass::At { .. }) => 'r',
1598 Some(SyntaxClass::Callee(..)) => 'c',
1599 Some(SyntaxClass::ImportPath(..)) => 'i',
1600 Some(SyntaxClass::IncludePath(..)) => 'I',
1601 None => ' ',
1602 }
1603 })
1604 }
1605
1606 fn map_context(source: &str) -> String {
1607 map_node(source, |root, cursor| {
1608 let node = root.leaf_at(cursor, Side::Before);
1609 let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1610 match kind {
1611 Some(SyntaxContext::Arg { .. }) => 'p',
1612 Some(SyntaxContext::Element { .. }) => 'e',
1613 Some(SyntaxContext::Paren { .. }) => 'P',
1614 Some(SyntaxContext::VarAccess { .. }) => 'v',
1615 Some(SyntaxContext::ImportPath(..)) => 'i',
1616 Some(SyntaxContext::IncludePath(..)) => 'I',
1617 Some(SyntaxContext::Label { .. }) => 'l',
1618 Some(SyntaxContext::Ref { .. }) => 'r',
1619 Some(SyntaxContext::At { .. }) => 'r',
1620 Some(SyntaxContext::Normal(..)) => 'n',
1621 None => ' ',
1622 }
1623 })
1624 }
1625
1626 #[test]
1627 fn test_get_syntax() {
1628 assert_snapshot!(map_syntax(r#"#let x = 1
1629Text
1630= Heading #let y = 2;
1631== Heading"#).trim(), @"
1632 #let x = 1
1633 nnnnvvnnn
1634 Text
1635
1636 = Heading #let y = 2;
1637 nnnnvvnnn
1638 == Heading
1639 ");
1640 assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @"
1641 #let f(x);
1642 nnnnv v
1643 ");
1644 assert_snapshot!(map_syntax(r#"#{
1645 calc.
1646}"#).trim(), @"
1647 #{
1648 n
1649 calc.
1650 nnvvvvvnn
1651 }
1652 n
1653 ");
1654 }
1655
1656 #[test]
1657 fn test_get_context() {
1658 assert_snapshot!(map_context(r#"#let x = 1
1659Text
1660= Heading #let y = 2;
1661== Heading"#).trim(), @"
1662 #let x = 1
1663 nnnnvvnnn
1664 Text
1665
1666 = Heading #let y = 2;
1667 nnnnvvnnn
1668 == Heading
1669 ");
1670 assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @"
1671 #let f(x);
1672 nnnnv v
1673 ");
1674 assert_snapshot!(map_context(r#"#f(1, 2) Test"#).trim(), @"
1675 #f(1, 2) Test
1676 vpppppp
1677 ");
1678 assert_snapshot!(map_context(r#"#() Test"#).trim(), @"
1679 #() Test
1680 ee
1681 ");
1682 assert_snapshot!(map_context(r#"#(1) Test"#).trim(), @"
1683 #(1) Test
1684 PPP
1685 ");
1686 assert_snapshot!(map_context(r#"#(a: 1) Test"#).trim(), @"
1687 #(a: 1) Test
1688 eeeeee
1689 ");
1690 assert_snapshot!(map_context(r#"#(1, 2) Test"#).trim(), @"
1691 #(1, 2) Test
1692 eeeeee
1693 ");
1694 assert_snapshot!(map_context(r#"#(1, 2)
1695 Test"#).trim(), @"
1696 #(1, 2)
1697 eeeeee
1698 Test
1699 ");
1700 }
1701
1702 #[test]
1703 fn ref_syntax() {
1704 assert_snapshot!(map_syntax("@ab:"), @"
1705 @ab:
1706 rrrr
1707 ");
1708 assert_snapshot!(map_syntax("@"), @"
1709 @
1710 r
1711 ");
1712 assert_snapshot!(map_syntax("@;"), @"
1713 @;
1714 r
1715 ");
1716 assert_snapshot!(map_syntax("@ t"), @"
1717 @ t
1718 r
1719 ");
1720 assert_snapshot!(map_syntax("@ab"), @"
1721 @ab
1722 rrr
1723 ");
1724 assert_snapshot!(map_syntax("@ab:"), @"
1725 @ab:
1726 rrrr
1727 ");
1728 assert_snapshot!(map_syntax("@ab:ab"), @"
1729 @ab:ab
1730 rrrrrr
1731 ");
1732 assert_snapshot!(map_syntax("@ab:ab:"), @"
1733 @ab:ab:
1734 rrrrrrr
1735 ");
1736 assert_snapshot!(map_syntax("@ab:ab:ab"), @"
1737 @ab:ab:ab
1738 rrrrrrrrr
1739 ");
1740 assert_snapshot!(map_syntax("@ab[]:"), @"
1741 @ab[]:
1742 rrrnn
1743 ");
1744 assert_snapshot!(map_syntax("@ab[ab]:"), @"
1745 @ab[ab]:
1746 rrrn n
1747 ");
1748 assert_snapshot!(map_syntax("@ab :ab: ab"), @"
1749 @ab :ab: ab
1750 rrr
1751 ");
1752 assert_snapshot!(map_syntax("@ab :ab:ab"), @"
1753 @ab :ab:ab
1754 rrr
1755 ");
1756 }
1757
1758 fn access_node(s: &str, cursor: i32) -> String {
1759 access_node_(s, cursor).unwrap_or_default()
1760 }
1761
1762 fn access_node_(s: &str, cursor: i32) -> Option<String> {
1763 access_var(s, cursor, |_source, var| {
1764 Some(var.accessed_node()?.get().clone().full_text().into())
1765 })
1766 }
1767
1768 fn access_field(s: &str, cursor: i32) -> String {
1769 access_field_(s, cursor).unwrap_or_default()
1770 }
1771
1772 fn access_field_(s: &str, cursor: i32) -> Option<String> {
1773 access_var(s, cursor, |source, var| {
1774 let field = var.accessing_field()?;
1775 Some(match field {
1776 FieldClass::Field(ident) => format!("Field: {}", ident.leaf_text()),
1777 FieldClass::DotSuffix(span_offset) => {
1778 let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1779 format!("DotSuffix: {offset:?}")
1780 }
1781 })
1782 })
1783 }
1784
1785 fn access_var(
1786 s: &str,
1787 cursor: i32,
1788 f: impl FnOnce(&Source, VarClass) -> Option<String>,
1789 ) -> Option<String> {
1790 let cursor = if cursor < 0 {
1791 s.len() as i32 + cursor
1792 } else {
1793 cursor
1794 };
1795 let source = Source::detached(s.to_owned());
1796 let root = LinkedNode::new(source.root());
1797 let node = root.leaf_at(cursor as usize, Side::Before)?;
1798 let syntax = classify_syntax(node, cursor as usize)?;
1799 let SyntaxClass::VarAccess(var) = syntax else {
1800 return None;
1801 };
1802 f(&source, var)
1803 }
1804
1805 #[test]
1806 fn test_access_field() {
1807 assert_snapshot!(access_field("#(a.b)", 5), @"Field: b");
1808 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1809 assert_snapshot!(access_field("$a.$", 3), @"DotSuffix: 3");
1810 assert_snapshot!(access_field("#(a.)", 4), @"DotSuffix: 4");
1811 assert_snapshot!(access_node("#(a..b)", 4), @"a");
1812 assert_snapshot!(access_field("#(a..b)", 4), @"DotSuffix: 4");
1813 assert_snapshot!(access_node("#(a..b())", 4), @"a");
1814 assert_snapshot!(access_field("#(a..b())", 4), @"DotSuffix: 4");
1815 }
1816
1817 #[test]
1818 fn test_code_access() {
1819 assert_snapshot!(access_node("#{`a`.}", 6), @"`a`");
1820 assert_snapshot!(access_field("#{`a`.}", 6), @"DotSuffix: 6");
1821 assert_snapshot!(access_node("#{$a$.}", 6), @"$a$");
1822 assert_snapshot!(access_field("#{$a$.}", 6), @"DotSuffix: 6");
1823 assert_snapshot!(access_node("#{\"a\".}", 6), @r#""a""#);
1824 assert_snapshot!(access_field("#{\"a\".}", 6), @"DotSuffix: 6");
1825 assert_snapshot!(access_node("#{<a>.}", 6), @"<a>");
1826 assert_snapshot!(access_field("#{<a>.}", 6), @"DotSuffix: 6");
1827 }
1828
1829 #[test]
1830 fn test_markup_access() {
1831 assert_snapshot!(access_field("_a_.", 4), @"");
1832 assert_snapshot!(access_field("*a*.", 4), @"");
1833 assert_snapshot!(access_field("`a`.", 4), @"");
1834 assert_snapshot!(access_field("$a$.", 4), @"");
1835 assert_snapshot!(access_field("\"a\".", 4), @"");
1836 assert_snapshot!(access_field("@a.", 3), @"");
1837 assert_snapshot!(access_field("<a>.", 4), @"");
1838 }
1839
1840 #[test]
1841 fn test_markup_chain_access() {
1842 assert_snapshot!(access_node("#a.b.", 5), @"a.b");
1843 assert_snapshot!(access_field("#a.b.", 5), @"DotSuffix: 5");
1844 assert_snapshot!(access_node("#a.b.c.", 7), @"a.b.c");
1845 assert_snapshot!(access_field("#a.b.c.", 7), @"DotSuffix: 7");
1846 assert_snapshot!(access_node("#context a.", 11), @"a");
1847 assert_snapshot!(access_field("#context a.", 11), @"DotSuffix: 11");
1848 assert_snapshot!(access_node("#context a.b.", 13), @"a.b");
1849 assert_snapshot!(access_field("#context a.b.", 13), @"DotSuffix: 13");
1850
1851 assert_snapshot!(access_node("#a.at(1).", 9), @"a.at(1)");
1852 assert_snapshot!(access_field("#a.at(1).", 9), @"DotSuffix: 9");
1853 assert_snapshot!(access_node("#context a.at(1).", 17), @"a.at(1)");
1854 assert_snapshot!(access_field("#context a.at(1).", 17), @"DotSuffix: 17");
1855
1856 assert_snapshot!(access_node("#a.at(1).c.", 11), @"a.at(1).c");
1857 assert_snapshot!(access_field("#a.at(1).c.", 11), @"DotSuffix: 11");
1858 assert_snapshot!(access_node("#context a.at(1).c.", 19), @"a.at(1).c");
1859 assert_snapshot!(access_field("#context a.at(1).c.", 19), @"DotSuffix: 19");
1860 }
1861
1862 #[test]
1863 fn test_hash_access() {
1864 assert_snapshot!(access_node("#a.", 3), @"a");
1865 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1866 assert_snapshot!(access_node("#(a).", 5), @"(a)");
1867 assert_snapshot!(access_field("#(a).", 5), @"DotSuffix: 5");
1868 assert_snapshot!(access_node("#`a`.", 5), @"`a`");
1869 assert_snapshot!(access_field("#`a`.", 5), @"DotSuffix: 5");
1870 assert_snapshot!(access_node("#$a$.", 5), @"$a$");
1871 assert_snapshot!(access_field("#$a$.", 5), @"DotSuffix: 5");
1872 assert_snapshot!(access_node("#(a,).", 6), @"(a,)");
1873 assert_snapshot!(access_field("#(a,).", 6), @"DotSuffix: 6");
1874 }
1875}