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 | SyntaxKind::MathText) {
751 let mode = interpret_mode_at(Some(&node));
752 if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
753 return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
754 }
755 }
756
757 let ancestor = first_ancestor_expr(node)?;
759 crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
760
761 let adjusted = adjust_expr(ancestor)?;
763 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
764
765 let expr = adjusted.cast::<ast::Expr>()?;
767 Some(match expr {
768 ast::Expr::Label(..) => SyntaxClass::label(adjusted),
769 ast::Expr::Ref(..) => SyntaxClass::Ref(adjusted),
770 ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
771 ast::Expr::Set(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
772 ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
773 SyntaxClass::VarAccess(VarClass::Ident(adjusted))
774 }
775 ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
776 ast::Expr::Str(..) => {
777 let parent = adjusted.parent()?;
778 if parent.kind() == SyntaxKind::ModuleImport {
779 SyntaxClass::ImportPath(adjusted)
780 } else if parent.kind() == SyntaxKind::ModuleInclude {
781 SyntaxClass::IncludePath(adjusted)
782 } else {
783 SyntaxClass::Normal(adjusted.kind(), adjusted)
784 }
785 }
786 _ if expr.hash()
787 || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
788 {
789 SyntaxClass::Normal(adjusted.kind(), adjusted)
790 }
791 _ => return None,
792 })
793}
794
795fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
798 !matches!(
799 interpret_mode_at_kind(kind),
800 Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
801 )
802}
803
804#[derive(Debug, Clone)]
806pub enum ArgClass<'a> {
807 Positional {
809 spreads: EcoVec<LinkedNode<'a>>,
811 positional: usize,
813 is_spread: bool,
815 },
816 Named(LinkedNode<'a>),
818}
819
820impl ArgClass<'_> {
821 pub fn first_positional() -> Self {
823 ArgClass::Positional {
824 spreads: EcoVec::new(),
825 positional: 0,
826 is_spread: false,
827 }
828 }
829}
830
831#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
834pub enum SurroundingSyntax {
835 Regular,
837 StringContent,
839 Selector,
841 ShowTransform,
843 ImportList,
845 SetRule,
847 ParamList,
849}
850
851pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
853 check_previous_syntax(node)
854 .or_else(|| check_surrounding_syntax(node))
855 .unwrap_or(SurroundingSyntax::Regular)
856}
857
858fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
859 use SurroundingSyntax::*;
860 let mut met_args = false;
861
862 if matches!(leaf.kind(), SyntaxKind::Str) {
863 return Some(StringContent);
864 }
865
866 while let Some(parent) = leaf.parent() {
867 crate::log_debug_ct!(
868 "check_surrounding_syntax: {:?}::{:?}",
869 parent.kind(),
870 leaf.kind()
871 );
872 match parent.kind() {
873 SyntaxKind::CodeBlock
874 | SyntaxKind::ContentBlock
875 | SyntaxKind::Equation
876 | SyntaxKind::Closure => {
877 return Some(Regular);
878 }
879 SyntaxKind::ImportItemPath
880 | SyntaxKind::ImportItems
881 | SyntaxKind::RenamedImportItem => {
882 return Some(ImportList);
883 }
884 SyntaxKind::ModuleImport => {
885 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
886 let Some(colon) = colon else {
887 return Some(Regular);
888 };
889
890 if leaf.offset() >= colon.offset() {
891 return Some(ImportList);
892 } else {
893 return Some(Regular);
894 }
895 }
896 SyntaxKind::Named => {
897 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
898 let Some(colon) = colon else {
899 return Some(Regular);
900 };
901
902 return if leaf.offset() >= colon.offset() {
903 Some(Regular)
904 } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
905 Some(ParamList)
906 } else {
907 Some(Regular)
908 };
909 }
910 SyntaxKind::Params => {
911 return Some(ParamList);
912 }
913 SyntaxKind::Args => {
914 met_args = true;
915 }
916 SyntaxKind::SetRule => {
917 let rule = parent.get().cast::<ast::SetRule>()?;
918 if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
919 return Some(Regular);
920 } else {
921 return Some(SetRule);
922 }
923 }
924 SyntaxKind::ShowRule => {
925 if met_args {
926 return Some(Regular);
927 }
928
929 let rule = parent.get().cast::<ast::ShowRule>()?;
930 let colon = rule
931 .to_untyped()
932 .children()
933 .find(|s| s.kind() == SyntaxKind::Colon);
934 let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
935 return Some(Selector);
937 };
938
939 if leaf.offset() >= colon.offset() {
940 return Some(ShowTransform);
941 } else {
942 return Some(Selector); }
944 }
945 _ => {}
946 }
947
948 leaf = parent;
949 }
950
951 None
952}
953
954fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
955 let mut leaf = leaf.clone();
956 if leaf.kind().is_trivia() {
957 leaf = leaf.prev_sibling()?;
958 }
959 if matches!(
960 leaf.kind(),
961 SyntaxKind::ShowRule
962 | SyntaxKind::SetRule
963 | SyntaxKind::ModuleImport
964 | SyntaxKind::ModuleInclude
965 ) {
966 return check_surrounding_syntax(&leaf.rightmost_leaf()?);
967 }
968
969 if matches!(leaf.kind(), SyntaxKind::Show) {
970 return Some(SurroundingSyntax::Selector);
971 }
972 if matches!(leaf.kind(), SyntaxKind::Set) {
973 return Some(SurroundingSyntax::SetRule);
974 }
975
976 None
977}
978
979fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
980 s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
981}
982
983#[derive(Debug, Clone)]
991pub enum SyntaxContext<'a> {
992 Arg {
994 callee: LinkedNode<'a>,
996 args: LinkedNode<'a>,
998 target: ArgClass<'a>,
1000 is_set: bool,
1002 },
1003 Element {
1005 container: LinkedNode<'a>,
1007 target: ArgClass<'a>,
1009 },
1010 Paren {
1012 container: LinkedNode<'a>,
1014 is_before: bool,
1017 },
1018 VarAccess(VarClass<'a>),
1022 ImportPath(LinkedNode<'a>),
1024 IncludePath(LinkedNode<'a>),
1026 Label {
1028 node: LinkedNode<'a>,
1030 is_error: bool,
1032 },
1033 Normal(LinkedNode<'a>),
1035}
1036
1037impl<'a> SyntaxContext<'a> {
1038 pub fn node(&self) -> Option<LinkedNode<'a>> {
1040 Some(match self {
1041 SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1042 match target {
1043 ArgClass::Positional { .. } => return None,
1044 ArgClass::Named(node) => node.clone(),
1045 }
1046 }
1047 SyntaxContext::VarAccess(cls) => cls.node().clone(),
1048 SyntaxContext::Paren { container, .. } => container.clone(),
1049 SyntaxContext::Label { node, .. }
1050 | SyntaxContext::ImportPath(node)
1051 | SyntaxContext::IncludePath(node)
1052 | SyntaxContext::Normal(node) => node.clone(),
1053 })
1054 }
1055}
1056
1057#[derive(Debug)]
1059enum ArgSourceKind {
1060 Call,
1062 Array,
1064 Dict,
1066}
1067
1068pub fn classify_context_outer<'a>(
1071 outer: LinkedNode<'a>,
1072 node: LinkedNode<'a>,
1073) -> Option<SyntaxContext<'a>> {
1074 use SyntaxClass::*;
1075 let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1076 let node_syntax = classify_syntax(node.clone(), node.offset())?;
1077
1078 match context_syntax {
1079 Callee(callee)
1080 if matches!(node_syntax, Normal(..) | Label { .. } | Ref(..))
1081 && !matches!(node_syntax, Callee(..)) =>
1082 {
1083 let parent = callee.parent()?;
1084 let args = match parent.cast::<ast::Expr>() {
1085 Some(ast::Expr::FuncCall(call)) => call.args(),
1086 Some(ast::Expr::Set(set)) => set.args(),
1087 _ => return None,
1088 };
1089 let args = parent.find(args.span())?;
1090
1091 let is_set = parent.kind() == SyntaxKind::SetRule;
1092 let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1093 Some(SyntaxContext::Arg {
1094 callee,
1095 args,
1096 target: arg_target,
1097 is_set,
1098 })
1099 }
1100 _ => None,
1101 }
1102}
1103
1104pub fn classify_context(node: LinkedNode, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1107 let mut node = node;
1108 if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1109 loop {
1110 node = node.prev_sibling()?;
1111
1112 if !node.kind().is_trivia() {
1113 break;
1114 }
1115 }
1116 }
1117
1118 let cursor = cursor.unwrap_or_else(|| node.offset());
1119 let syntax = classify_syntax(node.clone(), cursor)?;
1120
1121 let normal_syntax = match syntax {
1122 SyntaxClass::Callee(callee) => {
1123 return callee_context(callee, node);
1124 }
1125 SyntaxClass::Label { node, is_error } => {
1126 return Some(SyntaxContext::Label { node, is_error });
1127 }
1128 SyntaxClass::ImportPath(node) => {
1129 return Some(SyntaxContext::ImportPath(node));
1130 }
1131 SyntaxClass::IncludePath(node) => {
1132 return Some(SyntaxContext::IncludePath(node));
1133 }
1134 syntax => syntax,
1135 };
1136
1137 let Some(mut node_parent) = node.parent().cloned() else {
1138 return Some(SyntaxContext::Normal(node));
1139 };
1140
1141 while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1142 let Some(parent) = node_parent.parent() else {
1143 return Some(SyntaxContext::Normal(node));
1144 };
1145 node_parent = parent.clone();
1146 }
1147
1148 match node_parent.kind() {
1149 SyntaxKind::Args => {
1150 let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1151 let span = match ancestor.cast::<ast::Expr>()? {
1152 ast::Expr::FuncCall(call) => call.callee().span(),
1153 ast::Expr::Set(set) => set.target().span(),
1154 _ => return None,
1155 };
1156 ancestor.find(span)
1157 })?;
1158
1159 let param_node = match node.kind() {
1160 SyntaxKind::Ident
1161 if matches!(
1162 node.parent_kind().zip(node.next_sibling_kind()),
1163 Some((SyntaxKind::Named, SyntaxKind::Colon))
1164 ) =>
1165 {
1166 node
1167 }
1168 _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1169 node.parent().cloned()?
1170 }
1171 _ => node,
1172 };
1173
1174 callee_context(callee, param_node)
1175 }
1176 SyntaxKind::Array | SyntaxKind::Dict => {
1177 let element_target = arg_context(
1178 node_parent.clone(),
1179 node.clone(),
1180 match node_parent.kind() {
1181 SyntaxKind::Array => ArgSourceKind::Array,
1182 SyntaxKind::Dict => ArgSourceKind::Dict,
1183 _ => unreachable!(),
1184 },
1185 )?;
1186 Some(SyntaxContext::Element {
1187 container: node_parent.clone(),
1188 target: element_target,
1189 })
1190 }
1191 SyntaxKind::Parenthesized => {
1192 let is_before = node.offset() <= node_parent.offset() + 1;
1193 Some(SyntaxContext::Paren {
1194 container: node_parent.clone(),
1195 is_before,
1196 })
1197 }
1198 _ => Some(match normal_syntax {
1199 SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1200 normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1201 }),
1202 }
1203}
1204
1205fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
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 target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1216 Some(SyntaxContext::Arg {
1217 callee,
1218 args,
1219 target,
1220 is_set,
1221 })
1222}
1223
1224fn arg_context<'a>(
1225 args_node: LinkedNode<'a>,
1226 mut node: LinkedNode<'a>,
1227 param_kind: ArgSourceKind,
1228) -> Option<ArgClass<'a>> {
1229 if node.kind() == SyntaxKind::RightParen {
1230 node = node.prev_sibling()?;
1231 }
1232 match node.kind() {
1233 SyntaxKind::Named => {
1234 let param_ident = node.cast::<ast::Named>()?.name();
1235 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1236 }
1237 SyntaxKind::Colon => {
1238 let prev = node.prev_leaf()?;
1239 let param_ident = prev.cast::<ast::Ident>()?;
1240 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1241 }
1242 _ => {
1243 let parent = node.parent();
1244 if let Some(parent) = parent {
1245 if parent.kind() == SyntaxKind::Named {
1246 let param_ident = parent.cast::<ast::Named>()?;
1247 let name = param_ident.name();
1248 let init = param_ident.expr();
1249 let init = parent.find(init.span())?;
1250 if init.range().contains(&node.offset()) {
1251 let name = args_node.find(name.span())?;
1252 return Some(ArgClass::Named(name));
1253 }
1254 }
1255 }
1256
1257 let mut spreads = EcoVec::new();
1258 let mut positional = 0;
1259 let is_spread = node.kind() == SyntaxKind::Spread;
1260
1261 let args_before = args_node
1262 .children()
1263 .take_while(|arg| arg.range().end <= node.offset());
1264 match param_kind {
1265 ArgSourceKind::Call => {
1266 for ch in args_before {
1267 match ch.cast::<ast::Arg>() {
1268 Some(ast::Arg::Pos(..)) => {
1269 positional += 1;
1270 }
1271 Some(ast::Arg::Spread(..)) => {
1272 spreads.push(ch);
1273 }
1274 Some(ast::Arg::Named(..)) | None => {}
1275 }
1276 }
1277 }
1278 ArgSourceKind::Array => {
1279 for ch in args_before {
1280 match ch.cast::<ast::ArrayItem>() {
1281 Some(ast::ArrayItem::Pos(..)) => {
1282 positional += 1;
1283 }
1284 Some(ast::ArrayItem::Spread(..)) => {
1285 spreads.push(ch);
1286 }
1287 _ => {}
1288 }
1289 }
1290 }
1291 ArgSourceKind::Dict => {
1292 for ch in args_before {
1293 if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1294 spreads.push(ch);
1295 }
1296 }
1297 }
1298 }
1299
1300 Some(ArgClass::Positional {
1301 spreads,
1302 positional,
1303 is_spread,
1304 })
1305 }
1306 }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use super::*;
1312 use insta::assert_snapshot;
1313 use typst::syntax::{is_newline, Side, Source};
1314
1315 fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1316 let source = Source::detached(source.to_owned());
1317 let root = LinkedNode::new(source.root());
1318 let mut output_mapping = String::new();
1319
1320 let mut cursor = 0;
1321 for ch in source.text().chars() {
1322 cursor += ch.len_utf8();
1323 if is_newline(ch) {
1324 output_mapping.push(ch);
1325 continue;
1326 }
1327
1328 output_mapping.push(mapper(&root, cursor));
1329 }
1330
1331 source
1332 .text()
1333 .lines()
1334 .zip(output_mapping.lines())
1335 .flat_map(|(a, b)| [a, "\n", b, "\n"])
1336 .collect::<String>()
1337 }
1338
1339 fn map_syntax(source: &str) -> String {
1340 map_node(source, |root, cursor| {
1341 let node = root.leaf_at(cursor, Side::Before);
1342 let kind = node.and_then(|node| classify_syntax(node, cursor));
1343 match kind {
1344 Some(SyntaxClass::VarAccess(..)) => 'v',
1345 Some(SyntaxClass::Normal(..)) => 'n',
1346 Some(SyntaxClass::Label { .. }) => 'l',
1347 Some(SyntaxClass::Ref(..)) => 'r',
1348 Some(SyntaxClass::Callee(..)) => 'c',
1349 Some(SyntaxClass::ImportPath(..)) => 'i',
1350 Some(SyntaxClass::IncludePath(..)) => 'I',
1351 None => ' ',
1352 }
1353 })
1354 }
1355
1356 fn map_context(source: &str) -> String {
1357 map_node(source, |root, cursor| {
1358 let node = root.leaf_at(cursor, Side::Before);
1359 let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1360 match kind {
1361 Some(SyntaxContext::Arg { .. }) => 'p',
1362 Some(SyntaxContext::Element { .. }) => 'e',
1363 Some(SyntaxContext::Paren { .. }) => 'P',
1364 Some(SyntaxContext::VarAccess { .. }) => 'v',
1365 Some(SyntaxContext::ImportPath(..)) => 'i',
1366 Some(SyntaxContext::IncludePath(..)) => 'I',
1367 Some(SyntaxContext::Label { .. }) => 'l',
1368 Some(SyntaxContext::Normal(..)) => 'n',
1369 None => ' ',
1370 }
1371 })
1372 }
1373
1374 #[test]
1375 fn test_get_syntax() {
1376 assert_snapshot!(map_syntax(r#"#let x = 1
1377Text
1378= Heading #let y = 2;
1379== Heading"#).trim(), @r"
1380 #let x = 1
1381 nnnnvvnnn
1382 Text
1383
1384 = Heading #let y = 2;
1385 nnnnvvnnn
1386 == Heading
1387 ");
1388 assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @r"
1389 #let f(x);
1390 nnnnv v
1391 ");
1392 assert_snapshot!(map_syntax(r#"#{
1393 calc.
1394}"#).trim(), @r"
1395 #{
1396 n
1397 calc.
1398 nnvvvvvnn
1399 }
1400 n
1401 ");
1402 }
1403
1404 #[test]
1405 fn test_get_context() {
1406 assert_snapshot!(map_context(r#"#let x = 1
1407Text
1408= Heading #let y = 2;
1409== Heading"#).trim(), @r"
1410 #let x = 1
1411 nnnnvvnnn
1412 Text
1413
1414 = Heading #let y = 2;
1415 nnnnvvnnn
1416 == Heading
1417 ");
1418 assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @r"
1419 #let f(x);
1420 nnnnv v
1421 ");
1422 assert_snapshot!(map_context(r#"#f(1, 2) Test"#).trim(), @r"
1423 #f(1, 2) Test
1424 vpppppp
1425 ");
1426 assert_snapshot!(map_context(r#"#() Test"#).trim(), @r"
1427 #() Test
1428 ee
1429 ");
1430 assert_snapshot!(map_context(r#"#(1) Test"#).trim(), @r"
1431 #(1) Test
1432 PPP
1433 ");
1434 assert_snapshot!(map_context(r#"#(a: 1) Test"#).trim(), @r"
1435 #(a: 1) Test
1436 eeeeee
1437 ");
1438 assert_snapshot!(map_context(r#"#(1, 2) Test"#).trim(), @r"
1439 #(1, 2) Test
1440 eeeeee
1441 ");
1442 assert_snapshot!(map_context(r#"#(1, 2)
1443 Test"#).trim(), @r"
1444 #(1, 2)
1445 eeeeee
1446 Test
1447 ");
1448 }
1449
1450 fn access_node(s: &str, cursor: i32) -> String {
1451 access_node_(s, cursor).unwrap_or_default()
1452 }
1453
1454 fn access_node_(s: &str, cursor: i32) -> Option<String> {
1455 access_var(s, cursor, |_source, var| {
1456 Some(var.accessed_node()?.get().clone().into_text().into())
1457 })
1458 }
1459
1460 fn access_field(s: &str, cursor: i32) -> String {
1461 access_field_(s, cursor).unwrap_or_default()
1462 }
1463
1464 fn access_field_(s: &str, cursor: i32) -> Option<String> {
1465 access_var(s, cursor, |source, var| {
1466 let field = var.accessing_field()?;
1467 Some(match field {
1468 FieldClass::Field(ident) => format!("Field: {}", ident.text()),
1469 FieldClass::DotSuffix(span_offset) => {
1470 let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1471 format!("DotSuffix: {offset:?}")
1472 }
1473 })
1474 })
1475 }
1476
1477 fn access_var(
1478 s: &str,
1479 cursor: i32,
1480 f: impl FnOnce(&Source, VarClass) -> Option<String>,
1481 ) -> Option<String> {
1482 let cursor = if cursor < 0 {
1483 s.len() as i32 + cursor
1484 } else {
1485 cursor
1486 };
1487 let source = Source::detached(s.to_owned());
1488 let root = LinkedNode::new(source.root());
1489 let node = root.leaf_at(cursor as usize, Side::Before)?;
1490 let syntax = classify_syntax(node, cursor as usize)?;
1491 let SyntaxClass::VarAccess(var) = syntax else {
1492 return None;
1493 };
1494 f(&source, var)
1495 }
1496
1497 #[test]
1498 fn test_access_field() {
1499 assert_snapshot!(access_field("#(a.b)", 5), @r"Field: b");
1500 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1501 assert_snapshot!(access_field("$a.$", 3), @"DotSuffix: 3");
1502 assert_snapshot!(access_field("#(a.)", 4), @"DotSuffix: 4");
1503 assert_snapshot!(access_node("#(a..b)", 4), @"a");
1504 assert_snapshot!(access_field("#(a..b)", 4), @"DotSuffix: 4");
1505 assert_snapshot!(access_node("#(a..b())", 4), @"a");
1506 assert_snapshot!(access_field("#(a..b())", 4), @"DotSuffix: 4");
1507 }
1508
1509 #[test]
1510 fn test_code_access() {
1511 assert_snapshot!(access_node("#{`a`.}", 6), @"`a`");
1512 assert_snapshot!(access_field("#{`a`.}", 6), @"DotSuffix: 6");
1513 assert_snapshot!(access_node("#{$a$.}", 6), @"$a$");
1514 assert_snapshot!(access_field("#{$a$.}", 6), @"DotSuffix: 6");
1515 assert_snapshot!(access_node("#{\"a\".}", 6), @"\"a\"");
1516 assert_snapshot!(access_field("#{\"a\".}", 6), @"DotSuffix: 6");
1517 assert_snapshot!(access_node("#{<a>.}", 6), @"<a>");
1518 assert_snapshot!(access_field("#{<a>.}", 6), @"DotSuffix: 6");
1519 }
1520
1521 #[test]
1522 fn test_markup_access() {
1523 assert_snapshot!(access_field("_a_.", 4), @"");
1524 assert_snapshot!(access_field("*a*.", 4), @"");
1525 assert_snapshot!(access_field("`a`.", 4), @"");
1526 assert_snapshot!(access_field("$a$.", 4), @"");
1527 assert_snapshot!(access_field("\"a\".", 4), @"");
1528 assert_snapshot!(access_field("@a.", 3), @"");
1529 assert_snapshot!(access_field("<a>.", 4), @"");
1530 }
1531
1532 #[test]
1533 fn test_hash_access() {
1534 assert_snapshot!(access_node("#a.", 3), @"a");
1535 assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1536 assert_snapshot!(access_node("#(a).", 5), @"(a)");
1537 assert_snapshot!(access_field("#(a).", 5), @"DotSuffix: 5");
1538 assert_snapshot!(access_node("#`a`.", 5), @"`a`");
1539 assert_snapshot!(access_field("#`a`.", 5), @"DotSuffix: 5");
1540 assert_snapshot!(access_node("#$a$.", 5), @"$a$");
1541 assert_snapshot!(access_field("#$a$.", 5), @"DotSuffix: 5");
1542 assert_snapshot!(access_node("#(a,).", 6), @"(a,)");
1543 assert_snapshot!(access_field("#(a,).", 6), @"DotSuffix: 6");
1544 }
1545}