nanorust 0.1.0

A terminal-based text editor written in Rust, inspired by GNU nano
Documentation
use regex::Regex;
use std::path::Path;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
    Rust,
    Python,
    JavaScript,
    C,
    Cpp,
    Go,
    Plain,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightColor {
    Cyan,      // Keywords
    Yellow,    // Strings
    Green,     // Comments
    Magenta,   // Numbers
    Reset,     // Default
}

pub struct HighlightRule {
    pub pattern: Regex,
    pub color: HighlightColor,
}

pub struct SyntaxHighlighter {
    pub language: Language,
    pub rules: Vec<HighlightRule>,
}

impl SyntaxHighlighter {
    pub fn from_filename(filename: &str) -> Option<Self> {
        let path = Path::new(filename);
        let extension = path.extension()?.to_str()?;
        
        let language = match extension {
            "rs" => Language::Rust,
            "py" => Language::Python,
            "js" => Language::JavaScript,
            "c" | "h" => Language::C,
            "cpp" | "hpp" | "cc" | "cxx" | "hxx" => Language::Cpp,
            "go" => Language::Go,
            _ => return None,
        };
        
        Some(Self::new(language))
    }
    
    pub fn new(language: Language) -> Self {
        let rules = Self::create_rules(language);
        Self { language, rules }
    }
    
    fn create_rules(language: Language) -> Vec<HighlightRule> {
        match language {
            Language::Rust => Self::rust_rules(),
            Language::Python => Self::python_rules(),
            Language::JavaScript => Self::javascript_rules(),
            Language::C => Self::c_rules(),
            Language::Cpp => Self::cpp_rules(),
            Language::Go => Self::go_rules(),
            Language::Plain => Vec::new(),
        }
    }
    
