Skip to main content

mq_edit/document/
document_type.rs

1use miette::Result;
2use mq_markdown::Markdown;
3
4use crate::document::{FileType, LineMap};
5
6/// Document-specific data based on file type
7#[derive(Debug, Clone)]
8pub enum DocumentType {
9    /// Markdown document with parsed AST and line mapping
10    Markdown {
11        /// Parsed markdown AST
12        ast: Markdown,
13        /// Maps visual lines to AST nodes
14        line_map: LineMap,
15    },
16    /// Code file with language identifier
17    Code {
18        /// Programming language identifier
19        language: String,
20    },
21    /// Plain text file (no special handling)
22    PlainText,
23}
24
25impl DocumentType {
26    /// Create a new Markdown document type
27    pub fn new_markdown(content: &str) -> Result<Self> {
28        let ast = Markdown::from_markdown_str(content)
29            .map_err(|e| miette::miette!("Failed to parse markdown: {}", e))?;
30        let line_map = LineMap::from_markdown(&ast);
31
32        Ok(Self::Markdown { ast, line_map })
33    }
34
35    /// Create a new Code document type
36    pub fn new_code(language: String) -> Self {
37        Self::Code { language }
38    }
39
40    /// Create a new PlainText document type
41    pub fn new_plain_text() -> Self {
42        Self::PlainText
43    }
44
45    /// Get the file type for this document
46    pub fn file_type(&self) -> FileType {
47        match self {
48            Self::Markdown { .. } => FileType::Markdown,
49            Self::Code { language } => FileType::Code(language.clone()),
50            Self::PlainText => FileType::PlainText,
51        }
52    }
53
54    /// Get the Markdown AST if this is a Markdown document
55    pub fn markdown_ast(&self) -> Option<&Markdown> {
56        match self {
57            Self::Markdown { ast, .. } => Some(ast),
58            _ => None,
59        }
60    }
61
62    /// Get the line map if this is a Markdown document
63    pub fn line_map(&self) -> Option<&LineMap> {
64        match self {
65            Self::Markdown { line_map, .. } => Some(line_map),
66            _ => None,
67        }
68    }
69
70    /// Get the language identifier if this is a Code document
71    pub fn language(&self) -> Option<&str> {
72        match self {
73            Self::Code { language } => Some(language.as_str()),
74            _ => None,
75        }
76    }
77
78    /// Rebuild document-specific structures after content change
79    ///
80    /// For Markdown documents, this reparses the AST and rebuilds the line map.
81    /// For Code and PlainText documents, this is a no-op.
82    pub fn rebuild(&mut self, content: &str) -> Result<()> {
83        match self {
84            Self::Markdown { ast, line_map } => {
85                let new_ast = Markdown::from_markdown_str(content)
86                    .map_err(|e| miette::miette!("Failed to reparse markdown: {}", e))?;
87                *ast = new_ast;
88                *line_map = LineMap::from_markdown(ast);
89                Ok(())
90            }
91            Self::Code { .. } | Self::PlainText => {
92                // No rebuild needed for code/plain text
93                Ok(())
94            }
95        }
96    }
97
98    /// Check if this document type supports AST-based operations
99    pub fn has_ast(&self) -> bool {
100        matches!(self, Self::Markdown { .. })
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_markdown_document_creation() {
110        let doc = DocumentType::new_markdown("# Hello World").unwrap();
111        assert!(doc.has_ast());
112        assert!(doc.markdown_ast().is_some());
113        assert!(doc.line_map().is_some());
114        assert_eq!(doc.file_type(), FileType::Markdown);
115    }
116
117    #[test]
118    fn test_code_document_creation() {
119        let doc = DocumentType::new_code("rust".to_string());
120        assert!(!doc.has_ast());
121        assert_eq!(doc.language(), Some("rust"));
122        assert_eq!(doc.file_type(), FileType::Code("rust".to_string()));
123    }
124
125    #[test]
126    fn test_plain_text_document_creation() {
127        let doc = DocumentType::new_plain_text();
128        assert!(!doc.has_ast());
129        assert_eq!(doc.file_type(), FileType::PlainText);
130    }
131
132    #[test]
133    fn test_markdown_rebuild() {
134        let mut doc = DocumentType::new_markdown("# Title").unwrap();
135        doc.rebuild("## Subtitle").unwrap();
136        assert!(doc.markdown_ast().is_some());
137    }
138}