yaml_edit/
yaml.rs

1//! Lossless YAML parser and editor.
2
3use crate::{
4    lex::{lex, SyntaxKind},
5    parse::Parse,
6    scalar::ScalarValue,
7    value::YamlValue,
8    PositionedParseError,
9};
10use rowan::ast::AstNode;
11use rowan::{GreenNodeBuilder, TextRange};
12use std::path::Path;
13use std::str::FromStr;
14
15/// A positioned parse error containing location information.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct ParsedYaml {
18    pub green_node: rowan::GreenNode,
19    pub errors: Vec<String>,
20    pub positioned_errors: Vec<PositionedParseError>,
21}
22
23/// YAML language type for rowan.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub enum Lang {}
26
27impl rowan::Language for Lang {
28    type Kind = SyntaxKind;
29
30    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
31        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
32    }
33
34    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
35        kind.into()
36    }
37}
38
39type SyntaxNode = rowan::SyntaxNode<Lang>;
40type SyntaxToken = rowan::SyntaxToken<Lang>;
41
42/// A macro to create AST node wrappers.
43macro_rules! ast_node {
44    ($ast:ident, $kind:ident, $doc:expr) => {
45        #[doc = $doc]
46        #[derive(PartialEq, Eq, Hash)]
47        pub struct $ast(SyntaxNode);
48
49        impl std::fmt::Debug for $ast {
50            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51                f.debug_struct(stringify!($ast))
52                    .field("syntax", &self.0)
53                    .finish()
54            }
55        }
56
57        impl AstNode for $ast {
58            type Language = Lang;
59
60            fn can_cast(kind: SyntaxKind) -> bool {
61                kind == SyntaxKind::$kind
62            }
63
64            fn cast(syntax: SyntaxNode) -> Option<Self> {
65                if Self::can_cast(syntax.kind()) {
66                    Some(Self(syntax))
67                } else {
68                    None
69                }
70            }
71
72            fn syntax(&self) -> &SyntaxNode {
73                &self.0
74            }
75        }
76
77        impl From<SyntaxNode> for $ast {
78            fn from(node: SyntaxNode) -> Self {
79                $ast(node)
80            }
81        }
82
83        impl std::fmt::Display for $ast {
84            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85                write!(f, "{}", self.0.text())
86            }
87        }
88    };
89}
90
91ast_node!(
92    Yaml,
93    ROOT,
94    "A YAML document containing one or more documents"
95);
96ast_node!(Document, DOCUMENT, "A single YAML document");
97ast_node!(Sequence, SEQUENCE, "A YAML sequence (list)");
98ast_node!(Mapping, MAPPING, "A YAML mapping (key-value pairs)");
99ast_node!(Scalar, SCALAR, "A YAML scalar value");
100
101impl Default for Yaml {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107impl Yaml {
108    /// Create a new empty YAML document.
109    pub fn new() -> Yaml {
110        let mut builder = GreenNodeBuilder::new();
111        builder.start_node(SyntaxKind::ROOT.into());
112        builder.finish_node();
113        Yaml(SyntaxNode::new_root_mut(builder.finish()))
114    }
115
116    /// Parse YAML text, returning a Parse result
117    pub fn parse(text: &str) -> Parse<Yaml> {
118        Parse::parse_yaml(text)
119    }
120
121    /// Parse YAML from a file path
122    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Yaml, crate::Error> {
123        let contents = std::fs::read_to_string(path)?;
124        Ok(Self::from_str(&contents)?)
125    }
126
127    /// Get all documents in this YAML file
128    pub fn documents(&self) -> impl Iterator<Item = Document> {
129        self.0.children().filter_map(Document::cast)
130    }
131
132    /// Get the first document, if any
133    pub fn document(&self) -> Option<Document> {
134        self.documents().next()
135    }
136
137    /// Add a new document to the end of this YAML file
138    pub fn push_document(&mut self, document: Document) {
139        let children_count = self.0.children_with_tokens().count();
140        let mut new_nodes = Vec::new();
141
142        // Add separator if there are existing documents
143        if self.0.children().count() > 0 {
144            new_nodes.push(create_token_green(SyntaxKind::NEWLINE, "\n").into());
145            new_nodes.push(create_token_green(SyntaxKind::DOC_START, "---").into());
146            new_nodes.push(create_token_green(SyntaxKind::NEWLINE, "\n").into());
147        }
148
149        // Add the new document
150        new_nodes.push(document.0.green().into());
151
152        // Splice at the end of existing children
153        let new_green = self
154            .0
155            .green()
156            .splice_children(children_count..children_count, new_nodes);
157        self.0 = SyntaxNode::new_root_mut(new_green);
158    }
159}
160
161impl Document {
162    /// Create a new document
163    pub fn new() -> Document {
164        let mut builder = GreenNodeBuilder::new();
165        builder.start_node(SyntaxKind::DOCUMENT.into());
166        builder.finish_node();
167        Document(SyntaxNode::new_root_mut(builder.finish()))
168    }
169
170    /// Get the root node of this document (could be mapping, sequence, or scalar)
171    pub fn root_node(&self) -> Option<SyntaxNode> {
172        self.0.children().find(|child| {
173            matches!(
174                child.kind(),
175                SyntaxKind::MAPPING | SyntaxKind::SEQUENCE | SyntaxKind::SCALAR
176            )
177        })
178    }
179
180    /// Get this document as a mapping, if it is one
181    pub fn as_mapping(&self) -> Option<Mapping> {
182        self.root_node().and_then(Mapping::cast)
183    }
184
185    /// Get this document as a sequence, if it is one
186    pub fn as_sequence(&self) -> Option<Sequence> {
187        self.root_node().and_then(Sequence::cast)
188    }
189
190    /// Get this document as a scalar, if it is one
191    pub fn as_scalar(&self) -> Option<Scalar> {
192        self.root_node().and_then(Scalar::cast)
193    }
194}
195
196/// Check if a SyntaxKind represents a scalar token
197fn is_scalar_token(kind: SyntaxKind) -> bool {
198    matches!(
199        kind,
200        SyntaxKind::STRING
201            | SyntaxKind::INT
202            | SyntaxKind::FLOAT
203            | SyntaxKind::BOOL
204            | SyntaxKind::NULL
205            | SyntaxKind::VALUE
206    )
207}
208
209impl Mapping {
210    /// Get all key-value pairs in this mapping
211    pub fn pairs(&self) -> impl Iterator<Item = (Option<Scalar>, Option<SyntaxNode>)> {
212        // Look for KEY nodes followed by VALUE nodes
213        let children: Vec<_> = self.0.children().collect();
214        let mut pairs = Vec::new();
215        let mut i = 0;
216
217        while i < children.len() {
218            // Check if this is a KEY node
219            if children[i].kind() == SyntaxKind::KEY {
220                let key_text = children[i].text().to_string();
221                let key_scalar = Scalar(create_scalar_node(&key_text));
222
223                // Look for the next VALUE node
224                let mut value_node = None;
225                let mut j = i + 1;
226                while j < children.len() {
227                    if children[j].kind() == SyntaxKind::VALUE {
228                        value_node = Some(children[j].clone());
229                        break;
230                    }
231                    j += 1;
232                }
233
234                pairs.push((Some(key_scalar), value_node));
235                i = j + 1;
236            } else {
237                i += 1;
238            }
239        }
240
241        pairs.into_iter()
242    }
243
244    /// Get the value for a specific key
245    pub fn get(&self, key: &str) -> Option<SyntaxNode> {
246        self.pairs()
247            .find(|(k, _)| k.as_ref().map(|s| s.value()) == Some(key.to_string()))
248            .and_then(|(_, v)| v)
249    }
250}
251
252impl Sequence {
253    /// Get all items in this sequence
254    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
255        // TODO: Implementation for getting sequence items
256        std::iter::empty()
257    }
258}
259
260impl Scalar {
261    /// Get the string value of this scalar
262    pub fn value(&self) -> String {
263        self.0.text().to_string()
264    }
265
266    /// Check if this scalar is quoted
267    pub fn is_quoted(&self) -> bool {
268        let text = self.value();
269        (text.starts_with('"') && text.ends_with('"'))
270            || (text.starts_with('\'') && text.ends_with('\''))
271    }
272
273    /// Get the unquoted value
274    pub fn unquoted_value(&self) -> String {
275        let text = self.value();
276        if self.is_quoted() {
277            // Simple unquoting - a full implementation would handle escapes
278            text[1..text.len() - 1].to_string()
279        } else {
280            text.to_string()
281        }
282    }
283}
284
285impl FromStr for Yaml {
286    type Err = crate::ParseError;
287
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        Yaml::parse(s).to_result()
290    }
291}
292
293/// Internal parser state
294struct Parser {
295    tokens: Vec<(SyntaxKind, String)>,
296    current_token_index: usize,
297    builder: GreenNodeBuilder<'static>,
298    errors: Vec<String>,
299    positioned_errors: Vec<PositionedParseError>,
300    token_positions: Vec<(SyntaxKind, rowan::TextSize, rowan::TextSize)>,
301    in_flow_context: bool,
302}
303
304impl Parser {
305    fn new(text: &str) -> Self {
306        let lexed = lex(text);
307        let mut tokens = Vec::new();
308        let mut token_positions = Vec::new();
309        let mut offset = rowan::TextSize::from(0);
310
311        for (kind, token_text) in lexed {
312            let start = offset;
313            let len = rowan::TextSize::from(token_text.len() as u32);
314            let end = start + len;
315
316            tokens.push((kind, token_text.to_string()));
317            token_positions.push((kind, start, end));
318            offset = end;
319        }
320
321        // Reverse tokens so we can use pop() to get the next token
322        let token_count = tokens.len();
323        tokens.reverse();
324
325        Self {
326            tokens,
327            current_token_index: token_count,
328            builder: GreenNodeBuilder::new(),
329            errors: Vec::new(),
330            positioned_errors: Vec::new(),
331            token_positions,
332            in_flow_context: false,
333        }
334    }
335
336    fn parse(mut self) -> ParsedYaml {
337        self.builder.start_node(SyntaxKind::ROOT.into());
338
339        self.skip_ws_and_newlines();
340
341        // Parse documents
342        // Always parse at least one document
343        if self.current().is_some() {
344            self.parse_document();
345            self.skip_ws_and_newlines();
346
347            // Only parse additional documents if we see a document separator
348            while self.current() == Some(SyntaxKind::DOC_START) {
349                self.parse_document();
350                self.skip_ws_and_newlines();
351            }
352        }
353
354        self.builder.finish_node();
355
356        ParsedYaml {
357            green_node: self.builder.finish(),
358            errors: self.errors,
359            positioned_errors: self.positioned_errors,
360        }
361    }
362
363    fn parse_document(&mut self) {
364        self.builder.start_node(SyntaxKind::DOCUMENT.into());
365
366        // Handle document start marker
367        if self.current() == Some(SyntaxKind::DOC_START) {
368            self.bump();
369            self.skip_ws_and_newlines();
370        }
371
372        // Parse the document content
373        if self.current().is_some()
374            && self.current() != Some(SyntaxKind::DOC_END)
375            && self.current() != Some(SyntaxKind::DOC_START)
376        {
377            self.parse_value();
378        }
379
380        // Handle document end marker
381        if self.current() == Some(SyntaxKind::DOC_END) {
382            self.bump();
383        }
384
385        self.builder.finish_node();
386    }
387
388    fn parse_value(&mut self) {
389        match self.current() {
390            Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
391            Some(
392                SyntaxKind::STRING
393                | SyntaxKind::INT
394                | SyntaxKind::FLOAT
395                | SyntaxKind::BOOL
396                | SyntaxKind::NULL,
397            ) => {
398                // In flow context, always parse as scalar
399                // In block context, check if it's a mapping key
400                if !self.in_flow_context && self.is_mapping_key() {
401                    self.parse_mapping();
402                } else {
403                    self.parse_scalar();
404                }
405            }
406            Some(SyntaxKind::LEFT_BRACKET) => self.parse_flow_sequence(),
407            Some(SyntaxKind::LEFT_BRACE) => self.parse_flow_mapping(),
408            _ => self.parse_scalar(),
409        }
410    }
411
412    fn parse_scalar(&mut self) {
413        self.builder.start_node(SyntaxKind::SCALAR.into());
414
415        // Handle quotes
416        if matches!(
417            self.current(),
418            Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
419        ) {
420            let quote_type = self.current().unwrap();
421            self.bump(); // opening quote
422
423            // TODO: Parse quoted content properly
424            while self.current().is_some() && self.current() != Some(quote_type) {
425                self.bump();
426            }
427
428            if self.current() == Some(quote_type) {
429                self.bump(); // closing quote
430            } else {
431                self.add_error("Unterminated quoted string".to_string());
432            }
433        } else {
434            // Handle typed scalar tokens from lexer
435            if matches!(
436                self.current(),
437                Some(
438                    SyntaxKind::STRING
439                        | SyntaxKind::INT
440                        | SyntaxKind::FLOAT
441                        | SyntaxKind::BOOL
442                        | SyntaxKind::NULL
443                )
444            ) {
445                self.bump();
446            } else {
447                // Fallback: consume tokens until we hit structure
448                while let Some(kind) = self.current() {
449                    if matches!(
450                        kind,
451                        SyntaxKind::NEWLINE
452                            | SyntaxKind::COLON
453                            | SyntaxKind::DASH
454                            | SyntaxKind::COMMENT
455                            | SyntaxKind::DOC_START
456                            | SyntaxKind::DOC_END
457                    ) {
458                        break;
459                    }
460                    self.bump();
461                }
462            }
463        }
464
465        self.builder.finish_node();
466    }
467
468    fn parse_mapping(&mut self) {
469        self.builder.start_node(SyntaxKind::MAPPING.into());
470
471        while self.current().is_some() {
472            if !self.is_mapping_key() {
473                break;
474            }
475
476            // Parse key - wrap the scalar token in a KEY node
477            self.builder.start_node(SyntaxKind::KEY.into());
478            if matches!(
479                self.current(),
480                Some(
481                    SyntaxKind::STRING
482                        | SyntaxKind::INT
483                        | SyntaxKind::FLOAT
484                        | SyntaxKind::BOOL
485                        | SyntaxKind::NULL
486                )
487            ) {
488                self.bump(); // consume the key token
489            }
490            self.builder.finish_node();
491
492            self.skip_whitespace();
493
494            // Expect colon
495            if self.current() == Some(SyntaxKind::COLON) {
496                self.bump();
497                self.skip_whitespace();
498
499                // Parse value - wrap in VALUE node
500                self.builder.start_node(SyntaxKind::VALUE.into());
501                if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
502                    self.parse_value();
503                } else if self.current() == Some(SyntaxKind::NEWLINE) {
504                    // Check if next line is indented (nested content)
505                    self.bump(); // consume newline
506                    if self.current() == Some(SyntaxKind::INDENT) {
507                        self.bump(); // consume indent
508                                     // Parse the indented content as the value
509                        self.parse_value();
510                    }
511                }
512                // Empty VALUE node if no value present
513                self.builder.finish_node();
514            } else {
515                self.add_error("Expected ':' after mapping key".to_string());
516            }
517
518            self.skip_ws_and_newlines();
519        }
520
521        self.builder.finish_node();
522    }
523
524    fn parse_sequence(&mut self) {
525        self.builder.start_node(SyntaxKind::SEQUENCE.into());
526
527        while self.current() == Some(SyntaxKind::DASH) {
528            self.bump(); // consume dash
529            self.skip_whitespace();
530
531            if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
532                self.parse_value();
533            }
534
535            self.skip_ws_and_newlines();
536        }
537
538        self.builder.finish_node();
539    }
540
541    fn parse_flow_sequence(&mut self) {
542        self.builder.start_node(SyntaxKind::SEQUENCE.into());
543
544        self.bump(); // consume [
545        self.skip_whitespace();
546
547        let prev_flow = self.in_flow_context;
548        self.in_flow_context = true;
549
550        while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
551            self.parse_value();
552            self.skip_whitespace();
553
554            if self.current() == Some(SyntaxKind::COMMA) {
555                self.bump();
556                self.skip_whitespace();
557            }
558        }
559
560        self.in_flow_context = prev_flow;
561
562        if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
563            self.bump();
564        } else {
565            self.add_error("Expected ']' to close flow sequence".to_string());
566        }
567
568        self.builder.finish_node();
569    }
570
571    fn parse_flow_mapping(&mut self) {
572        self.builder.start_node(SyntaxKind::MAPPING.into());
573
574        self.bump(); // consume {
575        self.skip_whitespace();
576
577        let prev_flow = self.in_flow_context;
578        self.in_flow_context = true;
579
580        while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
581            // Parse key
582            self.parse_value();
583            self.skip_whitespace();
584
585            if self.current() == Some(SyntaxKind::COLON) {
586                self.bump();
587                self.skip_whitespace();
588                self.parse_value();
589            } else {
590                self.add_error("Expected ':' in flow mapping".to_string());
591            }
592
593            self.skip_whitespace();
594
595            if self.current() == Some(SyntaxKind::COMMA) {
596                self.bump();
597                self.skip_whitespace();
598            }
599        }
600
601        self.in_flow_context = prev_flow;
602
603        if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
604            self.bump();
605        } else {
606            self.add_error("Expected '}' to close flow mapping".to_string());
607        }
608
609        self.builder.finish_node();
610    }
611
612    fn is_mapping_key(&self) -> bool {
613        // Look ahead to see if there's a colon after the current token
614        for kind in self.upcoming_tokens() {
615            match kind {
616                SyntaxKind::COLON => return true,
617                SyntaxKind::WHITESPACE => continue,
618                // These tokens definitely end a potential mapping key
619                SyntaxKind::NEWLINE
620                | SyntaxKind::DASH
621                | SyntaxKind::DOC_START
622                | SyntaxKind::DOC_END
623                | SyntaxKind::COMMA
624                | SyntaxKind::RIGHT_BRACKET
625                | SyntaxKind::RIGHT_BRACE => return false,
626                // Other tokens could be part of a complex key, keep looking
627                _ => continue,
628            }
629        }
630        false
631    }
632
633    fn skip_whitespace(&mut self) {
634        while self.current() == Some(SyntaxKind::WHITESPACE) {
635            self.bump();
636        }
637    }
638
639    fn skip_ws_and_newlines(&mut self) {
640        while matches!(
641            self.current(),
642            Some(
643                SyntaxKind::WHITESPACE
644                    | SyntaxKind::NEWLINE
645                    | SyntaxKind::INDENT
646                    | SyntaxKind::COMMENT
647            )
648        ) {
649            self.bump();
650        }
651    }
652
653    fn bump(&mut self) {
654        if let Some((kind, text)) = self.tokens.pop() {
655            self.builder.token(kind.into(), &text);
656            if self.current_token_index > 0 {
657                self.current_token_index -= 1;
658            }
659        }
660    }
661
662    fn current(&self) -> Option<SyntaxKind> {
663        self.tokens.last().map(|(kind, _)| *kind)
664    }
665
666    /// Peek at the nth token ahead (0 = current, 1 = next, etc.)
667    fn nth(&self, n: usize) -> Option<SyntaxKind> {
668        if n >= self.tokens.len() {
669            None
670        } else {
671            let idx = self.tokens.len() - 1 - n;
672            Some(self.tokens[idx].0)
673        }
674    }
675
676    /// Iterator over upcoming tokens starting from the next token (not current)
677    fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
678        (1..self.tokens.len()).map(move |i| {
679            let idx = self.tokens.len() - 1 - i;
680            self.tokens[idx].0
681        })
682    }
683
684    fn add_error(&mut self, message: String) {
685        self.errors.push(message);
686    }
687}
688
689/// Parse YAML text
690pub fn parse(text: &str) -> ParsedYaml {
691    let parser = Parser::new(text);
692    parser.parse()
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698
699    #[test]
700    fn test_simple_mapping() {
701        let yaml = "key: value";
702        let parsed = Yaml::from_str(yaml).unwrap();
703        let doc = parsed.document().unwrap();
704        let mapping = doc.as_mapping().unwrap();
705
706        // Basic structure test
707        assert_eq!(parsed.to_string().trim(), "key: value");
708
709        // Test get functionality
710        let value = mapping.get("key");
711        assert!(value.is_some());
712    }
713
714    #[test]
715    fn test_simple_sequence() {
716        let yaml = "- item1\n- item2";
717        let parsed = Yaml::from_str(yaml);
718        assert!(parsed.is_ok());
719    }
720
721    #[test]
722    fn test_complex_yaml() {
723        let yaml = r#"
724name: my-app
725version: 1.0.0
726dependencies:
727  - serde
728  - tokio
729config:
730  port: 8080
731  enabled: true
732"#;
733        let parsed = Yaml::from_str(yaml).unwrap();
734        assert_eq!(parsed.documents().count(), 1);
735
736        let doc = parsed.document().unwrap();
737        assert!(doc.as_mapping().is_some());
738    }
739
740    #[test]
741    fn test_multiple_documents() {
742        let yaml = r#"---
743doc: first
744---
745doc: second
746...
747"#;
748        let parsed = Yaml::from_str(yaml).unwrap();
749        assert_eq!(parsed.documents().count(), 2);
750    }
751
752    #[test]
753    fn test_flow_styles() {
754        let yaml = r#"
755array: [1, 2, 3]
756object: {key: value, another: 42}
757"#;
758        let parsed = Yaml::from_str(yaml).unwrap();
759        assert!(parsed.document().is_some());
760    }
761
762    #[test]
763    fn test_scalar_types_parsing() {
764        let yaml = r#"
765string: hello
766integer: 42
767float: 3.14
768bool_true: true
769bool_false: false
770null_value: null
771tilde: ~
772"#;
773        let parsed = Yaml::from_str(yaml).unwrap();
774        let doc = parsed.document().unwrap();
775        let mapping = doc.as_mapping().unwrap();
776
777        // All keys should be accessible
778        assert!(mapping.get("string").is_some());
779        assert!(mapping.get("integer").is_some());
780        assert!(mapping.get("float").is_some());
781        assert!(mapping.get("bool_true").is_some());
782        assert!(mapping.get("bool_false").is_some());
783        assert!(mapping.get("null_value").is_some());
784        assert!(mapping.get("tilde").is_some());
785    }
786
787    #[test]
788    fn test_preserve_formatting() {
789        let yaml = r#"# Comment at start
790key:   value    # inline comment
791
792# Another comment
793list:
794  - item1   
795  - item2
796"#;
797        let parsed = Yaml::from_str(yaml).unwrap();
798        let output = parsed.to_string();
799
800        // Should preserve comments
801        assert!(output.contains("# Comment at start"));
802        assert!(output.contains("# inline comment"));
803        assert!(output.contains("# Another comment"));
804
805        // Should preserve extra spaces
806        assert!(output.contains("key:   value"));
807    }
808
809    #[test]
810    fn test_quoted_strings() {
811        let yaml = r#"
812single: 'single quoted'
813double: "double quoted"
814plain: unquoted
815"#;
816        let parsed = Yaml::from_str(yaml).unwrap();
817        let doc = parsed.document().unwrap();
818        let mapping = doc.as_mapping().unwrap();
819
820        assert!(mapping.get("single").is_some());
821        assert!(mapping.get("double").is_some());
822        assert!(mapping.get("plain").is_some());
823    }
824
825    #[test]
826    fn test_nested_structures() {
827        let yaml = r#"
828root:
829  nested:
830    deeply:
831      value: 42
832  list:
833    - item1
834    - item2
835"#;
836        let parsed = Yaml::from_str(yaml).unwrap();
837        assert!(parsed.document().is_some());
838    }
839
840    #[test]
841    fn test_empty_values() {
842        let yaml = r#"
843empty_string: ""
844empty_after_colon:
845another_key: value
846"#;
847        let parsed = Yaml::from_str(yaml).unwrap();
848        let doc = parsed.document().unwrap();
849        let mapping = doc.as_mapping().unwrap();
850
851        assert!(mapping.get("empty_string").is_some());
852        assert!(mapping.get("another_key").is_some());
853    }
854
855    #[test]
856    fn test_special_characters() {
857        let yaml = r#"
858special: "line1\nline2"
859unicode: "emoji 😀"
860escaped: 'it\'s escaped'
861"#;
862        let result = Yaml::from_str(yaml);
863        // Should parse without panicking
864        assert!(result.is_ok());
865    }
866
867    // Editing tests
868    #[test]
869    fn test_mapping_set_new_key() {
870        let yaml = "existing: value";
871        let mut parsed = Yaml::from_str(yaml).unwrap();
872
873        if let Some(doc) = parsed.document() {
874            if let Some(mut mapping) = doc.as_mapping() {
875                mapping.set("new_key", "new_value");
876                let output = parsed.to_string();
877                assert!(output.contains("new_key"));
878                assert!(output.contains("new_value"));
879            }
880        }
881    }
882
883    #[test]
884    fn test_mapping_rename_key() {
885        let yaml = "old_name: value";
886        let mut parsed = Yaml::from_str(yaml).unwrap();
887
888        if let Some(doc) = parsed.document() {
889            if let Some(mut mapping) = doc.as_mapping() {
890                let renamed = mapping.rename_key("old_name", "new_name");
891                assert!(renamed);
892                let output = parsed.to_string();
893                assert!(output.contains("new_name"));
894                assert!(!output.contains("old_name"));
895            }
896        }
897    }
898
899    #[test]
900    fn test_mapping_remove_key() {
901        let yaml = "key1: value1\nkey2: value2";
902        let mut parsed = Yaml::from_str(yaml).unwrap();
903
904        if let Some(doc) = parsed.document() {
905            if let Some(mut mapping) = doc.as_mapping() {
906                let removed = mapping.remove("key1");
907                assert!(removed);
908                let output = parsed.to_string();
909                assert!(!output.contains("key1"));
910                assert!(output.contains("key2"));
911            }
912        }
913    }
914
915    #[test]
916    fn test_sequence_operations() {
917        let yaml = "- item1\n- item2";
918        let mut parsed = Yaml::from_str(yaml).unwrap();
919
920        if let Some(doc) = parsed.document() {
921            if let Some(mut seq) = doc.as_sequence() {
922                // Test push
923                seq.push("item3");
924                let output = parsed.to_string();
925                assert!(output.contains("item3"));
926
927                // Test insert
928                seq.insert(0, "item0");
929                let output = parsed.to_string();
930                assert!(output.contains("item0"));
931            }
932        }
933    }
934
935    #[test]
936    fn test_error_handling() {
937        // Invalid YAML should return error
938        let yaml = "key: value\n  invalid indentation for key";
939        let result = Yaml::from_str(yaml);
940        // For now, just check it doesn't panic
941        let _ = result;
942    }
943}
944
945// Additional editing methods for existing types
946
947// Helper functions for creating YAML nodes
948fn create_scalar_green(value: &str) -> rowan::GreenNode {
949    let mut builder = GreenNodeBuilder::new();
950    builder.start_node(SyntaxKind::SCALAR.into());
951    builder.token(SyntaxKind::VALUE.into(), value);
952    builder.finish_node();
953    builder.finish()
954}
955
956fn create_token_green(kind: SyntaxKind, text: &str) -> rowan::GreenToken {
957    rowan::GreenToken::new(kind.into(), text)
958}
959
960fn create_mapping_entry_green(
961    key: &str,
962    value: &str,
963) -> Vec<rowan::NodeOrToken<rowan::GreenNode, rowan::GreenToken>> {
964    vec![
965        create_scalar_green(key).into(),
966        create_token_green(SyntaxKind::COLON, ":").into(),
967        create_token_green(SyntaxKind::WHITESPACE, " ").into(),
968        create_scalar_green(value).into(),
969        create_token_green(SyntaxKind::NEWLINE, "\n").into(),
970    ]
971}
972
973// Helper to create complete mapping entries like deb822-lossless Entry::new
974fn create_mapping_entry(key: &str, value: &str) -> SyntaxNode {
975    let mut builder = GreenNodeBuilder::new();
976    // Note: We don't wrap in a MAPPING_ENTRY node, just create the sequence of tokens
977    // This creates: KEY + COLON + WHITESPACE + SCALAR + NEWLINE
978    builder.token(SyntaxKind::VALUE.into(), key); // Key as VALUE token
979    builder.token(SyntaxKind::COLON.into(), ":");
980    builder.token(SyntaxKind::WHITESPACE.into(), " ");
981    builder.start_node(SyntaxKind::SCALAR.into());
982    builder.token(SyntaxKind::VALUE.into(), value);
983    builder.finish_node();
984    builder.token(SyntaxKind::NEWLINE.into(), "\n");
985    SyntaxNode::new_root_mut(builder.finish())
986}
987
988// Editing methods for Mapping
989impl Mapping {
990    /// Set a key-value pair with a scalar value, replacing if exists or adding if new
991    /// This method automatically escapes the key and value as needed.
992    pub fn set(&mut self, key: impl Into<ScalarValue>, value: impl Into<ScalarValue>) {
993        let key_scalar = key.into();
994        let value_scalar = value.into();
995        self.set_raw(&key_scalar.to_yaml_string(), &value_scalar.to_yaml_string());
996    }
997
998    /// Set a key-value pair with any YAML value type (scalar, sequence, mapping)
999    pub fn set_value(&mut self, key: impl Into<ScalarValue>, value: YamlValue) {
1000        let key_scalar = key.into();
1001        // TODO: Implement proper handling for non-scalar values
1002        // For now, convert to string representation
1003        self.set_raw(&key_scalar.to_yaml_string(), &value.to_yaml_string(0));
1004    }
1005
1006    /// Set a key-value pair, replacing if exists or adding if new
1007    /// This is the low-level method that doesn't escape values.
1008    pub fn set_raw(&mut self, key: &str, value: &str) {
1009        // Find existing key-value pair by looking for scalar nodes
1010        for (i, child) in self.0.children().enumerate() {
1011            if child.kind() == SyntaxKind::SCALAR {
1012                if let Some(scalar) = Scalar::cast(child.clone()) {
1013                    if scalar.value().trim() == key {
1014                        // Found existing key, need to find the complete entry range to replace
1015                        // For now, let's create a new entry and replace the entire mapping
1016                        // This is a simplified approach - a full implementation would be more surgical
1017                        let mut new_entries = Vec::new();
1018                        let mut found_key = false;
1019
1020                        // Collect all key-value pairs, replacing the matching one
1021                        for pair_result in self.pairs() {
1022                            if let (Some(k), Some(v_node)) = pair_result {
1023                                if k.value().trim() == key && !found_key {
1024                                    // Replace this entry
1025                                    let new_scalar = create_scalar_node(value);
1026                                    new_entries.push((k, new_scalar));
1027                                    found_key = true;
1028                                } else {
1029                                    new_entries.push((k, v_node));
1030                                }
1031                            }
1032                        }
1033
1034                        if found_key {
1035                            // Rebuild the entire mapping - this is inefficient but correct
1036                            self.rebuild_from_pairs(new_entries);
1037                            return;
1038                        }
1039                    }
1040                }
1041            }
1042        }
1043
1044        // Key doesn't exist, add new entry
1045        // For simplicity, rebuild entire mapping with new entry added
1046        let mut pairs = Vec::new();
1047        for (k, v) in self.pairs() {
1048            if let (Some(key_scalar), Some(value_node)) = (k, v) {
1049                pairs.push((key_scalar, value_node));
1050            }
1051        }
1052
1053        // Add new pair
1054        let key_scalar = Scalar(create_scalar_node(key));
1055        let value_node = create_scalar_node(value);
1056        pairs.push((key_scalar, value_node));
1057
1058        self.rebuild_from_pairs(pairs);
1059    }
1060
1061    // Helper to rebuild mapping from pairs - similar to deb822 approach
1062    fn rebuild_from_pairs(&mut self, pairs: Vec<(Scalar, SyntaxNode)>) {
1063        let mut builder = GreenNodeBuilder::new();
1064        builder.start_node(SyntaxKind::MAPPING.into());
1065
1066        for (key_scalar, value_node) in pairs {
1067            // Create key token
1068            builder.token(SyntaxKind::VALUE.into(), &key_scalar.value());
1069            builder.token(SyntaxKind::COLON.into(), ":");
1070            builder.token(SyntaxKind::WHITESPACE.into(), " ");
1071
1072            // Add value
1073            if value_node.kind() == SyntaxKind::SCALAR {
1074                builder.start_node(SyntaxKind::SCALAR.into());
1075                // Extract the VALUE token from the scalar node
1076                for token in value_node.children_with_tokens() {
1077                    if let Some(token) = token.as_token() {
1078                        if token.kind() == SyntaxKind::VALUE {
1079                            builder.token(SyntaxKind::VALUE.into(), token.text());
1080                        }
1081                    }
1082                }
1083                builder.finish_node();
1084            }
1085
1086            builder.token(SyntaxKind::NEWLINE.into(), "\n");
1087        }
1088
1089        builder.finish_node();
1090        let new_mapping = SyntaxNode::new_root_mut(builder.finish());
1091
1092        // Replace the entire mapping content
1093        let child_count = self.0.children_with_tokens().count();
1094        self.0
1095            .splice_children(0..child_count, vec![new_mapping.into()]);
1096    }
1097
1098    /// Remove a key-value pair
1099    pub fn remove(&mut self, key: &str) -> bool {
1100        let children: Vec<_> = self.0.children_with_tokens().collect();
1101        let mut removal_range = None;
1102
1103        for (i, child) in children.iter().enumerate() {
1104            if let Some(node) = child.as_node() {
1105                if node.kind() == SyntaxKind::KEY {
1106                    if node.text() == key {
1107                        // Found the key, find the complete entry to remove
1108                        let start = i;
1109                        let mut end = i + 1;
1110
1111                        // Skip colon, whitespace, value, and newline
1112                        while end < children.len() {
1113                            if let Some(token) = children[end].as_token() {
1114                                end += 1;
1115                                if token.kind() == SyntaxKind::NEWLINE {
1116                                    break;
1117                                }
1118                            } else {
1119                                end += 1;
1120                            }
1121                        }
1122
1123                        removal_range = Some(start..end);
1124                        break;
1125                    }
1126                }
1127            }
1128        }
1129
1130        if let Some(range) = removal_range {
1131            self.0.splice_children(range, vec![]);
1132            true
1133        } else {
1134            false
1135        }
1136    }
1137
1138    /// Rename a key while preserving its value and formatting, with proper escaping
1139    pub fn rename_key(&mut self, old_key: &str, new_key: impl Into<ScalarValue>) -> bool {
1140        let new_key_scalar = new_key.into();
1141        self.rename_key_raw(old_key, &new_key_scalar.to_yaml_string())
1142    }
1143
1144    /// Rename a key while preserving its value and formatting
1145    /// This is the low-level method that doesn't escape the new key.
1146    pub fn rename_key_raw(&mut self, old_key: &str, new_key: &str) -> bool {
1147        let children: Vec<_> = self.0.children().collect();
1148
1149        for (i, child) in children.iter().enumerate() {
1150            if child.kind() == SyntaxKind::KEY {
1151                // Check if this KEY node contains our target key
1152                let key_text = child.text().to_string();
1153                if key_text == old_key {
1154                    // Create a new KEY node with the new key
1155                    let mut builder = GreenNodeBuilder::new();
1156                    builder.start_node(SyntaxKind::KEY.into());
1157                    builder.token(SyntaxKind::STRING.into(), new_key);
1158                    builder.finish_node();
1159                    let new_key_node = SyntaxNode::new_root_mut(builder.finish());
1160
1161                    self.0.splice_children(i..i + 1, vec![new_key_node.into()]);
1162                    return true;
1163                }
1164            }
1165        }
1166        false
1167    }
1168
1169    /// Set a value at a nested path with proper escaping (e.g., "db.host" to set db: {host: value})
1170    pub fn set_path(&mut self, path: &str, value: impl Into<ScalarValue>) {
1171        let value_scalar = value.into();
1172        self.set_path_raw(path, &value_scalar.to_yaml_string());
1173    }
1174
1175    /// Set a value at a nested path (e.g., "db.host" to set db: {host: value})
1176    /// This is the low-level method that doesn't escape values.
1177    pub fn set_path_raw(&mut self, path: &str, value: &str) {
1178        let parts: Vec<&str> = path.split('.').collect();
1179        if parts.len() == 1 {
1180            self.set_raw(parts[0], value);
1181            return;
1182        }
1183
1184        // Navigate to nested structure - TODO: implement properly
1185        let (_first, _rest) = parts.split_first().unwrap();
1186        // if let Some(nested) = self.get_mapping(first) {
1187        //     nested.set_path(&rest.join("."), value);
1188        // }
1189    }
1190}
1191
1192fn create_scalar_node(value: &str) -> SyntaxNode {
1193    SyntaxNode::new_root_mut(create_scalar_green(value))
1194}
1195
1196fn create_sequence_item_green(
1197    value: &str,
1198) -> Vec<rowan::NodeOrToken<rowan::GreenNode, rowan::GreenToken>> {
1199    vec![
1200        create_token_green(SyntaxKind::DASH, "-").into(),
1201        create_token_green(SyntaxKind::WHITESPACE, " ").into(),
1202        create_scalar_green(value).into(),
1203        create_token_green(SyntaxKind::NEWLINE, "\n").into(),
1204    ]
1205}
1206
1207// Editing methods for Sequence
1208impl Sequence {
1209    /// Add an item to the end of the sequence
1210    pub fn push(&mut self, value: &str) {
1211        let new_item_tokens = create_sequence_item_green(value);
1212        let child_count = self.0.children_with_tokens().count();
1213        // Convert green nodes/tokens to syntax nodes/tokens
1214        let syntax_elements: Vec<_> = new_item_tokens
1215            .into_iter()
1216            .map(|element| {
1217                match element {
1218                    rowan::NodeOrToken::Node(green_node) => {
1219                        SyntaxNode::new_root_mut(green_node).into()
1220                    }
1221                    rowan::NodeOrToken::Token(green_token) => {
1222                        // We need to create a SyntaxToken from GreenToken - this is not straightforward
1223                        // Let's create a temporary node to contain the token
1224                        let mut builder = GreenNodeBuilder::new();
1225                        builder.start_node(SyntaxKind::ROOT.into());
1226                        builder.token(green_token.kind(), green_token.text());
1227                        builder.finish_node();
1228                        let temp_node = SyntaxNode::new_root_mut(builder.finish());
1229                        temp_node.first_token().unwrap().into()
1230                    }
1231                }
1232            })
1233            .collect();
1234        self.0
1235            .splice_children(child_count..child_count, syntax_elements);
1236    }
1237
1238    /// Insert an item at a specific position
1239    pub fn insert(&mut self, index: usize, value: &str) {
1240        let children: Vec<_> = self.0.children_with_tokens().collect();
1241        let mut item_count = 0;
1242        let mut insert_pos = children.len();
1243
1244        // Find the position to insert at
1245        for (i, child) in children.iter().enumerate() {
1246            if let Some(token) = child.as_token() {
1247                if token.kind() == SyntaxKind::DASH {
1248                    if item_count == index {
1249                        insert_pos = i;
1250                        break;
1251                    }
1252                    item_count += 1;
1253                }
1254            }
1255        }
1256
1257        let new_item_tokens = create_sequence_item_green(value);
1258        // Convert green nodes/tokens to syntax nodes/tokens
1259        let syntax_elements: Vec<_> = new_item_tokens
1260            .into_iter()
1261            .map(|element| match element {
1262                rowan::NodeOrToken::Node(green_node) => SyntaxNode::new_root_mut(green_node).into(),
1263                rowan::NodeOrToken::Token(green_token) => {
1264                    let mut builder = GreenNodeBuilder::new();
1265                    builder.start_node(SyntaxKind::ROOT.into());
1266                    builder.token(green_token.kind(), green_token.text());
1267                    builder.finish_node();
1268                    let temp_node = SyntaxNode::new_root_mut(builder.finish());
1269                    temp_node.first_token().unwrap().into()
1270                }
1271            })
1272            .collect();
1273        self.0
1274            .splice_children(insert_pos..insert_pos, syntax_elements);
1275    }
1276
1277    /// Replace an item at a specific position  
1278    pub fn set(&mut self, index: usize, value: &str) -> bool {
1279        let children: Vec<_> = self.0.children_with_tokens().collect();
1280        let mut item_count = 0;
1281
1282        for (i, child) in children.iter().enumerate() {
1283            if let Some(token) = child.as_token() {
1284                if token.kind() == SyntaxKind::DASH {
1285                    if item_count == index {
1286                        // Find the value node after this dash (skip whitespace)
1287                        let mut value_index = None;
1288                        for (j, next_child) in children.iter().enumerate().skip(i + 1) {
1289                            if let Some(node) = next_child.as_node() {
1290                                if node.kind() == SyntaxKind::SCALAR {
1291                                    value_index = Some(j);
1292                                    break;
1293                                }
1294                            }
1295                        }
1296
1297                        if let Some(val_idx) = value_index {
1298                            // Replace just the value node
1299                            let new_value_node =
1300                                SyntaxNode::new_root_mut(create_scalar_green(value));
1301                            self.0
1302                                .splice_children(val_idx..val_idx + 1, vec![new_value_node.into()]);
1303                            return true;
1304                        }
1305                    }
1306                    item_count += 1;
1307                }
1308            }
1309        }
1310        false
1311    }
1312
1313    /// Remove an item at a specific position
1314    pub fn remove(&mut self, index: usize) -> Option<String> {
1315        let children: Vec<_> = self.0.children().collect();
1316
1317        // Handle flow-style sequences [item1, item2]
1318        if self.is_flow_style() {
1319            let mut item_count = 0;
1320            for (i, child) in children.iter().enumerate() {
1321                if child.kind() == SyntaxKind::SCALAR {
1322                    if item_count == index {
1323                        if let Some(scalar) = Scalar::cast(child.clone()) {
1324                            let value = scalar.value();
1325
1326                            // Remove the scalar and surrounding punctuation
1327                            let mut start = i;
1328                            let mut end = i + 1;
1329
1330                            // Remove preceding comma if not first item
1331                            if start > 0 && children[start - 1].kind() == SyntaxKind::COMMA {
1332                                start -= 1;
1333                            }
1334                            // Remove following comma if exists
1335                            else if end < children.len()
1336                                && children[end].kind() == SyntaxKind::COMMA
1337                            {
1338                                end += 1;
1339                            }
1340
1341                            self.0.splice_children(start..end, vec![]);
1342                            return Some(value);
1343                        }
1344                    }
1345                    item_count += 1;
1346                }
1347            }
1348        } else {
1349            // Handle block-style sequences with dashes
1350            let mut item_count = 0;
1351            for (i, child) in children.iter().enumerate() {
1352                if child.kind() == SyntaxKind::DASH {
1353                    if item_count == index {
1354                        // Find the complete item to remove (dash, space, value, newline)
1355                        let start = i;
1356                        let mut end = i + 1;
1357                        let mut removed_value = None;
1358
1359                        while end < children.len() {
1360                            let child_kind = children[end].kind();
1361                            if child_kind == SyntaxKind::SCALAR {
1362                                if let Some(scalar) = Scalar::cast(children[end].clone()) {
1363                                    removed_value = Some(scalar.value());
1364                                }
1365                            }
1366                            end += 1;
1367                            if child_kind == SyntaxKind::NEWLINE {
1368                                break;
1369                            }
1370                        }
1371
1372                        self.0.splice_children(start..end, vec![]);
1373                        return removed_value;
1374                    }
1375                    item_count += 1;
1376                }
1377            }
1378        }
1379        None
1380    }
1381
1382    /// Check if this sequence is in flow style [item1, item2]
1383    fn is_flow_style(&self) -> bool {
1384        self.0
1385            .children()
1386            .any(|child| child.kind() == SyntaxKind::LEFT_BRACKET)
1387    }
1388}
1389
1390// Editing methods for Scalar
1391impl Scalar {
1392    /// Set the value of this scalar
1393    pub fn set_value(&mut self, value: &str) {
1394        let children_count = self.0.children_with_tokens().count();
1395        // Create a temporary node to wrap the token and extract a SyntaxToken
1396        let mut builder = GreenNodeBuilder::new();
1397        builder.start_node(SyntaxKind::ROOT.into());
1398        builder.token(SyntaxKind::VALUE.into(), value);
1399        builder.finish_node();
1400        let temp_node = SyntaxNode::new_root_mut(builder.finish());
1401        let new_token = temp_node.first_token().unwrap();
1402        self.0
1403            .splice_children(0..children_count, vec![new_token.into()]);
1404    }
1405}