use ratatui::style::{Color, Style}; #[derive(Clone, Copy, PartialEq, Eq)] pub enum TokenKind { Number, String, Comment, Keyword, StackOp, Operator, Sound, Param, Context, Default, } impl TokenKind { pub fn style(self) -> Style { match self { TokenKind::Number => Style::default().fg(Color::Rgb(255, 180, 100)), TokenKind::String => Style::default().fg(Color::Rgb(150, 220, 150)), TokenKind::Comment => Style::default().fg(Color::Rgb(100, 100, 100)), TokenKind::Keyword => Style::default().fg(Color::Rgb(220, 120, 220)), TokenKind::StackOp => Style::default().fg(Color::Rgb(120, 180, 220)), TokenKind::Operator => Style::default().fg(Color::Rgb(200, 200, 130)), TokenKind::Sound => Style::default().fg(Color::Rgb(100, 220, 200)), TokenKind::Param => Style::default().fg(Color::Rgb(180, 150, 220)), TokenKind::Context => Style::default().fg(Color::Rgb(220, 180, 120)), TokenKind::Default => Style::default().fg(Color::Rgb(200, 200, 200)), } } } pub struct Token { pub start: usize, pub end: usize, pub kind: TokenKind, } const STACK_OPS: &[&str] = &["dup", "drop", "swap", "over", "rot", "nip", "tuck"]; const OPERATORS: &[&str] = &[ "+", "-", "*", "/", "mod", "neg", "abs", "min", "max", "=", "<>", "<", ">", "<=", ">=", "and", "or", "not", ]; const KEYWORDS: &[&str] = &[ "if", "else", "then", "emit", "get", "set", "rand", "rrand", "seed", "cycle", "choose", "chance", "[", "]", ]; const SOUND: &[&str] = &["sound", "s"]; const CONTEXT: &[&str] = &[ "step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs", ]; const PARAMS: &[&str] = &[ "time", "repeat", "dur", "gate", "freq", "detune", "speed", "glide", "pw", "spread", "mult", "warp", "mirror", "harmonics", "timbre", "morph", "begin", "end", "gain", "postgain", "velocity", "pan", "attack", "decay", "sustain", "release", "lpf", "lpq", "lpe", "lpa", "lpd", "lps", "lpr", "hpf", "hpq", "hpe", "hpa", "hpd", "hps", "hpr", "bpf", "bpq", "bpe", "bpa", "bpd", "bps", "bpr", "ftype", "penv", "patt", "pdec", "psus", "prel", "vib", "vibmod", "vibshape", "fm", "fmh", "fmshape", "fme", "fma", "fmd", "fms", "fmr", "am", "amdepth", "amshape", "rm", "rmdepth", "rmshape", "phaser", "phaserdepth", "phasersweep", "phasercenter", "flanger", "flangerdepth", "flangerfeedback", "chorus", "chorusdepth", "chorusdelay", "comb", "combfreq", "combfeedback", "combdamp", "coarse", "crush", "fold", "wrap", "distort", "distortvol", "delay", "delaytime", "delayfeedback", "delaytype", "verb", "verbdecay", "verbdamp", "verbpredelay", "verbdiff", "voice", "orbit", "note", "size", "n", "cut", "reset", ]; pub fn tokenize_line(line: &str) -> Vec { let mut tokens = Vec::new(); let mut chars = line.char_indices().peekable(); while let Some((start, c)) = chars.next() { if c.is_whitespace() { continue; } if c == '(' { let end = line.len(); let comment_end = line[start..] .find(')') .map(|i| start + i + 1) .unwrap_or(end); tokens.push(Token { start, end: comment_end, kind: TokenKind::Comment, }); while let Some((i, _)) = chars.peek() { if *i >= comment_end { break; } chars.next(); } continue; } if c == '"' { let mut end = start + 1; while let Some((i, ch)) = chars.next() { end = i + ch.len_utf8(); if ch == '"' { break; } } tokens.push(Token { start, end, kind: TokenKind::String, }); continue; } let mut end = start + c.len_utf8(); while let Some((i, ch)) = chars.peek() { if ch.is_whitespace() { break; } end = *i + ch.len_utf8(); chars.next(); } let word = &line[start..end]; let kind = classify_word(word); tokens.push(Token { start, end, kind }); } tokens } fn classify_word(word: &str) -> TokenKind { if word.parse::().is_ok() || word.parse::().is_ok() { return TokenKind::Number; } if STACK_OPS.contains(&word) { return TokenKind::StackOp; } if OPERATORS.contains(&word) { return TokenKind::Operator; } if KEYWORDS.contains(&word) { return TokenKind::Keyword; } if SOUND.contains(&word) { return TokenKind::Sound; } if CONTEXT.contains(&word) { return TokenKind::Context; } if PARAMS.contains(&word) { return TokenKind::Param; } TokenKind::Default } pub fn highlight_line(line: &str) -> Vec<(Style, String)> { let tokens = tokenize_line(line); let mut result = Vec::new(); let mut last_end = 0; for token in tokens { if token.start > last_end { result.push(( TokenKind::Default.style(), line[last_end..token.start].to_string(), )); } result.push((token.kind.style(), line[token.start..token.end].to_string())); last_end = token.end; } if last_end < line.len() { result.push((TokenKind::Default.style(), line[last_end..].to_string())); } result }