thoth_cli/
utils.rs

1use crate::code_block_popup::CodeBlock;
2use anyhow::Result;
3use std::io::{BufRead, Write};
4use std::path::PathBuf;
5use std::{fs::File, io::BufReader};
6use tui_textarea::TextArea;
7
8pub fn extract_code_blocks(content: &str) -> Vec<CodeBlock> {
9    let mut code_blocks = Vec::new();
10    let mut in_code_block = false;
11    let mut current_block = String::new();
12    let mut current_language = String::new();
13    let mut start_line = 0;
14
15    for (i, line) in content.lines().enumerate() {
16        if line.trim().starts_with("```") {
17            if in_code_block {
18                // End of code block
19                code_blocks.push(CodeBlock::new(
20                    current_block.trim_end().to_string(),
21                    current_language.clone(),
22                    start_line,
23                    i,
24                ));
25                current_block.clear();
26                current_language.clear();
27                in_code_block = false;
28            } else {
29                // Start of code block
30                in_code_block = true;
31                start_line = i;
32
33                let lang_part = line.trim_start_matches('`').trim();
34                current_language = lang_part.to_string();
35            }
36        } else if in_code_block {
37            current_block.push_str(line);
38            current_block.push('\n');
39        }
40    }
41
42    if in_code_block && !current_block.is_empty() {
43        code_blocks.push(CodeBlock::new(
44            current_block.trim_end().to_string(),
45            current_language,
46            start_line,
47            content.lines().count() - 1,
48        ));
49    }
50
51    code_blocks
52}
53
54pub fn save_textareas(textareas: &[TextArea], titles: &[String], file_path: PathBuf) -> Result<()> {
55    let mut file = File::create(file_path)?;
56    for (textarea, title) in textareas.iter().zip(titles.iter()) {
57        writeln!(file, "# {}", title)?;
58        let content = textarea.lines().join("\n");
59        let mut in_code_block = false;
60        for line in content.lines() {
61            if line.trim().starts_with("```") {
62                in_code_block = !in_code_block;
63            }
64            if in_code_block || !line.starts_with('#') {
65                writeln!(file, "{}", line)?;
66            } else {
67                writeln!(file, "\\{}", line)?;
68            }
69        }
70    }
71    Ok(())
72}
73
74pub fn load_textareas(file_path: PathBuf) -> Result<(Vec<TextArea<'static>>, Vec<String>)> {
75    let file = File::open(file_path)?;
76    let reader = BufReader::new(file);
77    let mut textareas = Vec::with_capacity(10);
78    let mut titles = Vec::with_capacity(10);
79    let mut current_textarea = TextArea::default();
80    let mut current_title = String::new();
81    let mut in_code_block = false;
82    let mut is_first_line = true;
83
84    for line in reader.lines() {
85        let line = line?;
86        if !in_code_block && line.starts_with("# ") && is_first_line {
87            current_title = line[2..].to_string();
88            is_first_line = false;
89        } else {
90            if line.trim().starts_with("```") {
91                in_code_block = !in_code_block;
92            }
93            if in_code_block {
94                current_textarea.insert_str(&line);
95            } else if let Some(strip) = line.strip_prefix('\\') {
96                current_textarea.insert_str(strip);
97            } else if line.starts_with("# ") && !is_first_line {
98                if !current_title.is_empty() {
99                    textareas.push(current_textarea);
100                    titles.push(current_title);
101                }
102                current_textarea = TextArea::default();
103                current_title = line[2..].to_string();
104                is_first_line = true;
105                continue;
106            } else {
107                current_textarea.insert_str(&line);
108            }
109            current_textarea.insert_newline();
110            is_first_line = false;
111        }
112    }
113
114    if !current_title.is_empty() {
115        textareas.push(current_textarea);
116        titles.push(current_title);
117    }
118
119    Ok((textareas, titles))
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_extract_code_blocks() {
128        let content = r#"# Test Document
129        
130```rust
131fn main() {
132    println!("Hello, world!");
133}
134```
135
136Some text here
137
138```python
139def hello():
140    print("Hello")
141```"#;
142
143        let blocks = extract_code_blocks(content);
144        assert_eq!(blocks.len(), 2);
145        assert_eq!(blocks[0].language, "rust");
146        assert_eq!(
147            blocks[0].content,
148            "fn main() {\n    println!(\"Hello, world!\");\n}"
149        );
150        assert_eq!(blocks[1].language, "python");
151        assert_eq!(blocks[1].content, "def hello():\n    print(\"Hello\")");
152    }
153
154    #[test]
155    fn test_extract_empty_code_blocks() {
156        let content = "```rust\n```";
157        let blocks = extract_code_blocks(content);
158        assert_eq!(blocks.len(), 1);
159        assert_eq!(blocks[0].language, "rust");
160        assert_eq!(blocks[0].content, "");
161    }
162
163    #[test]
164    fn test_unclosed_code_block() {
165        let content = "```js\nlet x = 1;";
166        let blocks = extract_code_blocks(content);
167        assert_eq!(blocks.len(), 1);
168        assert_eq!(blocks[0].language, "js");
169        assert_eq!(blocks[0].content, "let x = 1;");
170    }
171}