Try to optimize

This commit is contained in:
2026-01-29 11:53:47 +01:00
parent 73db616139
commit 495bfb3bdc
14 changed files with 764 additions and 214 deletions

View File

@@ -16,39 +16,90 @@ pub enum TokenKind {
Note,
Interval,
Variable,
Emit,
Vary,
Generator,
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::Note => Style::default().fg(Color::Rgb(120, 200, 160)),
TokenKind::Interval => Style::default().fg(Color::Rgb(160, 200, 120)),
TokenKind::Variable => Style::default().fg(Color::Rgb(200, 140, 180)),
TokenKind::Default => Style::default().fg(Color::Rgb(200, 200, 200)),
TokenKind::Emit => Style::default()
.fg(Color::Rgb(255, 255, 255))
.bg(Color::Rgb(140, 50, 50))
.add_modifier(Modifier::BOLD),
TokenKind::Number => Style::default()
.fg(Color::Rgb(255, 200, 120))
.bg(Color::Rgb(60, 40, 15)),
TokenKind::String => Style::default()
.fg(Color::Rgb(150, 230, 150))
.bg(Color::Rgb(20, 55, 20)),
TokenKind::Comment => Style::default()
.fg(Color::Rgb(100, 100, 100))
.bg(Color::Rgb(18, 18, 18)),
TokenKind::Keyword => Style::default()
.fg(Color::Rgb(230, 130, 230))
.bg(Color::Rgb(55, 25, 55)),
TokenKind::StackOp => Style::default()
.fg(Color::Rgb(130, 190, 240))
.bg(Color::Rgb(20, 40, 70)),
TokenKind::Operator => Style::default()
.fg(Color::Rgb(220, 220, 140))
.bg(Color::Rgb(45, 45, 20)),
TokenKind::Sound => Style::default()
.fg(Color::Rgb(100, 240, 220))
.bg(Color::Rgb(15, 60, 55)),
TokenKind::Param => Style::default()
.fg(Color::Rgb(190, 160, 240))
.bg(Color::Rgb(45, 30, 70)),
TokenKind::Context => Style::default()
.fg(Color::Rgb(240, 190, 120))
.bg(Color::Rgb(60, 45, 20)),
TokenKind::Note => Style::default()
.fg(Color::Rgb(120, 220, 170))
.bg(Color::Rgb(20, 55, 40)),
TokenKind::Interval => Style::default()
.fg(Color::Rgb(170, 220, 120))
.bg(Color::Rgb(35, 55, 20)),
TokenKind::Variable => Style::default()
.fg(Color::Rgb(220, 150, 190))
.bg(Color::Rgb(60, 30, 50)),
TokenKind::Vary => Style::default()
.fg(Color::Rgb(230, 230, 100))
.bg(Color::Rgb(55, 55, 15)),
TokenKind::Generator => Style::default()
.fg(Color::Rgb(100, 220, 180))
.bg(Color::Rgb(15, 55, 45)),
TokenKind::Default => Style::default()
.fg(Color::Rgb(160, 160, 160))
.bg(Color::Rgb(25, 25, 25)),
}
}
pub fn gap_style() -> Style {
Style::default().bg(Color::Rgb(25, 25, 25))
}
}
pub struct Token {
pub start: usize,
pub end: usize,
pub kind: TokenKind,
pub varargs: bool,
}
fn lookup_word_kind(word: &str) -> Option<TokenKind> {
fn lookup_word_kind(word: &str) -> Option<(TokenKind, bool)> {
if word == "." {
return Some((TokenKind::Emit, false));
}
if word == ".!" {
return Some((TokenKind::Emit, true));
}
for w in WORDS {
if w.name == word || w.aliases.contains(&word) {
return Some(match &w.compile {
let kind = match &w.compile {
WordCompile::Param => TokenKind::Param,
WordCompile::Context(_) => TokenKind::Context,
_ => match w.category {
@@ -58,9 +109,12 @@ fn lookup_word_kind(word: &str) -> Option<TokenKind> {
TokenKind::Operator
}
"Sound" => TokenKind::Sound,
"Randomness" | "Probability" | "Selection" => TokenKind::Vary,
"Generator" => TokenKind::Generator,
_ => TokenKind::Keyword,
},
});
};
return Some((kind, w.varargs));
}
}
None
@@ -98,11 +152,11 @@ pub fn tokenize_line(line: &str) -> Vec<Token> {
}
if c == ';' && chars.peek().map(|(_, ch)| *ch) == Some(';') {
// ;; starts a comment to end of line
tokens.push(Token {
start,
end: line.len(),
kind: TokenKind::Comment,
varargs: false,
});
break;
}
@@ -119,6 +173,7 @@ pub fn tokenize_line(line: &str) -> Vec<Token> {
start,
end,
kind: TokenKind::String,
varargs: false,
});
continue;
}
@@ -133,35 +188,35 @@ pub fn tokenize_line(line: &str) -> Vec<Token> {
}
let word = &line[start..end];
let kind = classify_word(word);
tokens.push(Token { start, end, kind });
let (kind, varargs) = classify_word(word);
tokens.push(Token { start, end, kind, varargs });
}
tokens
}
fn classify_word(word: &str) -> TokenKind {
fn classify_word(word: &str) -> (TokenKind, bool) {
if word.parse::<f64>().is_ok() || word.parse::<i64>().is_ok() {
return TokenKind::Number;
return (TokenKind::Number, false);
}
if let Some(kind) = lookup_word_kind(word) {
return kind;
if let Some((kind, varargs)) = lookup_word_kind(word) {
return (kind, varargs);
}
if INTERVALS.contains(&word) {
return TokenKind::Interval;
return (TokenKind::Interval, false);
}
if is_note(&word.to_ascii_lowercase()) {
return TokenKind::Note;
return (TokenKind::Note, false);
}
if word.len() > 1 && (word.starts_with('@') || word.starts_with('!')) {
return TokenKind::Variable;
return (TokenKind::Variable, false);
}
TokenKind::Default
(TokenKind::Default, false)
}
pub fn highlight_line(line: &str) -> Vec<(Style, String)> {
@@ -179,13 +234,11 @@ pub fn highlight_line_with_runtime(
let executed_bg = Color::Rgb(40, 35, 50);
let selected_bg = Color::Rgb(80, 60, 20);
let gap_style = TokenKind::gap_style();
for token in tokens {
if token.start > last_end {
result.push((
TokenKind::Default.style(),
line[last_end..token.start].to_string(),
));
result.push((gap_style, line[last_end..token.start].to_string()));
}
let is_selected = selected_spans
@@ -196,6 +249,9 @@ pub fn highlight_line_with_runtime(
.any(|span| overlaps(token.start, token.end, span.start, span.end));
let mut style = token.kind.style();
if token.varargs {
style = style.add_modifier(Modifier::UNDERLINED);
}
if is_selected {
style = style.bg(selected_bg).add_modifier(Modifier::BOLD);
} else if is_executed {
@@ -207,7 +263,7 @@ pub fn highlight_line_with_runtime(
}
if last_end < line.len() {
result.push((TokenKind::Default.style(), line[last_end..].to_string()));
result.push((gap_style, line[last_end..].to_string()));
}
result

View File

@@ -1,4 +1,6 @@
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex};
use std::time::Instant;
@@ -15,7 +17,7 @@ use crate::app::App;
use crate::engine::{LinkState, SequencerSnapshot};
use crate::model::{SourceSpan, StepContext, Value};
use crate::page::Page;
use crate::state::{FlashKind, Modal, PanelFocus, PatternField, SidePanel};
use crate::state::{FlashKind, Modal, PanelFocus, PatternField, SidePanel, StackCache};
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
use crate::widgets::{
ConfirmModal, ModalFrame, NavMinimap, NavTile, SampleBrowser, TextInputModal,
@@ -25,43 +27,67 @@ use super::{
dict_view, engine_view, help_view, main_view, options_view, patterns_view, title_view,
};
fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor) -> String {
fn compute_stack_display(lines: &[String], editor: &cagire_ratatui::Editor, cache: &std::cell::RefCell<Option<StackCache>>) -> String {
let cursor_line = editor.cursor().0;
let mut hasher = DefaultHasher::new();
for (i, line) in lines.iter().enumerate() {
if i > cursor_line {
break;
}
line.hash(&mut hasher);
}
let lines_hash = hasher.finish();
if let Some(ref c) = *cache.borrow() {
if c.cursor_line == cursor_line && c.lines_hash == lines_hash {
return c.result.clone();
}
}
let partial: Vec<&str> = lines.iter().take(cursor_line + 1).map(|s| s.as_str()).collect();
let script = partial.join("\n");
if script.trim().is_empty() {
return "Stack: []".to_string();
}
let result = if script.trim().is_empty() {
"Stack: []".to_string()
} else {
let vars = Arc::new(Mutex::new(HashMap::new()));
let dict = Arc::new(Mutex::new(HashMap::new()));
let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(42)));
let forth = Forth::new(vars, dict, rng);
let vars = Arc::new(Mutex::new(HashMap::new()));
let dict = Arc::new(Mutex::new(HashMap::new()));
let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(42)));
let forth = Forth::new(vars, dict, rng);
let ctx = StepContext {
step: 0,
beat: 0.0,
bank: 0,
pattern: 0,
tempo: 120.0,
phase: 0.0,
slot: 0,
runs: 0,
iter: 0,
speed: 1.0,
fill: false,
nudge_secs: 0.0,
};
let ctx = StepContext {
step: 0,
beat: 0.0,
bank: 0,
pattern: 0,
tempo: 120.0,
phase: 0.0,
slot: 0,
runs: 0,
iter: 0,
speed: 1.0,
fill: false,
nudge_secs: 0.0,
match forth.evaluate(&script, &ctx) {
Ok(_) => {
let stack = forth.stack();
let formatted: Vec<String> = stack.iter().map(format_value).collect();
format!("Stack: [{}]", formatted.join(" "))
}
Err(e) => format!("Error: {e}"),
}
};
match forth.evaluate(&script, &ctx) {
Ok(_) => {
let stack = forth.stack();
let formatted: Vec<String> = stack.iter().map(format_value).collect();
format!("Stack: [{}]", formatted.join(" "))
}
Err(e) => format!("Error: {e}"),
}
*cache.borrow_mut() = Some(StackCache {
cursor_line,
lines_hash,
result: result.clone(),
});
result
}
fn format_value(v: &Value) -> String {
@@ -740,13 +766,13 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
]);
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
} else if app.editor_ctx.show_stack {
let stack_text = compute_stack_display(text_lines, &app.editor_ctx.editor);
let stack_text = compute_stack_display(text_lines, &app.editor_ctx.editor, &app.editor_ctx.stack_cache);
let hint = Line::from(vec![
Span::styled("Esc", key),
Span::styled(" save ", dim),
Span::styled("C-e", key),
Span::styled(" eval ", dim),
Span::styled("C-k", key),
Span::styled("C-s", key),
Span::styled(" hide", dim),
]);
let [hint_left, stack_right] = Layout::horizontal([
@@ -767,7 +793,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
Span::styled(" eval ", dim),
Span::styled("C-f", key),
Span::styled(" find ", dim),
Span::styled("C-k", key),
Span::styled("C-s", key),
Span::styled(" stack ", dim),
Span::styled("C-u", key),
Span::styled("/", dim),