use std::sync::Arc;
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
use syntect::util::as_24_bit_terminal_escaped;
use termimad::crossterm::style::{Attribute, Color};
use termimad::{CompoundStyle, LineStyle, MadSkin};
pub mod brand {
pub const PURPLE: &str = "\x1b[38;5;141m";
pub const MAGENTA: &str = "\x1b[38;5;207m";
pub const LIGHT_PURPLE: &str = "\x1b[38;5;183m";
pub const CYAN: &str = "\x1b[38;5;51m";
pub const TEXT: &str = "\x1b[38;5;252m";
pub const DIM: &str = "\x1b[38;5;245m";
pub const SUCCESS: &str = "\x1b[38;5;114m";
pub const YELLOW: &str = "\x1b[38;5;221m";
pub const PEACH: &str = "\x1b[38;5;216m";
pub const LIGHT_PEACH: &str = "\x1b[38;5;223m";
pub const CORAL: &str = "\x1b[38;5;209m";
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const ITALIC: &str = "\x1b[3m";
}
#[derive(Clone)]
pub struct SyntaxHighlighter {
syntax_set: Arc<SyntaxSet>,
theme_set: Arc<ThemeSet>,
}
impl Default for SyntaxHighlighter {
fn default() -> Self {
Self {
syntax_set: Arc::new(SyntaxSet::load_defaults_newlines()),
theme_set: Arc::new(ThemeSet::load_defaults()),
}
}
}
impl SyntaxHighlighter {
pub fn highlight(&self, code: &str, lang: &str) -> String {
let syntax = self
.syntax_set
.find_syntax_by_token(lang)
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
let theme = &self.theme_set.themes["base16-ocean.dark"];
let mut hl = HighlightLines::new(syntax, theme);
code.lines()
.filter_map(|line| hl.highlight_line(line, &self.syntax_set).ok())
.map(|ranges| format!("{}\x1b[0m", as_24_bit_terminal_escaped(&ranges, false)))
.collect::<Vec<_>>()
.join("\n")
}
}
#[derive(Clone, Debug)]
struct CodeBlock {
code: String,
lang: String,
}
struct CodeBlockParser {
markdown: String,
blocks: Vec<CodeBlock>,
}
impl CodeBlockParser {
fn parse(content: &str) -> Self {
let mut blocks = Vec::new();
let mut result = String::new();
let mut in_code_block = false;
let mut code_lines: Vec<&str> = Vec::new();
let mut current_lang = String::new();
for line in content.lines() {
if line.trim_start().starts_with("```") {
if in_code_block {
result.push_str(&format!("\x00{}\x00\n", blocks.len()));
blocks.push(CodeBlock {
code: code_lines.join("\n"),
lang: current_lang.clone(),
});
code_lines.clear();
current_lang.clear();
in_code_block = false;
} else {
current_lang = line
.trim_start()
.strip_prefix("```")
.unwrap_or("")
.to_string();
in_code_block = true;
}
} else if in_code_block {
code_lines.push(line);
} else {
result.push_str(line);
result.push('\n');
}
}
if in_code_block && !code_lines.is_empty() {
result.push_str(&format!("\x00{}\x00\n", blocks.len()));
blocks.push(CodeBlock {
code: code_lines.join("\n"),
lang: current_lang,
});
}
Self {
markdown: result,
blocks,
}
}
fn markdown(&self) -> &str {
&self.markdown
}
fn restore(&self, highlighter: &SyntaxHighlighter, mut rendered: String) -> String {
for (i, block) in self.blocks.iter().enumerate() {
let highlighted = highlighter.highlight(&block.code, &block.lang);
let code_block = format!("\n{}\n", highlighted);
rendered = rendered.replace(&format!("\x00{i}\x00"), &code_block);
}
rendered
}
}
pub struct MarkdownFormat {
skin: MadSkin,
highlighter: SyntaxHighlighter,
}
impl Default for MarkdownFormat {
fn default() -> Self {
Self::new()
}
}
impl MarkdownFormat {
#[allow(clippy::field_reassign_with_default)]
pub fn new() -> Self {
let mut skin = MadSkin::default();
skin.inline_code = CompoundStyle::new(Some(Color::Cyan), None, Default::default());
skin.code_block = LineStyle::new(
CompoundStyle::new(None, None, Default::default()),
Default::default(),
);
let mut h1_style = CompoundStyle::new(Some(Color::Magenta), None, Default::default());
h1_style.add_attr(Attribute::Bold);
skin.headers[0] = LineStyle::new(h1_style.clone(), Default::default());
skin.headers[1] = LineStyle::new(h1_style.clone(), Default::default());
let h3_style = CompoundStyle::new(Some(Color::Magenta), None, Default::default());
skin.headers[2] = LineStyle::new(h3_style, Default::default());
let mut bold_style = CompoundStyle::new(Some(Color::Magenta), None, Default::default());
bold_style.add_attr(Attribute::Bold);
skin.bold = bold_style;
skin.italic = CompoundStyle::with_attr(Attribute::Italic);
let mut strikethrough = CompoundStyle::with_attr(Attribute::CrossedOut);
strikethrough.add_attr(Attribute::Dim);
skin.strikeout = strikethrough;
Self {
skin,
highlighter: SyntaxHighlighter::default(),
}
}
pub fn render(&self, content: impl Into<String>) -> String {
let content = content.into();
let content = content.trim();
if content.is_empty() {
return String::new();
}
let parsed = CodeBlockParser::parse(content);
let rendered = self.skin.term_text(parsed.markdown()).to_string();
parsed
.restore(&self.highlighter, rendered)
.trim()
.to_string()
}
}
pub struct ResponseFormatter;
impl ResponseFormatter {
pub fn print_response(text: &str) {
println!();
Self::print_header();
println!();
let formatter = MarkdownFormat::new();
let rendered = formatter.render(text);
for line in rendered.lines() {
println!(" {}", line);
}
println!();
Self::print_separator();
}
fn print_header() {
print!("{}{}â•─ 🤖 Syncable AI ", brand::PURPLE, brand::BOLD);
println!(
"{}─────────────────────────────────────────────────────╮{}",
brand::DIM,
brand::RESET
);
}
fn print_separator() {
println!(
"{}╰───────────────────────────────────────────────────────────────────╯{}",
brand::DIM,
brand::RESET
);
}
}
pub struct SimpleResponse;
impl SimpleResponse {
pub fn print(text: &str) {
println!();
println!(
"{}{} Syncable AI:{}",
brand::PURPLE,
brand::BOLD,
brand::RESET
);
let formatter = MarkdownFormat::new();
println!("{}", formatter.render(text));
println!();
}
}
pub struct ToolProgress {
tools_executed: Vec<ToolExecution>,
}
#[derive(Clone)]
struct ToolExecution {
name: String,
description: String,
status: ToolStatus,
}
#[derive(Clone, Copy)]
enum ToolStatus {
Running,
Success,
Error,
}
impl ToolProgress {
pub fn new() -> Self {
Self {
tools_executed: Vec::new(),
}
}
pub fn tool_start(&mut self, name: &str, description: &str) {
self.tools_executed.push(ToolExecution {
name: name.to_string(),
description: description.to_string(),
status: ToolStatus::Running,
});
self.redraw();
}
pub fn tool_complete(&mut self, success: bool) {
if let Some(tool) = self.tools_executed.last_mut() {
tool.status = if success {
ToolStatus::Success
} else {
ToolStatus::Error
};
}
self.redraw();
}
fn redraw(&self) {
for tool in &self.tools_executed {
let (icon, color) = match tool.status {
ToolStatus::Running => ("", brand::YELLOW),
ToolStatus::Success => ("", brand::SUCCESS),
ToolStatus::Error => ("", "\x1b[38;5;196m"),
};
println!(
" {} {}{}{} {}{}{}",
icon,
color,
tool.name,
brand::RESET,
brand::DIM,
tool.description,
brand::RESET
);
}
}
pub fn print_summary(&self) {
if !self.tools_executed.is_empty() {
let success_count = self
.tools_executed
.iter()
.filter(|t| matches!(t.status, ToolStatus::Success))
.count();
println!(
"\n{} {} tools executed successfully{}",
brand::DIM,
success_count,
brand::RESET
);
}
}
}
impl Default for ToolProgress {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_markdown_render_empty() {
let formatter = MarkdownFormat::new();
assert!(formatter.render("").is_empty());
}
#[test]
fn test_markdown_render_simple() {
let formatter = MarkdownFormat::new();
let result = formatter.render("Hello world");
assert!(!result.is_empty());
}
#[test]
fn test_code_block_extraction() {
let parsed = CodeBlockParser::parse("Hello\n```rust\nfn main() {}\n```\nWorld");
assert_eq!(parsed.blocks.len(), 1);
assert_eq!(parsed.blocks[0].lang, "rust");
assert_eq!(parsed.blocks[0].code, "fn main() {}");
}
#[test]
fn test_syntax_highlighter() {
let hl = SyntaxHighlighter::default();
let result = hl.highlight("fn main() {}", "rust");
assert!(result.contains("\x1b["));
}
}