1use crate::debug_loc::SourceSpanOffset;
60use serde::{Deserialize, Serialize};
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).find(|n| n.is::<ast::Expr>()).cloned()
75}
76
77pub enum PreviousItem<'a> {
80 Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
82 Sibling(&'a LinkedNode<'a>),
84}
85
86impl<'a> PreviousItem<'a> {
87 pub fn node(&self) -> &'a LinkedNode<'a> {
89 match self {
90 PreviousItem::Sibling(node) => node,
91 PreviousItem::Parent(node, _) => node,
92 }
93 }
94}
95
96pub fn previous_items<T>(
99 node: LinkedNode,
100 mut recv: impl FnMut(PreviousItem) -> Option<T>,
101) -> Option<T> {
102 let mut ancestor = Some(node);
103 while let Some(node) = &ancestor {
104 let mut sibling = Some(node.clone());
105 while let Some(node) = &sibling {
106 if let Some(v) = recv(PreviousItem::Sibling(node)) {
107 return Some(v);
108 }
109
110 sibling = node.prev_sibling();
111 }
112
113 if let Some(parent) = node.parent() {
114 if let Some(v) = recv(PreviousItem::Parent(parent, node)) {
115 return Some(v);
116 }
117
118 ancestor = Some(parent.clone());
119 continue;
120 }
121
122 break;
123 }
124
125 None
126}
127
128pub enum PreviousDecl<'a> {
131 Ident(ast::Ident<'a>),
141 ImportSource(ast::Expr<'a>),
151 ImportAll(ast::ModuleImport<'a>),
161}
162
163pub fn previous_decls<T>(
166 node: LinkedNode,
167 mut recv: impl FnMut(PreviousDecl) -> Option<T>,
168) -> Option<T> {
169 previous_items(node, |item| {
170 match (&item, item.node().cast::<ast::Expr>()?) {
171 (PreviousItem::Sibling(..), ast::Expr::Let(lb)) => {
172 for ident in lb.kind().bindings() {
173 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
174 return Some(t);
175 }
176 }
177 }
178 (PreviousItem::Sibling(..), ast::Expr::Import(import)) => {
179 match import.imports() {
181 Some(ast::Imports::Wildcard) => {
182 if let Some(t) = recv(PreviousDecl::ImportAll(import)) {
183 return Some(t);
184 }
185 }
186 Some(ast::Imports::Items(items)) => {
187 for item in items.iter() {
188 if let Some(t) = recv(PreviousDecl::Ident(item.bound_name())) {
189 return Some(t);
190 }
191 }
192 }
193 _ => {}
194 }
195
196 if let Some(new_name) = import.new_name() {
198 if let Some(t) = recv(PreviousDecl::Ident(new_name)) {
199 return Some(t);
200 }
201 } else if import.imports().is_none() {
202 if let Some(t) = recv(PreviousDecl::ImportSource(import.source())) {
203 return Some(t);
204 }
205 }
206 }
207 (PreviousItem::Parent(parent, child), ast::Expr::For(for_expr)) => {
208 let body = parent.find(for_expr.body().span());
209 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
210 if !in_body {
211 return None;
212 }
213
214 for ident in for_expr.pattern().bindings() {
215 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
216 return Some(t);
217 }
218 }
219 }
220 (PreviousItem::Parent(parent, child), ast::Expr::Closure(closure)) => {
221 let body = parent.find(closure.body().span());
222 let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
223 if !in_body {
224 return None;
225 }
226
227 for param in closure.params().children() {
228 match param {
229 ast::Param::Pos(pos) => {
230 for ident in pos.bindings() {
231 if let Some(t) = recv(PreviousDecl::Ident(ident)) {
232 return Some(t);
233 }
234 }
235 }
236 ast::Param::Named(named) => {
237 if let Some(t) = recv(PreviousDecl::Ident(named.name())) {
238 return Some(t);
239 }
240 }
241 ast::Param::Spread(spread) => {
242 if let Some(sink_ident) = spread.sink_ident() {
243 if let Some(t) = recv(PreviousDecl::Ident(sink_ident)) {
244 return Some(t);
245 }
246 }
247 }
248 }
249 }
250 }
251 _ => {}
252 };
253 None
254 })
255}
256
257pub fn is_mark(sk: SyntaxKind) -> bool {
259 use SyntaxKind::*;
260 #[allow(clippy::match_like_matches_macro)]
261 match sk {
262 MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
263 Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
264 LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
265 Slash | Hat | Comma | Semicolon | Colon | Hash => true,
266 _ => false,
267 }
268}
269
270pub fn is_ident_like(node: &SyntaxNode) -> bool {
272 fn can_be_ident(node: &SyntaxNode) -> bool {
273 typst::syntax::is_ident(node.text())
274 }
275
276 use SyntaxKind::*;
277 let kind = node.kind();
278 matches!(kind, Ident | MathIdent | Underscore)
279 || (matches!(kind, Error) && can_be_ident(node))
280 || kind.is_keyword()
281}
282
283#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
285#[serde(rename_all = "camelCase")]
286pub enum InterpretMode {
287 Comment,
289 String,
291 Raw,
293 Markup,
295 Code,
297 Math,
299}
300
301pub fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode {
303 loop {
304 crate::log_debug_ct!("leaf for mode: {leaf:?}");
305 if let Some(t) = leaf {
306 if let Some(mode) = interpret_mode_at_kind(t.kind()) {
307 break mode;
308 }
309
310 if !t.kind().is_trivia() && {
311 t.prev_leaf().is_some_and(|n| n.kind() == SyntaxKind::Hash)
313 } {
314 return InterpretMode::Code;
315 }
316
317 leaf = t.parent();
318 } else {
319 break InterpretMode::Markup;
320 }
321 }
322}
323
324pub(crate) fn interpret_mode_at_kind(kind: SyntaxKind) -> Option<InterpretMode> {
326 use SyntaxKind::*;
327 Some(match kind {
328 LineComment | BlockComment | Shebang => InterpretMode::Comment,
329 Raw => InterpretMode::Raw,
330 Str => InterpretMode::String,
331 CodeBlock | Code => InterpretMode::Code,
332 ContentBlock | Markup => InterpretMode::Markup,
333 Equation | Math => InterpretMode::Math,
334 Hash => InterpretMode::Code,
335 Label | Text | Ident | Args | FuncCall | FieldAccess | Bool | Int | Float | Numeric
336 | Space | Linebreak | Parbreak | Escape | Shorthand | SmartQuote | RawLang | RawDelim
337 | RawTrimmed | LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen
338 | RightParen | Comma | Semicolon | Colon | Star | Underscore | Dollar | Plus | Minus
339 | Slash | Hat | Prime | Dot | Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq
340 | HyphEq | StarEq | SlashEq | Dots | Arrow | Root | Not | And | Or | None | Auto | As
341 | Named | Keyed | Spread | Error | End => return Option::None,
342 Strong | Emph | Link | Ref | RefMarker | Heading | HeadingMarker | ListItem
343 | ListMarker | EnumItem | EnumMarker | TermItem | TermMarker => InterpretMode::Markup,
344 MathIdent | MathAlignPoint | MathDelimited | MathAttach | MathPrimes | MathFrac
345 | MathRoot | MathShorthand | MathText => InterpretMode::Math,
346 Let | Set | Show | Context | If | Else | For | In | While | Break | Continue | Return
347 | Import | Include | Closure | Params | LetBinding | SetRule | ShowRule | Contextual
348 | Conditional | WhileLoop | ForLoop | LoopBreak | ModuleImport | ImportItems
349 | ImportItemPath | RenamedImportItem | ModuleInclude | LoopContinue | FuncReturn
350 | Unary | Binary | Parenthesized | Dict | Array | Destructuring | DestructAssignment => {
351 InterpretMode::Code
352 }
353 })
354}
355
356#[derive(Debug, Clone)]
358pub enum DefClass<'a> {
359 Let(LinkedNode<'a>),
361 Import(LinkedNode<'a>),
363}
364
365impl DefClass<'_> {
366 pub fn node(&self) -> &LinkedNode {
368 match self {
369 DefClass::Let(node) => node,
370 DefClass::Import(node) => node,
371 }
372 }
373
374 pub fn name(&self) -> Option<LinkedNode> {
376 match self {
377 DefClass::Let(node) => {
378 let lb: ast::LetBinding<'_> = node.cast()?;
379 let names = match lb.kind() {
380 ast::LetBindingKind::Closure(name) => node.find(name.span())?,
381 ast::LetBindingKind::Normal(ast::Pattern::Normal(name)) => {
382 node.find(name.span())?
383 }
384 _ => return None,
385 };
386
387 Some(names)
388 }
389 DefClass::Import(_node) => {
390 None
394 }
395 }
396 }
397
398 pub fn name_range(&self) -> Option<Range<usize>> {
400 self.name().map(|node| node.range())
401 }
402}
403
404pub fn classify_def_loosely(node: LinkedNode) -> Option<DefClass<'_>> {
407 classify_def_(node, false)
408}
409
410pub fn classify_def(node: LinkedNode) -> Option<DefClass<'_>> {
412 classify_def_(node, true)
413}
414
415fn classify_def_(node: LinkedNode, strict: bool) -> Option<DefClass<'_>> {
417 let mut ancestor = node;
418 if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
419 ancestor = ancestor.prev_sibling()?;
420 }
421
422 while !ancestor.is::<ast::Expr>() {
423 ancestor = ancestor.parent()?.clone();
424 }
425 crate::log_debug_ct!("ancestor: {ancestor:?}");
426 let adjusted = adjust_expr(ancestor)?;
427 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
428
429 let may_ident = adjusted.cast::<ast::Expr>()?;
430 if strict && !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
431 return None;
432 }
433
434 let expr = may_ident;
435 Some(match expr {
436 ast::Expr::FuncCall(..) => return None,
439 ast::Expr::Set(..) => return None,
440 ast::Expr::Let(..) => DefClass::Let(adjusted),
441 ast::Expr::Import(..) => DefClass::Import(adjusted),
442 ast::Expr::Ident(..)
444 | ast::Expr::MathIdent(..)
445 | ast::Expr::FieldAccess(..)
446 | ast::Expr::Closure(..) => {
447 let mut ancestor = adjusted;
448 while !ancestor.is::<ast::LetBinding>() {
449 ancestor = ancestor.parent()?.clone();
450 }
451
452 DefClass::Let(ancestor)
453 }
454 ast::Expr::Str(..) => {
455 let parent = adjusted.parent()?;
456 if parent.kind() != SyntaxKind::ModuleImport {
457 return None;
458 }
459
460 DefClass::Import(parent.clone())
461 }
462 _ if expr.hash() => return None,
463 _ => {
464 crate::log_debug_ct!("unsupported kind {:?}", adjusted.kind());
465 return None;
466 }
467 })
468}
469
470fn adjust_expr(mut node: LinkedNode) -> Option<LinkedNode> {
475 while let Some(paren_expr) = node.cast::<ast::Parenthesized>() {
476 node = node.find(paren_expr.expr().span())?;
477 }
478 if let Some(parent) = node.parent() {
479 if let Some(field_access) = parent.cast::<ast::FieldAccess>() {
480 if node.span() == field_access.field().span() {
481 return Some(parent.clone());
482 }
483 }
484 }
485 Some(node)
486}
487
488#[derive(Debug, Clone)]
490pub enum FieldClass<'a> {
491 Field(LinkedNode<'a>),
501
502 DotSuffix(SourceSpanOffset),
512}
513
514impl FieldClass<'_> {
515 pub fn offset(&self, source: &Source) -> Option<usize> {
517 Some(match self {
518 Self::Field(node) => node.offset(),
519 Self::DotSuffix(span_offset) => {
520 source.find(span_offset.span)?.offset() + span_offset.offset
521 }
522 })
523 }
524}
525
526#[derive(Debug, Clone)]
529pub enum VarClass<'a> {
530 Ident(LinkedNode<'a>),
532 FieldAccess(LinkedNode<'a>),
534 DotAccess(LinkedNode<'a>),
538}
539
540impl<'a> VarClass<'a> {
541 pub fn node(&self) -> &LinkedNode<'a> {
543 match self {
544 Self::Ident(node) | Self::FieldAccess(node) | Self::DotAccess(node) => node,
545 }
546 }
547
548 pub fn accessed_node(&self) -> Option<LinkedNode<'a>> {
550 Some(match self {
551 Self::Ident(node) => node.clone(),
552 Self::FieldAccess(node) => {
553 let field_access = node.cast::<ast::FieldAccess>()?;
554 node.find(field_access.target().span())?
555 }
556 Self::DotAccess(node) => node.clone(),
557 })
558 }
559
560 pub fn accessing_field(&self) -> Option<FieldClass<'a>> {
562 match self {
563 Self::FieldAccess(node) => {
564 let dot = node
565 .children()
566 .find(|n| matches!(n.kind(), SyntaxKind::Dot))?;
567 let mut iter_after_dot =
568 node.children().skip_while(|n| n.kind() != SyntaxKind::Dot);
569 let ident = iter_after_dot.find(|n| {
570 matches!(
571 n.kind(),
572 SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::Error
573 )
574 });
575
576 let ident_case = ident.map(|ident| {
577 if ident.text().is_empty() {
578 FieldClass::DotSuffix(SourceSpanOffset {
579 span: ident.span(),
580 offset: 0,
581 })
582 } else {
583 FieldClass::Field(ident)
584 }
585 });
586
587 ident_case.or_else(|| {
588 Some(FieldClass::DotSuffix(SourceSpanOffset {
589 span: dot.span(),
590 offset: 1,
591 }))
592 })
593 }
594 Self::DotAccess(node) => Some(FieldClass::DotSuffix(SourceSpanOffset {
595 span: node.span(),
596 offset: node.range().len() + 1,
597 })),
598 Self::Ident(_) => None,
599 }
600 }
601}
602
603#[derive(Debug, Clone)]
605pub enum SyntaxClass<'a> {
606 VarAccess(VarClass<'a>),
610 Label {
612 node: LinkedNode<'a>,
614 is_error: bool,
616 },
617 Ref(LinkedNode<'a>),
619 Callee(LinkedNode<'a>),
621 ImportPath(LinkedNode<'a>),
623 IncludePath(LinkedNode<'a>),
625 Normal(SyntaxKind, LinkedNode<'a>),
627}
628
629impl<'a> SyntaxClass<'a> {
630 pub fn label(node: LinkedNode<'a>) -> Self {
632 Self::Label {
633 node,
634 is_error: false,
635 }
636 }
637
638 pub fn error_as_label(node: LinkedNode<'a>) -> Self {
640 Self::Label {
641 node,
642 is_error: true,
643 }
644 }
645
646 pub fn node(&self) -> &LinkedNode<'a> {
648 match self {
649 SyntaxClass::VarAccess(cls) => cls.node(),
650 SyntaxClass::Label { node, .. }
651 | SyntaxClass::Ref(node)
652 | SyntaxClass::Callee(node)
653 | SyntaxClass::ImportPath(node)
654 | SyntaxClass::IncludePath(node)
655 | SyntaxClass::Normal(_, node) => node,
656 }
657 }
658
659 pub fn complete_offset(&self) -> Option<usize> {
661 match self {
662 SyntaxClass::Label { node, .. } => Some(node.offset() + 1),
665 _ => None,
666 }
667 }
668}
669
670pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option<SyntaxClass<'_>> {
673 if matches!(node.kind(), SyntaxKind::Error) && node.text().starts_with('<') {
674 return Some(SyntaxClass::error_as_label(node));
675 }
676
677 fn can_skip_trivia(node: &LinkedNode, cursor: usize) -> bool {
679 if !node.kind().is_trivia() || !node.parent_kind().is_some_and(possible_in_code_trivia) {
681 return false;
682 }
683
684 let previous_text = node.text().as_bytes();
686 let previous_text = if node.range().contains(&cursor) {
687 &previous_text[..cursor - node.offset()]
688 } else {
689 previous_text
690 };
691
692 !previous_text.contains(&b'\n')
697 }
698
699 let mut node = node;
701 if can_skip_trivia(&node, cursor) {
702 node = node.prev_sibling()?;
703 }
704
705 fn classify_dot_access<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
711 let dot_target = node.prev_leaf().and_then(first_ancestor_expr)?;
712 let mode = interpret_mode_at(Some(node));
713
714 if matches!(mode, InterpretMode::Math | InterpretMode::Code) || {
715 matches!(mode, InterpretMode::Markup)
716 && matches!(
717 dot_target.prev_leaf().as_deref().map(SyntaxNode::kind),
718 Some(SyntaxKind::Hash)
719 )
720 } {
721 return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
722 }
723
724 None
725 }
726
727 if node.offset() + 1 == cursor && {
728 matches!(node.kind(), SyntaxKind::Dot)
730 || (matches!(
731 node.kind(),
732 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
733 ) && node.text().starts_with("."))
734 } {
735 if let Some(dot_access) = classify_dot_access(&node) {
736 return Some(dot_access);
737 }
738 }
739
740 if node.offset() + 1 == cursor
741 && matches!(node.kind(), SyntaxKind::Dots)
742 && matches!(node.parent_kind(), Some(SyntaxKind::Spread))
743 {
744 if let Some(dot_access) = classify_dot_access(&node) {
745 return Some(dot_access);
746 }
747 }
748
749 if matches!(node.kind(), SyntaxKind::Text) {
750 let mode = interpret_mode_at(Some(&node));
751 if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
752 return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
753 }
754 }
755
756 let ancestor = first_ancestor_expr(node)?;
758 crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
759
760 let adjusted = adjust_expr(ancestor)?;
762 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
763
764 let expr = adjusted.cast::<ast::Expr>()?;
766 Some(match expr {
767 ast::Expr::Label(..) => SyntaxClass::label(adjusted),
768 ast::Expr::Ref(..) => SyntaxClass::Ref(adjusted),
769 ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
770 ast::Expr::Set(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
771 ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
772 SyntaxClass::VarAccess(VarClass::Ident(adjusted))
773 }
774 ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
775 ast::Expr::Str(..) => {
776 let parent = adjusted.parent()?;
777 if parent.kind() == SyntaxKind::ModuleImport {
778 SyntaxClass::ImportPath(adjusted)
779 } else if parent.kind() == SyntaxKind::ModuleInclude {
780 SyntaxClass::IncludePath(adjusted)
781 } else {
782 SyntaxClass::Normal(adjusted.kind(), adjusted)
783 }
784 }
785 _ if expr.hash()
786 || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
787 {
788 SyntaxClass::Normal(adjusted.kind(), adjusted)
789 }
790 _ => return None,
791 })
792}
793
794fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
797 !matches!(
798 interpret_mode_at_kind(kind),
799 Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
800 )
801}
802
803#[derive(Debug, Clone)]
805pub enum ArgClass<'a> {
806 Positional {
808 spreads: EcoVec<LinkedNode<'a>>,
810 positional: usize,
812 is_spread: bool,
814 },
815 Named(LinkedNode<'a>),
817}
818
819impl ArgClass<'_> {
820 pub fn first_positional() -> Self {
822 ArgClass::Positional {
823 spreads: EcoVec::new(),
824 positional: 0,
825 is_spread: false,
826 }
827 }
828}
829
830#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
833pub enum SurroundingSyntax {
834 Regular,
836 StringContent,
838 Selector,
840 ShowTransform,
842 ImportList,
844 SetRule,
846 ParamList,
848}
849
850pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
852 check_previous_syntax(node)
853 .or_else(|| check_surrounding_syntax(node))
854 .unwrap_or(SurroundingSyntax::Regular)
855}
856
857fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
858 use SurroundingSyntax::*;
859 let mut met_args = false;
860
861 if matches!(leaf.kind(), SyntaxKind::Str) {
862 return Some(StringContent);
863 }
864
865 while let Some(parent) = leaf.parent() {
866 crate::log_debug_ct!(
867 "check_surrounding_syntax: {:?}::{:?}",
868 parent.kind(),
869 leaf.kind()
870 );
871 match parent.kind() {
872 SyntaxKind::CodeBlock
873 | SyntaxKind::ContentBlock
874 | SyntaxKind::Equation
875 | SyntaxKind::Closure => {
876 return Some(Regular);
877 }
878 SyntaxKind::ImportItemPath
879 | SyntaxKind::ImportItems
880 | SyntaxKind::RenamedImportItem => {
881 return Some(ImportList);
882 }
883 SyntaxKind::ModuleImport => {
884 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
885 let Some(colon) = colon else {
886 return Some(Regular);
887 };
888
889 if leaf.offset() >= colon.offset() {
890 return Some(ImportList);
891 } else {
892 return Some(Regular);
893 }
894 }
895 SyntaxKind::Named => {
896 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
897 let Some(colon) = colon else {
898 return Some(Regular);
899 };
900
901 return if leaf.offset() >= colon.offset() {
902 Some(Regular)
903 } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
904 Some(ParamList)
905 } else {
906 Some(Regular)
907 };
908 }
909 SyntaxKind::Params => {
910 return Some(ParamList);
911 }
912 SyntaxKind::Args => {
913 met_args = true;
914 }
915 SyntaxKind::SetRule => {
916 let rule = parent.get().cast::<ast::SetRule>()?;
917 if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
918 return Some(Regular);
919 } else {
920 return Some(SetRule);
921 }
922 }
923 SyntaxKind::ShowRule => {
924 if met_args {
925 return Some(Regular);
926 }
927
928 let rule = parent.get().cast::<ast::ShowRule>()?;
929 let colon = rule
930 .to_untyped()
931 .children()
932 .find(|s| s.kind() == SyntaxKind::Colon);
933 let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
934 return Some(Selector);
936 };
937
938 if leaf.offset() >= colon.offset() {
939 return Some(ShowTransform);
940 } else {
941 return Some(Selector); }
943 }
944 _ => {}
945 }
946
947 leaf = parent;
948 }
949
950 None
951}
952
953fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
954 let mut leaf = leaf.clone();
955 if leaf.kind().is_trivia() {
956 leaf = leaf.prev_sibling()?;
957 }
958 if matches!(
959 leaf.kind(),
960 SyntaxKind::ShowRule
961 | SyntaxKind::SetRule
962 | SyntaxKind::ModuleImport
963 | SyntaxKind::ModuleInclude
964 ) {
965 return check_surrounding_syntax(&leaf.rightmost_leaf()?);
966 }
967
968 if matches!(leaf.kind(), SyntaxKind::Show) {
969 return Some(SurroundingSyntax::Selector);
970 }
971 if matches!(leaf.kind(), SyntaxKind::Set) {
972 return Some(SurroundingSyntax::SetRule);
973 }
974
975 None
976}
977
978fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
979 s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
980}
981
982#[derive(Debug, Clone)]
990pub enum SyntaxContext<'a> {
991 Arg {
993 callee: LinkedNode<'a>,
995 args: LinkedNode<'a>,
997 target: ArgClass<'a>,
999 is_set: bool,
1001 },
1002 Element {
1004 container: LinkedNode<'a>,
1006 target: ArgClass<'a>,
1008 },
1009 Paren {
1011 container: LinkedNode<'a>,
1013 is_before: bool,
1016 },
1017 VarAccess(VarClass<'a>),
1021 ImportPath(LinkedNode<'a>),
1023 IncludePath(LinkedNode<'a>),
1025 Label {
1027 node: LinkedNode<'a>,
1029 is_error: bool,
1031 },
1032 Normal(LinkedNode<'a>),
1034}
1035
1036impl<'a> SyntaxContext<'a> {
1037 pub fn node(&self) -> Option<LinkedNode<'a>> {
1039 Some(match self {
1040 SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1041 match target {
1042 ArgClass::Positional { .. } => return None,
1043 ArgClass::Named(node) => node.clone(),
1044 }
1045 }
1046 SyntaxContext::VarAccess(cls) => cls.node().clone(),
1047 SyntaxContext::Paren { container, .. } => container.clone(),
1048 SyntaxContext::Label { node, .. }
1049 | SyntaxContext::ImportPath(node)
1050 | SyntaxContext::IncludePath(node)
1051 | SyntaxContext::Normal(node) => node.clone(),
1052 })
1053 }
1054}
1055
1056#[derive(Debug)]
1058enum ArgSourceKind {
1059 Call,
1061 Array,
1063 Dict,
1065}
1066
1067pub fn classify_context_outer<'a>(
1070 outer: LinkedNode<'a>,
1071 node: LinkedNode<'a>,
1072) -> Option<SyntaxContext<'a>> {
1073 use SyntaxClass::*;
1074 let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1075 let node_syntax = classify_syntax(node.clone(), node.offset())?;
1076
1077 match context_syntax {
1078 Callee(callee)
1079 if matches!(node_syntax, Normal(..) | Label { .. } | Ref(..))
1080 && !matches!(node_syntax, Callee(..)) =>
1081 {
1082 let parent = callee.parent()?;
1083 let args = match parent.cast::<ast::Expr>() {
1084 Some(ast::Expr::FuncCall(call)) => call.args(),
1085 Some(ast::Expr::Set(set)) => set.args(),
1086 _ => return None,
1087 };
1088 let args = parent.find(args.span())?;
1089
1090 let is_set = parent.kind() == SyntaxKind::SetRule;
1091 let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1092 Some(SyntaxContext::Arg {
1093 callee,
1094 args,
1095 target: arg_target,
1096 is_set,
1097 })
1098 }
1099 _ => None,
1100 }
1101}
1102
1103pub fn classify_context(node: LinkedNode, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1106 let mut node = node;
1107 if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1108 loop {
1109 node = node.prev_sibling()?;
1110
1111 if !node.kind().is_trivia() {
1112 break;
1113 }
1114 }
1115 }
1116
1117 let cursor = cursor.unwrap_or_else(|| node.offset());
1118 let syntax = classify_syntax(node.clone(), cursor)?;
1119
1120 let normal_syntax = match syntax {
1121 SyntaxClass::Callee(callee) => {
1122 return callee_context(callee, node);
1123 }
1124 SyntaxClass::Label { node, is_error } => {
1125 return Some(SyntaxContext::Label { node, is_error });
1126 }
1127 SyntaxClass::ImportPath(node) => {
1128 return Some(SyntaxContext::ImportPath(node));
1129 }
1130 SyntaxClass::IncludePath(node) => {
1131 return Some(SyntaxContext::IncludePath(node));
1132 }
1133 syntax => syntax,
1134 };
1135
1136 let Some(mut node_parent) = node.parent().cloned() else {
1137 return Some(SyntaxContext::Normal(node));
1138 };
1139
1140 while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1141 let Some(parent) = node_parent.parent() else {
1142 return Some(SyntaxContext::Normal(node));
1143 };
1144 node_parent = parent.clone();
1145 }
1146
1147 match node_parent.kind() {
1148 SyntaxKind::Args => {
1149 let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1150 let span = match ancestor.cast::<ast::Expr>()? {
1151 ast::Expr::FuncCall(call) => call.callee().span(),
1152 ast::Expr::Set(set) => set.target().span(),
1153 _ => return None,
1154 };
1155 ancestor.find(span)
1156 })?;
1157
1158 let param_node = match node.kind() {
1159 SyntaxKind::Ident
1160 if matches!(
1161 node.parent_kind().zip(node.next_sibling_kind()),
1162 Some((SyntaxKind::Named, SyntaxKind::Colon))
1163 ) =>
1164 {
1165 node
1166 }
1167 _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1168 node.parent().cloned()?
1169 }
1170 _ => node,
1171 };
1172
1173 callee_context(callee, param_node)
1174 }
1175 SyntaxKind::Array | SyntaxKind::Dict => {
1176 let element_target = arg_context(
1177 node_parent.clone(),
1178 node.clone(),
1179 match node_parent.kind() {
1180 SyntaxKind::Array => ArgSourceKind::Array,
1181 SyntaxKind::Dict => ArgSourceKind::Dict,
1182 _ => unreachable!(),
1183 },
1184 )?;
1185 Some(SyntaxContext::Element {
1186 container: node_parent.clone(),
1187 target: element_target,
1188 })
1189 }
1190 SyntaxKind::Parenthesized => {
1191 let is_before = node.offset() <= node_parent.offset() + 1;
1192 Some(SyntaxContext::Paren {
1193 container: node_parent.clone(),
1194 is_before,
1195 })
1196 }
1197 _ => Some(match normal_syntax {
1198 SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1199 normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1200 }),
1201 }
1202}
1203
1204fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
1205 let parent = callee.parent()?;
1206 let args = match parent.cast::<ast::Expr>() {
1207 Some(ast::Expr::FuncCall(call)) => call.args(),
1208 Some(ast::Expr::Set(set)) => set.args(),
1209 _ => return None,
1210 };
1211 let args = parent.find(args.span())?;
1212
1213 let is_set = parent.kind() == SyntaxKind::SetRule;
1214 let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1215 Some(SyntaxContext::Arg {
1216 callee,
1217 args,
1218 target,
1219 is_set,
1220 })
1221}
1222
1223fn arg_context<'a>(
1224 args_node: LinkedNode<'a>,
1225 mut node: LinkedNode<'a>,
1226 param_kind: ArgSourceKind,
1227) -> Option<ArgClass<'a>> {
1228 if node.kind() == SyntaxKind::RightParen {
1229 node = node.prev_sibling()?;
1230 }
1231 match node.kind() {
1232 SyntaxKind::Named => {
1233 let param_ident = node.cast::<ast::Named>()?.name();
1234 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1235 }
1236 SyntaxKind::Colon => {
1237 let prev = node.prev_leaf()?;
1238 let param_ident = prev.cast::<ast::Ident>()?;
1239 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1240 }
1241 _ => {
1242 let mut spreads = EcoVec::new();
1243 let mut positional = 0;
1244 let is_spread = node.kind() == SyntaxKind::Spread;
1245
1246 let args_before = args_node
1247 .children()
1248 .take_while(|arg| arg.range().end <= node.offset());
1249 match param_kind {
1250 ArgSourceKind::Call => {
1251 for ch in args_before {
1252 match ch.cast::<ast::Arg>() {
1253 Some(ast::Arg::Pos(..)) => {
1254 positional += 1;
1255 }
1256 Some(ast::Arg::Spread(..)) => {
1257 spreads.push(ch);
1258 }
1259 Some(ast::Arg::Named(..)) | None => {}
1260 }
1261 }
1262 }
1263 ArgSourceKind::Array => {
1264 for ch in args_before {
1265 match ch.cast::<ast::ArrayItem>() {
1266 Some(ast::ArrayItem::Pos(..)) => {
1267 positional += 1;
1268 }
1269 Some(ast::ArrayItem::Spread(..)) => {
1270 spreads.push(ch);
1271 }
1272 _ => {}
1273 }
1274 }
1275 }
1276 ArgSourceKind::Dict => {
1277 for ch in args_before {
1278 if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1279 spreads.push(ch);
1280 }
1281 }
1282 }
1283 }
1284
1285 Some(ArgClass::Positional {
1286 spreads,
1287 positional,
1288 is_spread,
1289 })
1290 }
1291 }
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296 use super::*;
1297 use insta::assert_snapshot;
1298 use typst::syntax::{is_newline, Side, Source};
1299
1300 fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1301 let source = Source::detached(source.to_owned());
1302 let root = LinkedNode::new(source.root());
1303 let mut output_mapping = String::new();
1304
1305 let mut cursor = 0;
1306 for ch in source.text().chars() {
1307 cursor += ch.len_utf8();
1308 if is_newline(ch) {
1309 output_mapping.push(ch);
1310 continue;
1311 }
1312
1313 output_mapping.push(mapper(&root, cursor));
1314 }
1315
1316 source
1317 .text()
1318 .lines()
1319 .zip(output_mapping.lines())
1320 .flat_map(|(a, b)| [a, "\n", b, "\n"])
1321 .collect::<String>()
1322 }
1323
1324 fn map_syntax(source: &str) -> String {
1325 map_node(source, |root, cursor| {
1326 let node = root.leaf_at(cursor, Side::Before);
1327 let kind = node.and_then(|node| classify_syntax(node, cursor));
1328 match kind {
1329 Some(SyntaxClass::VarAccess(..)) => 'v',
1330 Some(SyntaxClass::Normal(..)) => 'n',
1331 Some(SyntaxClass::Label { .. }) => 'l',
1332 Some(SyntaxClass::Ref(..)) => 'r',
1333 Some(SyntaxClass::Callee(..)) => 'c',
1334 Some(SyntaxClass::ImportPath(..)) => 'i',
1335 Some(SyntaxClass::IncludePath(..)) => 'I',
1336 None => ' ',
1337 }
1338 })
1339 }
1340
1341 fn map_context(source: &str) -> String {
1342 map_node(source, |root, cursor| {
1343 let node = root.leaf_at(cursor, Side::Before);
1344 let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1345 match kind {
1346 Some(SyntaxContext::Arg { .. }) => 'p',
1347 Some(SyntaxContext::Element { .. }) => 'e',
1348 Some(SyntaxContext::Paren { .. }) => 'P',
1349 Some(SyntaxContext::VarAccess { .. }) => 'v',
1350 Some(SyntaxContext::ImportPath(..)) => 'i',
1351 Some(SyntaxContext::IncludePath(..)) => 'I',
1352 Some(SyntaxContext::Label { .. }) => 'l',
1353 Some(SyntaxContext::Normal(..)) => 'n',
1354 None => ' ',
1355 }
1356 })
1357 }
1358
1359 #[test]
1360 fn test_get_syntax() {
1361 assert_snapshot!(map_syntax(r#"#let x = 1
1362Text
1363= Heading #let y = 2;
1364== Heading"#).trim(), @r"
1365 #let x = 1
1366 nnnnvvnnn
1367 Text
1368
1369 = Heading #let y = 2;
1370 nnnnvvnnn
1371 == Heading
1372 ");
1373 assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @r"
1374 #let f(x);
1375 nnnnv v
1376 ");
1377 assert_snapshot!(map_syntax(r#"#{
1378 calc.
1379}"#).trim(), @r"
1380 #{
1381 n
1382 calc.
1383 nnvvvvvnn
1384 }
1385 n
1386 ");
1387 }
1388
1389 #[test]
1390 fn test_get_context() {
1391 assert_snapshot!(map_context(r#"#let x = 1
1392Text
1393= Heading #let y = 2;
1394== Heading"#).trim(), @r"
1395 #let x = 1
1396 nnnnvvnnn
1397 Text
1398
1399 = Heading #let y = 2;
1400 nnnnvvnnn
1401 == Heading
1402 ");
1403 assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @r"
1404 #let f(x);
1405 nnnnv v
1406 ");
1407 assert_snapshot!(map_context(r#"#f(1, 2) Test"#).trim(), @r"
1408 #f(1, 2) Test
1409 vpppppp
1410 ");
1411 assert_snapshot!(map_context(r#"#() Test"#).trim(), @r"
1412 #() Test
1413 ee
1414 ");
1415 assert_snapshot!(map_context(r#"#(1) Test"#).trim(), @r"
1416 #(1) Test
1417 PPP
1418 ");
1419 assert_snapshot!(map_context(r#"#(a: 1) Test"#).trim(), @r"
1420 #(a: 1) Test
1421 eeeeee
1422 ");
1423 assert_snapshot!(map_context(r#"#(1, 2) Test"#).trim(), @r"
1424 #(1, 2) Test
1425 eeeeee
1426 ");
1427 assert_snapshot!(map_context(r#"#(1, 2)
1428 Test"#).trim(), @r"
1429 #(1, 2)
1430 eeeeee
1431 Test
1432 ");
1433 }
1434
1435 fn access_node(s: &str, cursor: i32) -> String {
1436 access_node_(s, cursor).unwrap_or_default()
1437 }
1438
1439 fn access_node_(s: &str, cursor: i32) -> Option<String> {
1440 access_var(s, cursor, |_source, var| {
1441 Some(var.accessed_node()?.get().clone().into_text().into())
1442 })
1443 }
1444
1445 fn access_field(s: &str, cursor: i32) -> String {
1446 access_field_(s, cursor).unwrap_or_default()
1447 }
1448
1449 fn access_field_(s: &str, cursor: i32) -> Option<String> {
1450 access_var(s, cursor, |source, var| {
1451 let field = var.accessing_field()?;
1452 Some(match field {
1453 FieldClass::Field(ident) => format!("Field: {}", ident.text()),
1454 FieldClass::DotSuffix(span_offset) => {
1455 let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1456 format!("DotSuffix: {offset:?}")
1457 }
1458 })
1459 })
1460 }
1461
1462 fn access_var(
1463 s: &str,
1464 cursor: i32,
1465 f: impl FnOnce(&Source, VarClass) -> Option<String>,
1466 ) -> Option<String> {
1467 let cursor = if cursor < 0 {
1468 s.len() as i32 + cursor
1469 } else {
1470 cursor
1471 };
1472 let source = Source::detached(s.to_owned());
1473 let root = LinkedNode::new(source.root());
1474 let node = root.leaf_at(cursor as usize, Side::Before)?;
1475 let syntax = classify_syntax(node, cursor as usize)?;
1476 let SyntaxClass::VarAccess(var) = syntax else {
1477 return None;
1478 };
1479 f(&source, var)
1480 }
1481
1482 #[test]
1483 fn test_access_field() {
1484 assert_snapshot!(access_field("#(a.b)", 5), @r"Field: b");
1485 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1486 assert_snapshot!(access_field("$a.$", 3), @"DotSuffix: 3");
1487 assert_snapshot!(access_field("#(a.)", 4), @"DotSuffix: 4");
1488 assert_snapshot!(access_node("#(a..b)", 4), @"a");
1489 assert_snapshot!(access_field("#(a..b)", 4), @"DotSuffix: 4");
1490 assert_snapshot!(access_node("#(a..b())", 4), @"a");
1491 assert_snapshot!(access_field("#(a..b())", 4), @"DotSuffix: 4");
1492 }
1493
1494 #[test]
1495 fn test_code_access() {
1496 assert_snapshot!(access_node("#{`a`.}", 6), @"`a`");
1497 assert_snapshot!(access_field("#{`a`.}", 6), @"DotSuffix: 6");
1498 assert_snapshot!(access_node("#{$a$.}", 6), @"$a$");
1499 assert_snapshot!(access_field("#{$a$.}", 6), @"DotSuffix: 6");
1500 assert_snapshot!(access_node("#{\"a\".}", 6), @"\"a\"");
1501 assert_snapshot!(access_field("#{\"a\".}", 6), @"DotSuffix: 6");
1502 assert_snapshot!(access_node("#{<a>.}", 6), @"<a>");
1503 assert_snapshot!(access_field("#{<a>.}", 6), @"DotSuffix: 6");
1504 }
1505
1506 #[test]
1507 fn test_markup_access() {
1508 assert_snapshot!(access_field("_a_.", 4), @"");
1509 assert_snapshot!(access_field("*a*.", 4), @"");
1510 assert_snapshot!(access_field("`a`.", 4), @"");
1511 assert_snapshot!(access_field("$a$.", 4), @"");
1512 assert_snapshot!(access_field("\"a\".", 4), @"");
1513 assert_snapshot!(access_field("@a.", 3), @"");
1514 assert_snapshot!(access_field("<a>.", 4), @"");
1515 }
1516
1517 #[test]
1518 fn test_hash_access() {
1519 assert_snapshot!(access_node("#a.", 3), @"a");
1520 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1521 assert_snapshot!(access_node("#(a).", 5), @"(a)");
1522 assert_snapshot!(access_field("#(a).", 5), @"DotSuffix: 5");
1523 assert_snapshot!(access_node("#`a`.", 5), @"`a`");
1524 assert_snapshot!(access_field("#`a`.", 5), @"DotSuffix: 5");
1525 assert_snapshot!(access_node("#$a$.", 5), @"$a$");
1526 assert_snapshot!(access_field("#$a$.", 5), @"DotSuffix: 5");
1527 assert_snapshot!(access_node("#(a,).", 6), @"(a,)");
1528 assert_snapshot!(access_field("#(a,).", 6), @"DotSuffix: 6");
1529 }
1530}