Skip to main content

streamdown_config/
features.rs

1//! Feature flags configuration.
2//!
3//! This module contains the `FeaturesConfig` struct which holds
4//! all boolean feature flags and related settings.
5
6use serde::{Deserialize, Serialize};
7
8/// Feature flags configuration.
9///
10/// Controls which features are enabled in streamdown.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "PascalCase")]
13pub struct FeaturesConfig {
14    /// Enable space-indented code blocks (4 spaces = code).
15    /// Default: false (only fenced code blocks are recognized)
16    #[serde(default)]
17    pub code_spaces: bool,
18
19    /// Enable clipboard integration for code blocks.
20    /// Default: true
21    #[serde(default = "default_true")]
22    pub clipboard: bool,
23
24    /// Enable debug logging.
25    /// Default: false
26    #[serde(default)]
27    pub logging: bool,
28
29    /// Timeout in seconds for streaming operations.
30    /// Default: 0.1
31    #[serde(default = "default_timeout")]
32    pub timeout: f64,
33
34    /// Save brace matching state.
35    /// Default: true
36    #[serde(default = "default_true")]
37    pub savebrace: bool,
38
39    /// Enable image rendering (where supported).
40    /// Default: true
41    #[serde(default = "default_true")]
42    pub images: bool,
43
44    /// Enable link rendering with OSC 8 hyperlinks.
45    /// Default: true
46    #[serde(default = "default_true")]
47    pub links: bool,
48}
49
50impl Default for FeaturesConfig {
51    fn default() -> Self {
52        Self {
53            code_spaces: false,
54            clipboard: true,
55            logging: false,
56            timeout: 0.1,
57            savebrace: true,
58            images: true,
59            links: true,
60        }
61    }
62}
63
64impl FeaturesConfig {
65    /// Merge another FeaturesConfig into this one.
66    ///
67    /// All fields are copied from `other` since they're all
68    /// simple values with no "unset" state in TOML.
69    pub fn merge(&mut self, other: &FeaturesConfig) {
70        // For a proper merge, we'd need Option<T> fields.
71        // Since TOML doesn't distinguish "not set" from "set to default",
72        // we just copy all values from other.
73        // In practice, this means the override file needs only the
74        // values the user wants to change, and we parse a partial
75        // config for the override.
76        self.code_spaces = other.code_spaces;
77        self.clipboard = other.clipboard;
78        self.logging = other.logging;
79        self.timeout = other.timeout;
80        self.savebrace = other.savebrace;
81        self.images = other.images;
82        self.links = other.links;
83    }
84
85    /// Create a new FeaturesConfig with all features enabled.
86    pub fn all_enabled() -> Self {
87        Self {
88            code_spaces: true,
89            clipboard: true,
90            logging: true,
91            timeout: 0.1,
92            savebrace: true,
93            images: true,
94            links: true,
95        }
96    }
97
98    /// Create a new FeaturesConfig with all features disabled.
99    pub fn all_disabled() -> Self {
100        Self {
101            code_spaces: false,
102            clipboard: false,
103            logging: false,
104            timeout: 0.0,
105            savebrace: false,
106            images: false,
107            links: false,
108        }
109    }
110}
111
112fn default_true() -> bool {
113    true
114}
115
116fn default_timeout() -> f64 {
117    0.1
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_default() {
126        let features = FeaturesConfig::default();
127        assert!(!features.code_spaces);
128        assert!(features.clipboard);
129        assert!(!features.logging);
130        assert!((features.timeout - 0.1).abs() < f64::EPSILON);
131        assert!(features.savebrace);
132        assert!(features.images);
133        assert!(features.links);
134    }
135
136    #[test]
137    fn test_serde_pascal_case() {
138        let toml_str = r#"
139            CodeSpaces = true
140            Clipboard = false
141            Logging = true
142            Timeout = 0.5
143            Savebrace = false
144            Images = false
145            Links = false
146        "#;
147
148        let features: FeaturesConfig = toml::from_str(toml_str).unwrap();
149        assert!(features.code_spaces);
150        assert!(!features.clipboard);
151        assert!(features.logging);
152        assert!((features.timeout - 0.5).abs() < f64::EPSILON);
153        assert!(!features.savebrace);
154        assert!(!features.images);
155        assert!(!features.links);
156    }
157
158    #[test]
159    fn test_all_enabled() {
160        let features = FeaturesConfig::all_enabled();
161        assert!(features.code_spaces);
162        assert!(features.clipboard);
163        assert!(features.logging);
164    }
165
166    #[test]
167    fn test_all_disabled() {
168        let features = FeaturesConfig::all_disabled();
169        assert!(!features.code_spaces);
170        assert!(!features.clipboard);
171        assert!(!features.logging);
172    }
173}