semantic_code_edit_mcp/languages/
toml.rs

1use crate::languages::{LanguageCommon, LanguageName, traits::LanguageEditor};
2use anyhow::Result;
3use std::ops::Range;
4use taplo::rowan::{TextRange, TextSize};
5use tree_sitter::Tree;
6
7pub fn language() -> Result<LanguageCommon> {
8    let language = tree_sitter_toml_ng::LANGUAGE.into();
9    let editor = Box::new(TomlEditor::new());
10
11    Ok(LanguageCommon {
12        name: LanguageName::Toml,
13        file_extensions: &["toml"],
14        language,
15        editor,
16        validation_query: None,
17    })
18}
19
20pub struct TomlEditor;
21
22impl Default for TomlEditor {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl TomlEditor {
29    pub fn new() -> Self {
30        Self
31    }
32}
33
34impl LanguageEditor for TomlEditor {
35    fn format_code(&self, source: &str) -> Result<String> {
36        Ok(taplo::formatter::format(
37            source,
38            taplo::formatter::Options::default(),
39        ))
40    }
41
42    fn collect_errors(&self, _tree: &Tree, content: &str) -> Vec<usize> {
43        let converter = LineConverter::new(content);
44
45        taplo::parser::parse(content)
46            .errors
47            .into_iter()
48            .flat_map(|error| converter.range_to_lines(error.range))
49            .collect()
50    }
51}
52
53struct LineConverter {
54    newline_positions: Vec<usize>,
55}
56
57impl LineConverter {
58    fn new(text: &str) -> Self {
59        let newline_positions = std::iter::once(0)
60            .chain(text.match_indices('\n').map(|(i, _)| i + 1))
61            .chain(std::iter::once(text.len())) // End of file
62            .collect();
63
64        Self { newline_positions }
65    }
66
67    fn textsize_to_line(&self, offset: TextSize) -> usize {
68        let byte_offset = usize::from(offset); // Safe conversion
69        match self.newline_positions.binary_search(&byte_offset) {
70            Ok(line) => line + 1,
71            Err(line) => line,
72        }
73    }
74
75    fn range_to_lines(&self, range: TextRange) -> Range<usize> {
76        Range {
77            start: self.textsize_to_line(range.start()),
78            end: self.textsize_to_line(range.end()),
79        }
80    }
81}