semantic_code_edit_mcp/languages/
toml.rs1use 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())) .collect();
63
64 Self { newline_positions }
65 }
66
67 fn textsize_to_line(&self, offset: TextSize) -> usize {
68 let byte_offset = usize::from(offset); 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}