use regex::{Regex, RegexBuilder};
use std::sync::OnceLock;
use tower_lsp::lsp_types::Url;
use tracing::{debug, instrument};
use crate::domain::error::{DomainError, DomainResult};
use crate::lsp::domain::{LanguageInfo, LanguageRegistry, Snippet};
static LINE_COMMENT_START: OnceLock<Regex> = OnceLock::new();
static LINE_COMMENT_END: OnceLock<Regex> = OnceLock::new();
static RUST_INDENT: OnceLock<Regex> = OnceLock::new();
fn get_line_comment_start() -> &'static Regex {
LINE_COMMENT_START
.get_or_init(|| Regex::new(r"^(\s*)//\s*(.*)$").expect("compile line comment start regex"))
}
fn get_line_comment_end() -> &'static Regex {
LINE_COMMENT_END.get_or_init(|| {
Regex::new(r"^(.+?)(\s+)//\s*(.*)$").expect("compile line comment end regex")
})
}
fn get_rust_indent() -> &'static Regex {
RUST_INDENT.get_or_init(|| Regex::new(r"^( {4})+").expect("compile rust indentation regex"))
}
pub struct LanguageTranslator;
impl LanguageTranslator {
#[instrument(skip(snippet))]
pub fn translate_snippet(
snippet: &Snippet,
language_id: &str,
uri: &Url,
) -> DomainResult<String> {
let content = if snippet.is_universal() {
debug!("Processing universal snippet: {}", snippet.title);
debug!("Content: {:?}", snippet.get_content());
Self::translate_rust_patterns(snippet.get_content(), language_id, uri)?
} else {
snippet.get_content().to_string()
};
debug!("Final translated content: {:?}", content);
Ok(content)
}
#[instrument(skip(content))]
pub fn translate_rust_patterns(
content: &str,
language_id: &str,
uri: &Url,
) -> DomainResult<String> {
let target_lang = LanguageRegistry::get_language_info(language_id);
debug!("Translating Rust patterns for language: {}", language_id);
debug!("Input content: {:?}", content);
debug!("Content length: {} bytes", content.len());
let mut processed_content =
Self::translate_rust_patterns_line_by_line(content, &target_lang).map_err(|e| {
DomainError::Other(format!("Failed to process content line by line: {}", e))
})?;
if let Some((target_start, target_end)) = &target_lang.block_comment {
let block_comment_regex = RegexBuilder::new(r"/\*(.*?)\*/")
.dot_matches_new_line(true)
.build()
.map_err(|e| {
DomainError::Other(format!("Failed to compile block comment regex: {}", e))
})?;
processed_content = block_comment_regex
.replace_all(&processed_content, |caps: ®ex::Captures| {
format!("{}{}{}", target_start, &caps[1], target_end)
})
.to_string();
}
if processed_content.contains("{{ filename }}") {
let filename = uri.path().split('/').next_back().unwrap_or("untitled");
processed_content = processed_content.replace("{{ filename }}", filename);
}
debug!("Rust pattern translation complete");
debug!("Final content: {:?}", processed_content);
debug!("Final content length: {} bytes", processed_content.len());
Ok(processed_content)
}
fn translate_rust_patterns_line_by_line(
content: &str,
target_lang: &LanguageInfo,
) -> DomainResult<String> {
let lines: Vec<&str> = content.split('\n').collect();
let mut processed_lines = Vec::new();
for line in lines {
let mut processed_line = line.to_string();
if let Some(target_comment) = &target_lang.line_comment {
if let Some(captures) = get_line_comment_start().captures(line) {
processed_line = format!("{}{} {}", &captures[1], target_comment, &captures[2]);
}
else if let Some(captures) = get_line_comment_end().captures(line) {
processed_line = format!(
"{}{}{} {}",
&captures[1], &captures[2], target_comment, &captures[3]
);
}
} else if let Some((block_start, block_end)) = &target_lang.block_comment {
if let Some(captures) = get_line_comment_start().captures(line) {
processed_line = format!(
"{}{} {} {}",
&captures[1], block_start, &captures[2], block_end
);
} else if let Some(captures) = get_line_comment_end().captures(line) {
processed_line = format!(
"{}{}{} {} {}",
&captures[1], &captures[2], block_start, &captures[3], block_end
);
}
}
if target_lang.indent_char != " " {
if let Some(captures) = get_rust_indent().captures(&processed_line) {
let rust_indent_count = captures[0].len() / 4;
let new_indent = target_lang.indent_char.repeat(rust_indent_count);
processed_line = processed_line.replacen(&captures[0], &new_indent, 1);
}
}
processed_lines.push(processed_line);
}
Ok(processed_lines.join("\n"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn given_universal_snippet_when_translating_then_processes_content() {
let snippet = Snippet::new(
1,
"Test Universal Snippet".to_string(),
"// This is a test".to_string(),
"Test description".to_string(),
vec!["universal".to_string(), "_snip_".to_string()],
);
let uri = Url::parse("file:///test.py").expect("parse URI");
let result = LanguageTranslator::translate_snippet(&snippet, "python", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert!(translated.contains("# This is a test"));
}
#[test]
fn given_regular_snippet_when_translating_then_returns_as_is() {
let snippet = Snippet::new(
1,
"Test Regular Snippet".to_string(),
"// This is a test".to_string(),
"Test description".to_string(),
vec!["rust".to_string(), "_snip_".to_string()],
);
let uri = Url::parse("file:///test.py").expect("parse URI");
let result = LanguageTranslator::translate_snippet(&snippet, "python", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert_eq!(translated, "// This is a test"); }
#[test]
fn given_rust_line_comments_when_translating_to_python_then_converts_correctly() {
let uri = Url::parse("file:///test.py").expect("parse URI");
let rust_content = r#"// This is a line comment
// Indented comment
let x = 5; // End of line comment"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri);
assert!(result.is_ok());
let python_result = result.expect("Python translation result");
assert!(python_result.contains("# This is a line comment"));
assert!(python_result.contains(" # Indented comment"));
assert!(python_result.contains("let x = 5; # End of line comment"));
}
#[test]
fn given_rust_block_comments_when_translating_to_python_then_converts_correctly() {
let uri = Url::parse("file:///test.py").expect("parse URI");
let rust_content = r#"/* This is a block comment */
/*
Multi-line
block comment
*/"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri);
assert!(result.is_ok());
let python_result = result.expect("Python translation result");
assert!(python_result.contains("\"\"\" This is a block comment \"\"\""));
assert!(python_result.contains("\"\"\"\nMulti-line\nblock comment\n\"\"\""));
}
#[test]
fn given_rust_indentation_when_translating_to_go_then_converts_to_tabs() {
let uri = Url::parse("file:///test.go").expect("parse URI");
let rust_content = r#"fn example() {
let x = 5;
let y = 10;
let z = 15;
}"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "go", &uri);
assert!(result.is_ok());
let go_result = result.expect("Go translation result");
assert!(go_result.contains("fn example() {"));
assert!(go_result.contains("\tlet x = 5;"));
assert!(go_result.contains("\t\tlet y = 10;"));
assert!(go_result.contains("\t\t\tlet z = 15;"));
}
#[test]
fn given_filename_template_when_translating_then_replaces_correctly() {
let uri = Url::parse("file:///path/to/example.rs").expect("parse URI");
let content = "// File: {{ filename }}";
let result = LanguageTranslator::translate_rust_patterns(content, "rust", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert!(translated.contains("// File: example.rs"));
}
}