LCOV - code coverage report
Current view: top level - src - syntax.rs (source / functions) Coverage Total Hit
Test: Hunky Coverage Lines: 98.1 % 54 53
Test Date: 2026-02-25 04:31:59 Functions: 100.0 % 9 9

            Line data    Source code
       1              : use ratatui::style::Color;
       2              : use std::path::Path;
       3              : use syntect::easy::HighlightLines;
       4              : use syntect::highlighting::ThemeSet;
       5              : use syntect::parsing::SyntaxSet;
       6              : 
       7              : pub struct SyntaxHighlighter {
       8              :     syntax_set: SyntaxSet,
       9              :     theme_set: ThemeSet,
      10              : }
      11              : 
      12              : impl SyntaxHighlighter {
      13           35 :     pub fn new() -> Self {
      14           35 :         Self {
      15           35 :             syntax_set: SyntaxSet::load_defaults_newlines(),
      16           35 :             theme_set: ThemeSet::load_defaults(),
      17           35 :         }
      18           35 :     }
      19              : 
      20              :     /// Get a highlighter for a specific file that can be used to highlight multiple lines sequentially
      21           11 :     pub fn create_highlighter(&self, file_path: &Path) -> FileHighlighter<'_> {
      22           11 :         let syntax = self
      23           11 :             .syntax_set
      24           11 :             .find_syntax_for_file(file_path)
      25           11 :             .ok()
      26           11 :             .flatten()
      27           11 :             .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
      28              : 
      29           11 :         let theme = &self.theme_set.themes["base16-ocean.dark"];
      30              : 
      31           11 :         FileHighlighter {
      32           11 :             highlighter: HighlightLines::new(syntax, theme),
      33           11 :             syntax_set: &self.syntax_set,
      34           11 :         }
      35           11 :     }
      36              : 
      37              :     #[allow(dead_code)]
      38            1 :     pub fn detect_language(&self, file_path: &Path) -> Option<String> {
      39            1 :         self.syntax_set
      40            1 :             .find_syntax_for_file(file_path)
      41            1 :             .ok()
      42            1 :             .flatten()
      43            1 :             .map(|s| s.name.clone())
      44            1 :     }
      45              : }
      46              : 
      47              : pub struct FileHighlighter<'a> {
      48              :     highlighter: HighlightLines<'a>,
      49              :     syntax_set: &'a SyntaxSet,
      50              : }
      51              : 
      52              : impl<'a> FileHighlighter<'a> {
      53              :     /// Highlight a single line (must be called sequentially for proper context)
      54           30 :     pub fn highlight_line(&mut self, line: &str) -> Vec<(Color, String)> {
      55           30 :         let mut result = Vec::new();
      56              : 
      57           30 :         if let Ok(ranges) = self.highlighter.highlight_line(line, self.syntax_set) {
      58          134 :             for (style, text) in ranges {
      59          134 :                 let color = syntect_color_to_ratatui(style.foreground);
      60          134 :                 result.push((color, text.to_string()));
      61          134 :             }
      62            0 :         }
      63              : 
      64           30 :         result
      65           30 :     }
      66              : }
      67              : 
      68              : /// Convert syntect color to ratatui color
      69              : /// Uses 256-color palette for better terminal compatibility (macOS Terminal.app)
      70          134 : fn syntect_color_to_ratatui(color: syntect::highlighting::Color) -> Color {
      71          134 :     rgb_to_ansi256(color.r, color.g, color.b)
      72          134 : }
      73              : 
      74              : /// Convert RGB color to nearest ANSI 256-color palette index
      75              : /// This provides better compatibility with terminals that don't support 24-bit true color
      76          136 : fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> Color {
      77              :     // Check if it's a grayscale color
      78          136 :     let max_diff = r.abs_diff(g).max(g.abs_diff(b)).max(r.abs_diff(b));
      79              : 
      80          136 :     if max_diff < 10 {
      81              :         // It's grayscale - use the 24 grayscale colors (232-255)
      82              :         // Map 0-255 to 0-23
      83            1 :         let gray_index = ((r as u16 * 23) / 255) as u8;
      84            1 :         return Color::Indexed(232 + gray_index);
      85          135 :     }
      86              : 
      87              :     // Map to 6x6x6 color cube (16-231)
      88              :     // Each component is mapped to 0-5
      89          135 :     let r_index = (r as u16 * 5 / 255) as u8;
      90          135 :     let g_index = (g as u16 * 5 / 255) as u8;
      91          135 :     let b_index = (b as u16 * 5 / 255) as u8;
      92              : 
      93          135 :     let index = 16 + 36 * r_index + 6 * g_index + b_index;
      94          135 :     Color::Indexed(index)
      95          136 : }
      96              : 
      97              : impl Default for SyntaxHighlighter {
      98            1 :     fn default() -> Self {
      99            1 :         Self::new()
     100            1 :     }
     101              : }
     102              : 
     103              : #[cfg(test)]
     104              : #[path = "../tests/syntax.rs"]
     105              : mod tests;
        

Generated by: LCOV version 2.0-1