semantic_code_edit_mcp/languages/
python.rs1use crate::languages::{LanguageCommon, LanguageName, traits::LanguageEditor};
2use anyhow::Result;
3use rustpython_parser::ast::TextSize;
4
5pub fn language() -> Result<LanguageCommon> {
6 let language = tree_sitter_python::LANGUAGE.into();
7 let editor = Box::new(PythonEditor);
8
9 Ok(LanguageCommon {
10 name: LanguageName::Python,
11 file_extensions: &["py", "pyi"],
12 language,
13 editor,
14 validation_query: None,
15 })
16}
17
18pub struct PythonEditor;
19
20impl PythonEditor {
21 pub fn new() -> Self {
22 Self
23 }
24}
25
26impl Default for PythonEditor {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl LanguageEditor for PythonEditor {
33 fn collect_errors(&self, _tree: &tree_sitter::Tree, content: &str) -> Vec<usize> {
34 if let Some(err) =
35 rustpython_parser::parse(content, rustpython_parser::Mode::Module, "anonymous.py").err()
36 {
37 let converter = LineConverter::new(content);
38 vec![converter.textsize_to_line(err.offset)]
39 } else {
40 vec![]
41 }
42 }
43}
44
45struct LineConverter {
46 newline_positions: Vec<usize>,
47}
48
49impl LineConverter {
50 fn new(text: &str) -> Self {
51 let newline_positions = std::iter::once(0)
52 .chain(text.match_indices('\n').map(|(i, _)| i + 1))
53 .chain(std::iter::once(text.len())) .collect();
55
56 Self { newline_positions }
57 }
58
59 fn textsize_to_line(&self, offset: TextSize) -> usize {
60 let byte_offset = usize::from(offset); match self.newline_positions.binary_search(&byte_offset) {
62 Ok(line) => line + 1,
63 Err(line) => line,
64 }
65 }
66}