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 if node.offset() + 1 == cursor && {
706 matches!(node.kind(), SyntaxKind::Dot)
708 || (matches!(
709 node.kind(),
710 SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
711 ) && node.text().starts_with("."))
712 } {
713 let dot_target = node.clone().prev_leaf().and_then(first_ancestor_expr);
714
715 if let Some(dot_target) = dot_target {
716 return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
717 }
718 }
719
720 if node.offset() + 1 == cursor && matches!(node.kind(), SyntaxKind::Dots) {
721 let dot_target = node.parent()?;
722 if dot_target.kind() == SyntaxKind::Spread {
723 let dot_target = dot_target.prev_leaf().and_then(first_ancestor_expr);
724
725 if let Some(dot_target) = dot_target {
726 return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
727 }
728 }
729 }
730
731 if matches!(node.kind(), SyntaxKind::Text) {
732 let mode = interpret_mode_at(Some(&node));
733 if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
734 return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
735 }
736 }
737
738 let ancestor = first_ancestor_expr(node)?;
740 crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
741
742 let adjusted = adjust_expr(ancestor)?;
744 crate::log_debug_ct!("adjust_expr: {adjusted:?}");
745
746 let expr = adjusted.cast::<ast::Expr>()?;
748 Some(match expr {
749 ast::Expr::Label(..) => SyntaxClass::label(adjusted),
750 ast::Expr::Ref(..) => SyntaxClass::Ref(adjusted),
751 ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
752 ast::Expr::Set(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
753 ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
754 SyntaxClass::VarAccess(VarClass::Ident(adjusted))
755 }
756 ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
757 ast::Expr::Str(..) => {
758 let parent = adjusted.parent()?;
759 if parent.kind() == SyntaxKind::ModuleImport {
760 SyntaxClass::ImportPath(adjusted)
761 } else if parent.kind() == SyntaxKind::ModuleInclude {
762 SyntaxClass::IncludePath(adjusted)
763 } else {
764 SyntaxClass::Normal(adjusted.kind(), adjusted)
765 }
766 }
767 _ if expr.hash()
768 || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
769 {
770 SyntaxClass::Normal(adjusted.kind(), adjusted)
771 }
772 _ => return None,
773 })
774}
775
776fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
779 !matches!(
780 interpret_mode_at_kind(kind),
781 Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
782 )
783}
784
785#[derive(Debug, Clone)]
787pub enum ArgClass<'a> {
788 Positional {
790 spreads: EcoVec<LinkedNode<'a>>,
792 positional: usize,
794 is_spread: bool,
796 },
797 Named(LinkedNode<'a>),
799}
800
801impl ArgClass<'_> {
802 pub fn first_positional() -> Self {
804 ArgClass::Positional {
805 spreads: EcoVec::new(),
806 positional: 0,
807 is_spread: false,
808 }
809 }
810}
811
812#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
815pub enum SurroundingSyntax {
816 Regular,
818 StringContent,
820 Selector,
822 ShowTransform,
824 ImportList,
826 SetRule,
828 ParamList,
830}
831
832pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
834 check_previous_syntax(node)
835 .or_else(|| check_surrounding_syntax(node))
836 .unwrap_or(SurroundingSyntax::Regular)
837}
838
839fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
840 use SurroundingSyntax::*;
841 let mut met_args = false;
842
843 if matches!(leaf.kind(), SyntaxKind::Str) {
844 return Some(StringContent);
845 }
846
847 while let Some(parent) = leaf.parent() {
848 crate::log_debug_ct!(
849 "check_surrounding_syntax: {:?}::{:?}",
850 parent.kind(),
851 leaf.kind()
852 );
853 match parent.kind() {
854 SyntaxKind::CodeBlock
855 | SyntaxKind::ContentBlock
856 | SyntaxKind::Equation
857 | SyntaxKind::Closure => {
858 return Some(Regular);
859 }
860 SyntaxKind::ImportItemPath
861 | SyntaxKind::ImportItems
862 | SyntaxKind::RenamedImportItem => {
863 return Some(ImportList);
864 }
865 SyntaxKind::ModuleImport => {
866 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
867 let Some(colon) = colon else {
868 return Some(Regular);
869 };
870
871 if leaf.offset() >= colon.offset() {
872 return Some(ImportList);
873 } else {
874 return Some(Regular);
875 }
876 }
877 SyntaxKind::Named => {
878 let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
879 let Some(colon) = colon else {
880 return Some(Regular);
881 };
882
883 return if leaf.offset() >= colon.offset() {
884 Some(Regular)
885 } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
886 Some(ParamList)
887 } else {
888 Some(Regular)
889 };
890 }
891 SyntaxKind::Params => {
892 return Some(ParamList);
893 }
894 SyntaxKind::Args => {
895 met_args = true;
896 }
897 SyntaxKind::SetRule => {
898 let rule = parent.get().cast::<ast::SetRule>()?;
899 if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
900 return Some(Regular);
901 } else {
902 return Some(SetRule);
903 }
904 }
905 SyntaxKind::ShowRule => {
906 if met_args {
907 return Some(Regular);
908 }
909
910 let rule = parent.get().cast::<ast::ShowRule>()?;
911 let colon = rule
912 .to_untyped()
913 .children()
914 .find(|s| s.kind() == SyntaxKind::Colon);
915 let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
916 return Some(Selector);
918 };
919
920 if leaf.offset() >= colon.offset() {
921 return Some(ShowTransform);
922 } else {
923 return Some(Selector); }
925 }
926 _ => {}
927 }
928
929 leaf = parent;
930 }
931
932 None
933}
934
935fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
936 let mut leaf = leaf.clone();
937 if leaf.kind().is_trivia() {
938 leaf = leaf.prev_sibling()?;
939 }
940 if matches!(
941 leaf.kind(),
942 SyntaxKind::ShowRule
943 | SyntaxKind::SetRule
944 | SyntaxKind::ModuleImport
945 | SyntaxKind::ModuleInclude
946 ) {
947 return check_surrounding_syntax(&leaf.rightmost_leaf()?);
948 }
949
950 if matches!(leaf.kind(), SyntaxKind::Show) {
951 return Some(SurroundingSyntax::Selector);
952 }
953 if matches!(leaf.kind(), SyntaxKind::Set) {
954 return Some(SurroundingSyntax::SetRule);
955 }
956
957 None
958}
959
960fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
961 s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
962}
963
964#[derive(Debug, Clone)]
972pub enum SyntaxContext<'a> {
973 Arg {
975 callee: LinkedNode<'a>,
977 args: LinkedNode<'a>,
979 target: ArgClass<'a>,
981 is_set: bool,
983 },
984 Element {
986 container: LinkedNode<'a>,
988 target: ArgClass<'a>,
990 },
991 Paren {
993 container: LinkedNode<'a>,
995 is_before: bool,
998 },
999 VarAccess(VarClass<'a>),
1003 ImportPath(LinkedNode<'a>),
1005 IncludePath(LinkedNode<'a>),
1007 Label {
1009 node: LinkedNode<'a>,
1011 is_error: bool,
1013 },
1014 Normal(LinkedNode<'a>),
1016}
1017
1018impl<'a> SyntaxContext<'a> {
1019 pub fn node(&self) -> Option<LinkedNode<'a>> {
1021 Some(match self {
1022 SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1023 match target {
1024 ArgClass::Positional { .. } => return None,
1025 ArgClass::Named(node) => node.clone(),
1026 }
1027 }
1028 SyntaxContext::VarAccess(cls) => cls.node().clone(),
1029 SyntaxContext::Paren { container, .. } => container.clone(),
1030 SyntaxContext::Label { node, .. }
1031 | SyntaxContext::ImportPath(node)
1032 | SyntaxContext::IncludePath(node)
1033 | SyntaxContext::Normal(node) => node.clone(),
1034 })
1035 }
1036}
1037
1038#[derive(Debug)]
1040enum ArgSourceKind {
1041 Call,
1043 Array,
1045 Dict,
1047}
1048
1049pub fn classify_context_outer<'a>(
1052 outer: LinkedNode<'a>,
1053 node: LinkedNode<'a>,
1054) -> Option<SyntaxContext<'a>> {
1055 use SyntaxClass::*;
1056 let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1057 let node_syntax = classify_syntax(node.clone(), node.offset())?;
1058
1059 match context_syntax {
1060 Callee(callee)
1061 if matches!(node_syntax, Normal(..) | Label { .. } | Ref(..))
1062 && !matches!(node_syntax, Callee(..)) =>
1063 {
1064 let parent = callee.parent()?;
1065 let args = match parent.cast::<ast::Expr>() {
1066 Some(ast::Expr::FuncCall(call)) => call.args(),
1067 Some(ast::Expr::Set(set)) => set.args(),
1068 _ => return None,
1069 };
1070 let args = parent.find(args.span())?;
1071
1072 let is_set = parent.kind() == SyntaxKind::SetRule;
1073 let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1074 Some(SyntaxContext::Arg {
1075 callee,
1076 args,
1077 target: arg_target,
1078 is_set,
1079 })
1080 }
1081 _ => None,
1082 }
1083}
1084
1085pub fn classify_context(node: LinkedNode, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1088 let mut node = node;
1089 if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1090 loop {
1091 node = node.prev_sibling()?;
1092
1093 if !node.kind().is_trivia() {
1094 break;
1095 }
1096 }
1097 }
1098
1099 let cursor = cursor.unwrap_or_else(|| node.offset());
1100 let syntax = classify_syntax(node.clone(), cursor)?;
1101
1102 let normal_syntax = match syntax {
1103 SyntaxClass::Callee(callee) => {
1104 return callee_context(callee, node);
1105 }
1106 SyntaxClass::Label { node, is_error } => {
1107 return Some(SyntaxContext::Label { node, is_error });
1108 }
1109 SyntaxClass::ImportPath(node) => {
1110 return Some(SyntaxContext::ImportPath(node));
1111 }
1112 SyntaxClass::IncludePath(node) => {
1113 return Some(SyntaxContext::IncludePath(node));
1114 }
1115 syntax => syntax,
1116 };
1117
1118 let Some(mut node_parent) = node.parent().cloned() else {
1119 return Some(SyntaxContext::Normal(node));
1120 };
1121
1122 while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1123 let Some(parent) = node_parent.parent() else {
1124 return Some(SyntaxContext::Normal(node));
1125 };
1126 node_parent = parent.clone();
1127 }
1128
1129 match node_parent.kind() {
1130 SyntaxKind::Args => {
1131 let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1132 let span = match ancestor.cast::<ast::Expr>()? {
1133 ast::Expr::FuncCall(call) => call.callee().span(),
1134 ast::Expr::Set(set) => set.target().span(),
1135 _ => return None,
1136 };
1137 ancestor.find(span)
1138 })?;
1139
1140 let param_node = match node.kind() {
1141 SyntaxKind::Ident
1142 if matches!(
1143 node.parent_kind().zip(node.next_sibling_kind()),
1144 Some((SyntaxKind::Named, SyntaxKind::Colon))
1145 ) =>
1146 {
1147 node
1148 }
1149 _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1150 node.parent().cloned()?
1151 }
1152 _ => node,
1153 };
1154
1155 callee_context(callee, param_node)
1156 }
1157 SyntaxKind::Array | SyntaxKind::Dict => {
1158 let element_target = arg_context(
1159 node_parent.clone(),
1160 node.clone(),
1161 match node_parent.kind() {
1162 SyntaxKind::Array => ArgSourceKind::Array,
1163 SyntaxKind::Dict => ArgSourceKind::Dict,
1164 _ => unreachable!(),
1165 },
1166 )?;
1167 Some(SyntaxContext::Element {
1168 container: node_parent.clone(),
1169 target: element_target,
1170 })
1171 }
1172 SyntaxKind::Parenthesized => {
1173 let is_before = node.offset() <= node_parent.offset() + 1;
1174 Some(SyntaxContext::Paren {
1175 container: node_parent.clone(),
1176 is_before,
1177 })
1178 }
1179 _ => Some(match normal_syntax {
1180 SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1181 normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1182 }),
1183 }
1184}
1185
1186fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
1187 let parent = callee.parent()?;
1188 let args = match parent.cast::<ast::Expr>() {
1189 Some(ast::Expr::FuncCall(call)) => call.args(),
1190 Some(ast::Expr::Set(set)) => set.args(),
1191 _ => return None,
1192 };
1193 let args = parent.find(args.span())?;
1194
1195 let is_set = parent.kind() == SyntaxKind::SetRule;
1196 let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1197 Some(SyntaxContext::Arg {
1198 callee,
1199 args,
1200 target,
1201 is_set,
1202 })
1203}
1204
1205fn arg_context<'a>(
1206 args_node: LinkedNode<'a>,
1207 mut node: LinkedNode<'a>,
1208 param_kind: ArgSourceKind,
1209) -> Option<ArgClass<'a>> {
1210 if node.kind() == SyntaxKind::RightParen {
1211 node = node.prev_sibling()?;
1212 }
1213 match node.kind() {
1214 SyntaxKind::Named => {
1215 let param_ident = node.cast::<ast::Named>()?.name();
1216 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1217 }
1218 SyntaxKind::Colon => {
1219 let prev = node.prev_leaf()?;
1220 let param_ident = prev.cast::<ast::Ident>()?;
1221 Some(ArgClass::Named(args_node.find(param_ident.span())?))
1222 }
1223 _ => {
1224 let mut spreads = EcoVec::new();
1225 let mut positional = 0;
1226 let is_spread = node.kind() == SyntaxKind::Spread;
1227
1228 let args_before = args_node
1229 .children()
1230 .take_while(|arg| arg.range().end <= node.offset());
1231 match param_kind {
1232 ArgSourceKind::Call => {
1233 for ch in args_before {
1234 match ch.cast::<ast::Arg>() {
1235 Some(ast::Arg::Pos(..)) => {
1236 positional += 1;
1237 }
1238 Some(ast::Arg::Spread(..)) => {
1239 spreads.push(ch);
1240 }
1241 Some(ast::Arg::Named(..)) | None => {}
1242 }
1243 }
1244 }
1245 ArgSourceKind::Array => {
1246 for ch in args_before {
1247 match ch.cast::<ast::ArrayItem>() {
1248 Some(ast::ArrayItem::Pos(..)) => {
1249 positional += 1;
1250 }
1251 Some(ast::ArrayItem::Spread(..)) => {
1252 spreads.push(ch);
1253 }
1254 _ => {}
1255 }
1256 }
1257 }
1258 ArgSourceKind::Dict => {
1259 for ch in args_before {
1260 if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1261 spreads.push(ch);
1262 }
1263 }
1264 }
1265 }
1266
1267 Some(ArgClass::Positional {
1268 spreads,
1269 positional,
1270 is_spread,
1271 })
1272 }
1273 }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279 use insta::assert_snapshot;
1280 use typst::syntax::{is_newline, Side, Source};
1281
1282 fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1283 let source = Source::detached(source.to_owned());
1284 let root = LinkedNode::new(source.root());
1285 let mut output_mapping = String::new();
1286
1287 let mut cursor = 0;
1288 for ch in source.text().chars() {
1289 cursor += ch.len_utf8();
1290 if is_newline(ch) {
1291 output_mapping.push(ch);
1292 continue;
1293 }
1294
1295 output_mapping.push(mapper(&root, cursor));
1296 }
1297
1298 source
1299 .text()
1300 .lines()
1301 .zip(output_mapping.lines())
1302 .flat_map(|(a, b)| [a, "\n", b, "\n"])
1303 .collect::<String>()
1304 }
1305
1306 fn map_syntax(source: &str) -> String {
1307 map_node(source, |root, cursor| {
1308 let node = root.leaf_at(cursor, Side::Before);
1309 let kind = node.and_then(|node| classify_syntax(node, cursor));
1310 match kind {
1311 Some(SyntaxClass::VarAccess(..)) => 'v',
1312 Some(SyntaxClass::Normal(..)) => 'n',
1313 Some(SyntaxClass::Label { .. }) => 'l',
1314 Some(SyntaxClass::Ref(..)) => 'r',
1315 Some(SyntaxClass::Callee(..)) => 'c',
1316 Some(SyntaxClass::ImportPath(..)) => 'i',
1317 Some(SyntaxClass::IncludePath(..)) => 'I',
1318 None => ' ',
1319 }
1320 })
1321 }
1322
1323 fn map_context(source: &str) -> String {
1324 map_node(source, |root, cursor| {
1325 let node = root.leaf_at(cursor, Side::Before);
1326 let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1327 match kind {
1328 Some(SyntaxContext::Arg { .. }) => 'p',
1329 Some(SyntaxContext::Element { .. }) => 'e',
1330 Some(SyntaxContext::Paren { .. }) => 'P',
1331 Some(SyntaxContext::VarAccess { .. }) => 'v',
1332 Some(SyntaxContext::ImportPath(..)) => 'i',
1333 Some(SyntaxContext::IncludePath(..)) => 'I',
1334 Some(SyntaxContext::Label { .. }) => 'l',
1335 Some(SyntaxContext::Normal(..)) => 'n',
1336 None => ' ',
1337 }
1338 })
1339 }
1340
1341 #[test]
1342 fn test_get_syntax() {
1343 assert_snapshot!(map_syntax(r#"#let x = 1
1344Text
1345= Heading #let y = 2;
1346== Heading"#).trim(), @r"
1347 #let x = 1
1348 nnnnvvnnn
1349 Text
1350
1351 = Heading #let y = 2;
1352 nnnnvvnnn
1353 == Heading
1354 ");
1355 assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @r"
1356 #let f(x);
1357 nnnnv v
1358 ");
1359 assert_snapshot!(map_syntax(r#"#{
1360 calc.
1361}"#).trim(), @r"
1362 #{
1363 n
1364 calc.
1365 nnvvvvvnn
1366 }
1367 n
1368 ");
1369 }
1370
1371 #[test]
1372 fn test_get_context() {
1373 assert_snapshot!(map_context(r#"#let x = 1
1374Text
1375= Heading #let y = 2;
1376== Heading"#).trim(), @r"
1377 #let x = 1
1378 nnnnvvnnn
1379 Text
1380
1381 = Heading #let y = 2;
1382 nnnnvvnnn
1383 == Heading
1384 ");
1385 assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @r"
1386 #let f(x);
1387 nnnnv v
1388 ");
1389 assert_snapshot!(map_context(r#"#f(1, 2) Test"#).trim(), @r"
1390 #f(1, 2) Test
1391 vpppppp
1392 ");
1393 assert_snapshot!(map_context(r#"#() Test"#).trim(), @r"
1394 #() Test
1395 ee
1396 ");
1397 assert_snapshot!(map_context(r#"#(1) Test"#).trim(), @r"
1398 #(1) Test
1399 PPP
1400 ");
1401 assert_snapshot!(map_context(r#"#(a: 1) Test"#).trim(), @r"
1402 #(a: 1) Test
1403 eeeeee
1404 ");
1405 assert_snapshot!(map_context(r#"#(1, 2) Test"#).trim(), @r"
1406 #(1, 2) Test
1407 eeeeee
1408 ");
1409 assert_snapshot!(map_context(r#"#(1, 2)
1410 Test"#).trim(), @r"
1411 #(1, 2)
1412 eeeeee
1413 Test
1414 ");
1415 }
1416
1417 #[test]
1418 fn test_access_field() {
1419 fn test_fn(s: &str, cursor: i32) -> String {
1420 test_fn_(s, cursor).unwrap_or_default()
1421 }
1422
1423 fn test_fn_(s: &str, cursor: i32) -> Option<String> {
1424 let cursor = if cursor < 0 {
1425 s.len() as i32 + cursor
1426 } else {
1427 cursor
1428 };
1429 let source = Source::detached(s.to_owned());
1430 let root = LinkedNode::new(source.root());
1431 let node = root.leaf_at(cursor as usize, Side::Before)?;
1432 let syntax = classify_syntax(node, cursor as usize)?;
1433 let SyntaxClass::VarAccess(var) = syntax else {
1434 return None;
1435 };
1436
1437 let field = var.accessing_field()?;
1438 Some(match field {
1439 FieldClass::Field(ident) => format!("Field: {}", ident.text()),
1440 FieldClass::DotSuffix(span_offset) => {
1441 let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1442 format!("DotSuffix: {offset:?}")
1443 }
1444 })
1445 }
1446
1447 assert_snapshot!(test_fn("#(a.b)", 5), @r"Field: b");
1448 assert_snapshot!(test_fn("#a.", 3), @"DotSuffix: 3");
1449 assert_snapshot!(test_fn("$a.$", 3), @"DotSuffix: 3");
1450 assert_snapshot!(test_fn("#(a.)", 4), @"DotSuffix: 4");
1451 assert_snapshot!(test_fn("#(a..b)", 4), @"DotSuffix: 4");
1452 assert_snapshot!(test_fn("#(a..b())", 4), @"DotSuffix: 4");
1453 }
1454}