Skip to main content

syntect_tmlanguage/
raw.rs

1//! Deserialization model for `.tmLanguage.json` grammars.
2//!
3//! Mirrors the shape TextMate/VS Code grammars are actually authored in,
4//! not `syntect`'s own `SyntaxDefinition` model — see [`crate::translate`]
5//! for the conversion between the two.
6
7use std::collections::HashMap;
8
9use serde::Deserialize;
10
11#[derive(Debug, Clone, Deserialize)]
12pub struct RawGrammar {
13    #[serde(rename = "scopeName")]
14    pub scope_name: String,
15    pub name: Option<String>,
16    #[serde(rename = "fileTypes", default)]
17    pub file_types: Vec<String>,
18    #[serde(rename = "firstLineMatch")]
19    pub first_line_match: Option<String>,
20    #[serde(default)]
21    pub patterns: Vec<RawRule>,
22    #[serde(default)]
23    pub repository: HashMap<String, RawRule>,
24}
25
26#[derive(Debug, Clone, Deserialize)]
27pub struct RawRule {
28    pub include: Option<String>,
29
30    #[serde(rename = "match")]
31    pub match_: Option<String>,
32    pub name: Option<String>,
33    pub captures: Option<HashMap<String, RawCapture>>,
34
35    pub begin: Option<String>,
36    #[serde(rename = "beginCaptures")]
37    pub begin_captures: Option<HashMap<String, RawCapture>>,
38    pub end: Option<String>,
39    #[serde(rename = "endCaptures")]
40    pub end_captures: Option<HashMap<String, RawCapture>>,
41    /// `begin`/`while` rules are real (VS Code's own `markdown.tmLanguage.json`
42    /// uses them extensively, e.g. for blockquotes and fenced code blocks) —
43    /// but `syntect` has no engine-level equivalent at all (no YAML `while:`
44    /// key, no `MatchOperation` variant for it), so this field exists only
45    /// to detect and reject them with a clear error rather than silently
46    /// mistranslating. See [`crate::translate::Error::WhileNotSupported`].
47    #[serde(rename = "while")]
48    pub while_: Option<String>,
49    #[serde(rename = "contentName")]
50    pub content_name: Option<String>,
51    #[serde(
52        rename = "applyEndPatternLast",
53        default,
54        deserialize_with = "lenient_bool"
55    )]
56    pub apply_end_pattern_last: bool,
57
58    #[serde(default)]
59    pub patterns: Vec<RawRule>,
60}
61
62#[derive(Debug, Clone, Deserialize)]
63pub struct RawCapture {
64    pub name: Option<String>,
65}
66
67impl RawGrammar {
68    pub fn from_json(source: &str) -> Result<Self, serde_json::Error> {
69        serde_json::from_str(source)
70    }
71}
72
73/// Real-world grammars aren't always strict about `applyEndPatternLast`
74/// being a JSON boolean — e.g. VS Code's own `shell-unix-bash.tmLanguage.json`
75/// has `"applyEndPatternLast": 1`. Accept a number as truthy/falsy too,
76/// matching how such grammars are actually authored and how a JS-based
77/// consumer would treat the value.
78fn lenient_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
79where
80    D: serde::Deserializer<'de>,
81{
82    struct LenientBoolVisitor;
83
84    impl serde::de::Visitor<'_> for LenientBoolVisitor {
85        type Value = bool;
86
87        fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88            f.write_str("a boolean, or a truthy/falsy number")
89        }
90
91        fn visit_bool<E>(self, v: bool) -> Result<bool, E> {
92            Ok(v)
93        }
94
95        fn visit_u64<E>(self, v: u64) -> Result<bool, E> {
96            Ok(v != 0)
97        }
98
99        fn visit_i64<E>(self, v: i64) -> Result<bool, E> {
100            Ok(v != 0)
101        }
102
103        fn visit_f64<E>(self, v: f64) -> Result<bool, E> {
104            Ok(v != 0.0)
105        }
106    }
107
108    deserializer.deserialize_any(LenientBoolVisitor)
109}