    fn rust_rules() -> Vec<HighlightRule> {
        vec![
            // Comments (must come before other patterns)
            HighlightRule {
                pattern: Regex::new(r"//.*$").unwrap(),
                color: HighlightColor::Green,
            },
            HighlightRule {
                pattern: Regex::new(r"/\*.*?\*/").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Keywords
            HighlightRule {
                pattern: Regex::new(r"\b(fn|let|mut|pub|struct|enum|impl|trait|use|mod|const|static|match|if|else|loop|while|for|in|return|break|continue|as|ref|move|type|where|unsafe|async|await|dyn|crate|super|self|Self)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    fn python_rules() -> Vec<HighlightRule> {
        vec![
            // Comments
            HighlightRule {
                pattern: Regex::new(r"#.*$").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""""([^"\\]|\\.)*""""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Decorators
            HighlightRule {
                pattern: Regex::new(r"@\w+").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Keywords
            HighlightRule {
                pattern: Regex::new(r"\b(def|class|if|elif|else|for|while|import|from|return|yield|try|except|finally|with|as|pass|break|continue|raise|assert|lambda|global|nonlocal|del|and|or|not|in|is|None|True|False)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    fn javascript_rules() -> Vec<HighlightRule> {
        vec![
            // Comments
            HighlightRule {
                pattern: Regex::new(r"//.*$").unwrap(),
                color: HighlightColor::Green,
            },
            HighlightRule {
                pattern: Regex::new(r"/\*.*?\*/").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"`([^`\\]|\\.)*`").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Keywords
            HighlightRule {
                pattern: Regex::new(r"\b(function|const|let|var|if|else|for|while|return|class|extends|import|export|from|default|async|await|try|catch|finally|throw|new|this|super|static|get|set|typeof|instanceof|delete|void|yield|break|continue|switch|case|do)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    fn c_rules() -> Vec<HighlightRule> {
        vec![
            // Comments
            HighlightRule {
                pattern: Regex::new(r"//.*$").unwrap(),
                color: HighlightColor::Green,
            },
            HighlightRule {
                pattern: Regex::new(r"/\*.*?\*/").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Keywords
            HighlightRule {
                pattern: Regex::new(r"\b(int|char|float|double|void|long|short|unsigned|signed|struct|union|enum|typedef|sizeof|const|static|extern|auto|register|volatile|if|else|for|while|do|switch|case|default|break|continue|return|goto)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    fn cpp_rules() -> Vec<HighlightRule> {
        vec![
            // Comments
            HighlightRule {
                pattern: Regex::new(r"//.*$").unwrap(),
                color: HighlightColor::Green,
            },
            HighlightRule {
                pattern: Regex::new(r"/\*.*?\*/").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Keywords (C++ specific + C keywords)
            HighlightRule {
                pattern: Regex::new(r"\b(int|char|float|double|void|long|short|unsigned|signed|struct|union|enum|typedef|sizeof|const|static|extern|auto|register|volatile|if|else|for|while|do|switch|case|default|break|continue|return|goto|class|public|private|protected|virtual|namespace|using|template|typename|try|catch|throw|new|delete|this|operator|friend|inline|explicit|mutable|constexpr|nullptr)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    fn go_rules() -> Vec<HighlightRule> {
        vec![
            // Comments
            HighlightRule {
                pattern: Regex::new(r"//.*$").unwrap(),
                color: HighlightColor::Green,
            },
            HighlightRule {
                pattern: Regex::new(r"/\*.*?\*/").unwrap(),
                color: HighlightColor::Green,
            },
            // Strings
            HighlightRule {
                pattern: Regex::new(r#""([^"\\]|\\.)*""#).unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"`[^`]*`").unwrap(),
                color: HighlightColor::Yellow,
            },
            HighlightRule {
                pattern: Regex::new(r"'([^'\\]|\\.)*'").unwrap(),
                color: HighlightColor::Yellow,
            },
            // Keywords
            HighlightRule {
                pattern: Regex::new(r"\b(func|var|const|type|struct|interface|package|import|if|else|for|range|return|break|continue|switch|case|default|go|defer|chan|select|fallthrough|goto|map)\b").unwrap(),
                color: HighlightColor::Cyan,
            },
            // Numbers
            HighlightRule {
                pattern: Regex::new(r"\b\d+(\.\d+)?\b").unwrap(),
                color: HighlightColor::Magenta,
            },
        ]
    }
    
    pub fn highlight_line(&self, line: &str) -> Vec<(String, HighlightColor)> {
        if self.language == Language::Plain {
            return vec![(line.to_string(), HighlightColor::Reset)];
        }
        
        let mut segments = Vec::new();
        let mut last_end = 0;
        let mut matches: Vec<(usize, usize, HighlightColor)> = Vec::new();
        
        // Collect all matches
        for rule in &self.rules {
            for mat in rule.pattern.find_iter(line) {
                matches.push((mat.start(), mat.end(), rule.color));
            }
        }
        
        // Sort matches by start position, then by length (longer first)
        matches.sort_by(|a, b| {
            if a.0 == b.0 {
                (b.1 - b.0).cmp(&(a.1 - a.0))
            } else {
                a.0.cmp(&b.0)
            }
        });
        
        // Remove overlapping matches (keep first/longest)
        let mut filtered_matches = Vec::new();
        let mut covered_end = 0;
        for (start, end, color) in matches {
            if start >= covered_end {
                filtered_matches.push((start, end, color));
                covered_end = end;
            }
        }
        
        // Build segments
        for (start, end, color) in filtered_matches {
            if start > last_end {
                // Add unhighlighted text before this match
                segments.push((line[last_end..start].to_string(), HighlightColor::Reset));
            }
            // Add highlighted match
            segments.push((line[start..end].to_string(), color));
            last_end = end;
        }
        
        // Add remaining unhighlighted text
        if last_end < line.len() {
            segments.push((line[last_end..].to_string(), HighlightColor::Reset));
        }
        
        // If no segments, return the whole line
        if segments.is_empty() {
            segments.push((line.to_string(), HighlightColor::Reset));
        }
        
        segments
    }
}