Init
This commit is contained in:
299
src/views/highlight.rs
Normal file
299
src/views/highlight.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
use crate::model::SourceSpan;
|
||||
|
||||
#[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<Token> {
|
||||
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;
|
||||
for (i, ch) in chars.by_ref() {
|
||||
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::<f64>().is_ok() || word.parse::<i64>().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)> {
|
||||
highlight_line_with_runtime(line, &[])
|
||||
}
|
||||
|
||||
pub fn highlight_line_with_runtime(line: &str, runtime_spans: &[SourceSpan]) -> Vec<(Style, String)> {
|
||||
let tokens = tokenize_line(line);
|
||||
let mut result = Vec::new();
|
||||
let mut last_end = 0;
|
||||
|
||||
let runtime_bg = Color::Rgb(80, 60, 20);
|
||||
|
||||
for token in tokens {
|
||||
if token.start > last_end {
|
||||
result.push((
|
||||
TokenKind::Default.style(),
|
||||
line[last_end..token.start].to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let is_runtime = runtime_spans
|
||||
.iter()
|
||||
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
||||
|
||||
let mut style = token.kind.style();
|
||||
if is_runtime {
|
||||
style = style.bg(runtime_bg).add_modifier(Modifier::BOLD);
|
||||
}
|
||||
|
||||
result.push((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
|
||||
}
|
||||
|
||||
fn overlaps(a_start: usize, a_end: usize, b_start: usize, b_end: usize) -> bool {
|
||||
a_start < b_end && b_start < a_end
|
||||
}
|
||||
Reference in New Issue
Block a user