Skip to main content

mnk_vmf/types/
viewsettings.rs

1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4    impl_block_properties_parser,
5    parser::{
6        close_block, key_value_boolean, key_value_numeric, open_block, InternalParser, TokenError,
7        TokenSource,
8    },
9    Parser,
10};
11
12/// ViewSettings holds all the parameters for an editor
13#[derive(Debug, Default, Eq, PartialEq)]
14pub struct ViewSettings {
15    snap_to_grid: bool,
16    show_grid: bool,
17    show_logical_grid: bool,
18    grid_spacing: u32,
19    show_3d_grid: bool,
20    hide_objects: bool,
21    hide_walls: bool,
22    hide_stripes: bool,
23    hide_neighbors: bool,
24    hide_detail: bool,
25    show_brushes: bool,
26    show_entities: bool,
27    show_light_radius: bool,
28    show_lighting_preview: bool,
29    show_wireframe: bool,
30}
31
32/// Internal [`ViewSettings`] Properties to be used in a parser impl
33#[derive(Debug, Clone)]
34enum ViewSettingsProperty {
35    SnapToGrid(bool),
36    ShowGrid(bool),
37    ShowLogicalGrid(bool),
38    GridSpacing(u32),
39    Show3DGrid(bool),
40    HideObjects(bool),
41    HideWalls(bool),
42    HideStripes(bool),
43    HideNeighbors(bool),
44    HideDetail(bool),
45    ShowBrushes(bool),
46    ShowEntities(bool),
47    ShowLightRadius(bool),
48    ShowLightingPreview(bool),
49    ShowWireframe(bool),
50}
51
52/// Public parser trait implementation that allows [`ViewSettings`] to use ::parse(input) call.
53impl Parser<'_> for ViewSettings {}
54
55/// A [`ViewSettings`] implementation for [`ViewSettings`].
56/// Every key-value pair needs to be in order, like in the example bellow.
57///
58/// usage: `let view_settings = ViewSettings::parser().parse();`.
59///
60/// The format that is being parsed here is:
61/// viewsettings
62///{
63/// "bSnapToGrid" "1"
64/// "bShowGrid" "1"
65/// "bShowLogicalGrid" "0"
66/// "nGridSpacing" "64"
67/// "bShow3DGrid" "0"
68///}
69impl<'src> InternalParser<'src> for ViewSettings {
70    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
71    where
72        I: TokenSource<'src>,
73    {
74        impl_block_properties_parser! {
75            property_list: ViewSettingsProperty = {
76                p_snap_to_grid        = key_value_boolean("bSnapToGrid")          => ViewSettingsProperty::SnapToGrid,
77                p_show_grid           = key_value_boolean("bShowGrid")            => ViewSettingsProperty::ShowGrid,
78                p_show_logical_grid   = key_value_boolean("bShowLogicalGrid")     => ViewSettingsProperty::ShowLogicalGrid,
79                p_grid_spacing        = key_value_numeric("nGridSpacing")         => ViewSettingsProperty::GridSpacing,
80                p_show_3d_grid        = key_value_boolean("bShow3DGrid")          => ViewSettingsProperty::Show3DGrid,
81                p_hide_objects        = key_value_boolean("bHideObjects")         => ViewSettingsProperty::HideObjects,
82                p_hide_walls          = key_value_boolean("bHideWalls")           => ViewSettingsProperty::HideWalls,
83                p_hide_stripes        = key_value_boolean("bHideStripes")         => ViewSettingsProperty::HideStripes,
84                p_hide_neighbors      = key_value_boolean("bHideNeighbors")       => ViewSettingsProperty::HideNeighbors,
85                p_hide_detail         = key_value_boolean("bHideDetail")          => ViewSettingsProperty::HideDetail,
86                p_show_brushes        = key_value_boolean("bShowBrushes")         => ViewSettingsProperty::ShowBrushes,
87                p_show_entities       = key_value_boolean("bShowEntities")        => ViewSettingsProperty::ShowEntities,
88                p_show_light_radius   = key_value_boolean("bShowLightRadius")     => ViewSettingsProperty::ShowLightRadius,
89                p_show_lighting_preview = key_value_boolean("bShowLightingPreview") => ViewSettingsProperty::ShowLightingPreview,
90                p_show_wireframe      = key_value_boolean("bShowWireframe")       => ViewSettingsProperty::ShowWireframe,
91            }
92        }
93        open_block("viewsettings")
94            .ignore_then(
95                property_list
96                    .repeated()
97                    .collect::<Vec<ViewSettingsProperty>>(),
98            )
99            .then_ignore(close_block())
100            .map(|properties: Vec<ViewSettingsProperty>| {
101                let mut settings = ViewSettings::default();
102                for prop in properties {
103                    match prop {
104                        ViewSettingsProperty::SnapToGrid(val) => settings.snap_to_grid = val,
105                        ViewSettingsProperty::ShowGrid(val) => settings.show_grid = val,
106                        ViewSettingsProperty::ShowLogicalGrid(val) => {
107                            settings.show_logical_grid = val
108                        }
109                        ViewSettingsProperty::GridSpacing(val) => settings.grid_spacing = val,
110                        ViewSettingsProperty::Show3DGrid(val) => settings.show_3d_grid = val,
111                        ViewSettingsProperty::HideObjects(val) => settings.hide_objects = val,
112                        ViewSettingsProperty::HideWalls(val) => settings.hide_walls = val,
113                        ViewSettingsProperty::HideStripes(val) => settings.hide_stripes = val,
114                        ViewSettingsProperty::HideNeighbors(val) => settings.hide_neighbors = val,
115                        ViewSettingsProperty::HideDetail(val) => settings.hide_detail = val,
116                        ViewSettingsProperty::ShowBrushes(val) => settings.show_brushes = val,
117                        ViewSettingsProperty::ShowEntities(val) => settings.show_entities = val,
118                        ViewSettingsProperty::ShowLightRadius(val) => {
119                            settings.show_light_radius = val
120                        }
121                        ViewSettingsProperty::ShowLightingPreview(val) => {
122                            settings.show_lighting_preview = val
123                        }
124                        ViewSettingsProperty::ShowWireframe(val) => settings.show_wireframe = val,
125                    }
126                }
127                settings
128            })
129            .boxed()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use crate::{parser::lexer::Token, util::lex, Parser};
136
137    use super::*;
138    use chumsky::error::RichReason;
139
140    fn parse_viewsettings_str(input_str: &str) -> Result<ViewSettings, Vec<RichReason<Token<'_>>>> {
141        ViewSettings::parse(lex(input_str))
142    }
143
144    #[test]
145    fn test_viewsettings_full_valid_input() {
146        let input = r#"
147        viewsettings
148        {
149            "bSnapToGrid" "1"
150            "bShowGrid" "1"
151            "bShowLogicalGrid" "0"
152            "nGridSpacing" "64"
153            "bShow3DGrid" "1"
154            "bHideObjects" "0"
155            "bHideWalls" "1"
156            "bHideStripes" "0"
157            "bHideNeighbors" "1"
158            "bHideDetail" "0"
159            "bShowBrushes" "1"
160            "bShowEntities" "0"
161            "bShowLightRadius" "1"
162            "bShowLightingPreview" "0"
163            "bShowWireframe" "1"
164        }"#;
165
166        let result = parse_viewsettings_str(input);
167        assert!(
168            result.is_ok(),
169            "Parser failed with errors: {:?}",
170            result.err()
171        );
172
173        let settings = result.unwrap();
174        assert_eq!(
175            settings,
176            ViewSettings {
177                snap_to_grid: true,
178                show_grid: true,
179                show_logical_grid: false,
180                grid_spacing: 64,
181                show_3d_grid: true,
182                hide_objects: false,
183                hide_walls: true,
184                hide_stripes: false,
185                hide_neighbors: true,
186                hide_detail: false,
187                show_brushes: true,
188                show_entities: false,
189                show_light_radius: true,
190                show_lighting_preview: false,
191                show_wireframe: true,
192            }
193        );
194    }
195
196    #[test]
197    fn test_viewsettings_partial_input_uses_defaults() {
198        let input = r#"
199        viewsettings
200        {
201            "bSnapToGrid" "1"
202            "nGridSpacing" "32"
203            "bShowWireframe" "0"
204        }"#;
205
206        let result = parse_viewsettings_str(input);
207        assert!(
208            result.is_ok(),
209            "Parser failed with errors: {:?}",
210            result.err()
211        );
212
213        let settings = result.unwrap();
214        let expected = ViewSettings {
215            snap_to_grid: true,
216            grid_spacing: 32,
217            show_wireframe: false,
218            ..Default::default()
219        };
220
221        assert_eq!(settings, expected);
222    }
223
224    #[test]
225    fn test_viewsettings_out_of_order() {
226        let input = r#"
227        viewsettings
228        {
229            "nGridSpacing" "16"
230            "bShowGrid" "0"
231            "bSnapToGrid" "1"
232        }"#;
233
234        let result = parse_viewsettings_str(input);
235        assert!(
236            result.is_ok(),
237            "Parser failed with errors: {:?}",
238            result.err()
239        );
240
241        let settings = result.unwrap();
242        let expected = ViewSettings {
243            grid_spacing: 16,
244            show_grid: false,
245            snap_to_grid: true,
246            ..Default::default()
247        };
248
249        assert_eq!(settings, expected);
250    }
251
252    #[test]
253    fn test_viewsettings_empty_block() {
254        let input = r#"
255        viewsettings
256        {
257        }"#;
258
259        let result = parse_viewsettings_str(input);
260        assert!(
261            result.is_ok(),
262            "Parser failed with errors: {:?}",
263            result.err()
264        );
265
266        let settings = result.unwrap();
267        assert_eq!(settings, ViewSettings::default());
268    }
269
270    #[test]
271    fn test_viewsettings_unknown_key_causes_error() {
272        // An unknown key, if not consumed by a more general rule,
273        // will prevent subsequent tokens (like the closing '}') from being parsed correctly.
274        let input = r#"
275        viewsettings
276        {
277            "bSnapToGrid" "1"
278            "bUnknownKey" "some_value"
279            "nGridSpacing" "64"
280        }"#;
281
282        let result = parse_viewsettings_str(input);
283        assert!(
284            result.is_err(),
285            "Parser should fail on unknown key mid-list"
286        );
287        // You could inspect the error types/reasons if needed, e.g., expecting '}' but found "bUnknownKey".
288    }
289
290    #[test]
291    fn test_viewsettings_unknown_key_at_end_still_errors_if_not_last() {
292        // Similar to above, if "unknown" is not the very last property before "}"
293        let input = r#"
294        viewsettings
295        {
296            "bSnapToGrid" "1"
297            "bShowGrid" "1"
298            "bUnknownKey" "some_value"
299        }"#; // Missing closing brace technically
300
301        let result = parse_viewsettings_str(input);
302        assert!(
303            result.is_err(),
304            "Parser should fail if block doesn't close properly after unknown key"
305        );
306    }
307
308    #[test]
309    fn test_viewsettings_invalid_value_type_for_numeric() {
310        let input = r#"
311        viewsettings
312        {
313            "nGridSpacing" "not_a_number"
314        }"#;
315
316        let result = parse_viewsettings_str(input);
317        assert!(
318            result.is_err(),
319            "Parser should fail on invalid numeric value"
320        );
321        // Error would likely be "integer out of range" or similar from your `number` parser.
322    }
323
324    #[test]
325    fn test_viewsettings_invalid_value_type_for_boolean() {
326        let input = r#"
327        viewsettings
328        {
329            "bSnapToGrid" "not_a_boolean"
330        }"#;
331
332        let result = parse_viewsettings_str(input);
333        assert!(
334            result.is_err(),
335            "Parser should fail on invalid boolean value"
336        );
337        // Error would be from `quoted_string("true").or(quoted_string("false"))` failing.
338    }
339
340    #[test]
341    fn test_viewsettings_duplicate_keys_last_one_wins() {
342        let input = r#"
343        viewsettings
344        {
345            "bSnapToGrid" "0"
346            "nGridSpacing" "32"
347            "bSnapToGrid" "1"
348            "nGridSpacing" "64"
349        }"#;
350
351        let result = parse_viewsettings_str(input);
352        assert!(
353            result.is_ok(),
354            "Parser failed with errors: {:?}",
355            result.err()
356        );
357
358        let settings = result.unwrap();
359        let expected = ViewSettings {
360            snap_to_grid: true,
361            grid_spacing: 64,
362            ..Default::default()
363        };
364
365        assert_eq!(settings, expected);
366    }
367
368    #[test]
369    fn test_viewsettings_malformed_block_missing_closing_brace() {
370        let input = r#"
371        viewsettings
372        {
373            "bSnapToGrid" "1"
374        "#;
375
376        let result = parse_viewsettings_str(input);
377        assert!(
378            result.is_err(),
379            "Parser should fail on missing closing brace"
380        );
381    }
382
383    #[test]
384    fn test_viewsettings_malformed_block_missing_opening_brace() {
385        let input = r#"
386        viewsettings
387            "bSnapToGrid" "1"
388        }"#;
389
390        let result = parse_viewsettings_str(input);
391        assert!(
392            result.is_err(),
393            "Parser should fail on missing opening brace"
394        );
395    }
396
397    #[test]
398    fn test_viewsettings_invalid_block_name() {
399        let input = r#"
400        wrongblockname
401        {
402            "bSnapToGrid" "1"
403        }"#;
404
405        let result = parse_viewsettings_str(input);
406        assert!(result.is_err(), "Parser should fail on invalid block name");
407    }
408
409    #[test]
410    fn test_viewsettings_extra_content_after_closing_brace() {
411        let input = r#"
412        viewsettings
413        {
414            "bSnapToGrid" "1"
415        }
416        "extrastuff" "not good"
417        "#;
418        let result = parse_viewsettings_str(input);
419        assert!(
420            result.is_err(),
421            "Parser should fail. Errors: {:?}",
422            result.err()
423        );
424    }
425}