Before going crazy
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,3 +2,7 @@
|
||||
Cargo.lock
|
||||
*.prof
|
||||
.DS_Store
|
||||
|
||||
# Claude
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,15 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::project::{Bank, Project};
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
pub const EXTENSION: &str = "cagire";
|
||||
|
||||
pub fn ensure_extension(path: &Path) -> PathBuf {
|
||||
if path.extension().map(|e| e == EXTENSION).unwrap_or(false) {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
path.with_extension(EXTENSION)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ProjectFile {
|
||||
@@ -74,11 +83,12 @@ impl From<serde_json::Error> for FileError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(project: &Project, path: &Path) -> Result<(), FileError> {
|
||||
pub fn save(project: &Project, path: &Path) -> Result<PathBuf, FileError> {
|
||||
let path = ensure_extension(path);
|
||||
let file = ProjectFile::from(project);
|
||||
let json = serde_json::to_string_pretty(&file)?;
|
||||
fs::write(path, json)?;
|
||||
Ok(())
|
||||
fs::write(&path, json)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> Result<Project, FileError> {
|
||||
|
||||
@@ -216,6 +216,12 @@ pub struct Step {
|
||||
pub source: Option<usize>,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.active && self.script.is_empty() && self.source.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Step {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -227,20 +233,141 @@ impl Default for Step {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone)]
|
||||
pub struct Pattern {
|
||||
pub steps: Vec<Step>,
|
||||
pub length: usize,
|
||||
#[serde(default)]
|
||||
pub speed: PatternSpeed,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub quantization: LaunchQuantization,
|
||||
#[serde(default)]
|
||||
pub sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SparseStep {
|
||||
i: usize,
|
||||
#[serde(default = "default_active", skip_serializing_if = "is_true")]
|
||||
active: bool,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
script: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
source: Option<usize>,
|
||||
}
|
||||
|
||||
fn default_active() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_true(v: &bool) -> bool {
|
||||
*v
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SparsePattern {
|
||||
steps: Vec<SparseStep>,
|
||||
length: usize,
|
||||
#[serde(default)]
|
||||
speed: PatternSpeed,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "is_default_quantization")]
|
||||
quantization: LaunchQuantization,
|
||||
#[serde(default, skip_serializing_if = "is_default_sync_mode")]
|
||||
sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
fn is_default_quantization(q: &LaunchQuantization) -> bool {
|
||||
*q == LaunchQuantization::default()
|
||||
}
|
||||
|
||||
fn is_default_sync_mode(s: &SyncMode) -> bool {
|
||||
*s == SyncMode::default()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LegacyPattern {
|
||||
steps: Vec<Step>,
|
||||
length: usize,
|
||||
#[serde(default)]
|
||||
speed: PatternSpeed,
|
||||
#[serde(default)]
|
||||
name: Option<String>,
|
||||
#[serde(default)]
|
||||
quantization: LaunchQuantization,
|
||||
#[serde(default)]
|
||||
sync_mode: SyncMode,
|
||||
}
|
||||
|
||||
impl Serialize for Pattern {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let sparse_steps: Vec<SparseStep> = self
|
||||
.steps
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, step)| !step.is_default())
|
||||
.map(|(i, step)| SparseStep {
|
||||
i,
|
||||
active: step.active,
|
||||
script: step.script.clone(),
|
||||
source: step.source,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let sparse = SparsePattern {
|
||||
steps: sparse_steps,
|
||||
length: self.length,
|
||||
speed: self.speed,
|
||||
name: self.name.clone(),
|
||||
quantization: self.quantization,
|
||||
sync_mode: self.sync_mode,
|
||||
};
|
||||
sparse.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Pattern {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum PatternFormat {
|
||||
Sparse(SparsePattern),
|
||||
Legacy(LegacyPattern),
|
||||
}
|
||||
|
||||
match PatternFormat::deserialize(deserializer)? {
|
||||
PatternFormat::Sparse(sparse) => {
|
||||
let mut steps: Vec<Step> = (0..MAX_STEPS).map(|_| Step::default()).collect();
|
||||
for ss in sparse.steps {
|
||||
if ss.i < MAX_STEPS {
|
||||
steps[ss.i] = Step {
|
||||
active: ss.active,
|
||||
script: ss.script,
|
||||
command: None,
|
||||
source: ss.source,
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(Pattern {
|
||||
steps,
|
||||
length: sparse.length,
|
||||
speed: sparse.speed,
|
||||
name: sparse.name,
|
||||
quantization: sparse.quantization,
|
||||
sync_mode: sparse.sync_mode,
|
||||
})
|
||||
}
|
||||
PatternFormat::Legacy(legacy) => Ok(Pattern {
|
||||
steps: legacy.steps,
|
||||
length: legacy.length,
|
||||
speed: legacy.speed,
|
||||
name: legacy.name,
|
||||
quantization: legacy.quantization,
|
||||
sync_mode: legacy.sync_mode,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Pattern {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::ModalFrame;
|
||||
pub struct FileBrowserModal<'a> {
|
||||
title: &'a str,
|
||||
input: &'a str,
|
||||
entries: &'a [(String, bool)],
|
||||
entries: &'a [(String, bool, bool)],
|
||||
selected: usize,
|
||||
scroll_offset: usize,
|
||||
border_color: Color,
|
||||
@@ -18,7 +18,7 @@ pub struct FileBrowserModal<'a> {
|
||||
}
|
||||
|
||||
impl<'a> FileBrowserModal<'a> {
|
||||
pub fn new(title: &'a str, input: &'a str, entries: &'a [(String, bool)]) -> Self {
|
||||
pub fn new(title: &'a str, input: &'a str, entries: &'a [(String, bool, bool)]) -> Self {
|
||||
Self {
|
||||
title,
|
||||
input,
|
||||
@@ -85,7 +85,7 @@ impl<'a> FileBrowserModal<'a> {
|
||||
|
||||
let lines: Vec<Line> = visible_entries
|
||||
.enumerate()
|
||||
.map(|(i, (name, is_dir))| {
|
||||
.map(|(i, (name, is_dir, is_cagire))| {
|
||||
let abs_idx = i + self.scroll_offset;
|
||||
let is_selected = abs_idx == self.selected;
|
||||
let prefix = if is_selected { "> " } else { " " };
|
||||
@@ -98,6 +98,8 @@ impl<'a> FileBrowserModal<'a> {
|
||||
Color::Yellow
|
||||
} else if *is_dir {
|
||||
Color::Blue
|
||||
} else if *is_cagire {
|
||||
Color::Magenta
|
||||
} else {
|
||||
Color::White
|
||||
};
|
||||
|
||||
@@ -495,9 +495,9 @@ impl App {
|
||||
self.project_state.project.sample_paths = self.audio.config.sample_paths.clone();
|
||||
self.project_state.project.tempo = link.tempo();
|
||||
match model::save(&self.project_state.project, &path) {
|
||||
Ok(()) => {
|
||||
self.ui.set_status(format!("Saved: {}", path.display()));
|
||||
self.project_state.file_path = Some(path);
|
||||
Ok(final_path) => {
|
||||
self.ui.set_status(format!("Saved: {}", final_path.display()));
|
||||
self.project_state.file_path = Some(final_path);
|
||||
}
|
||||
Err(e) => {
|
||||
self.ui.set_status(format!("Save error: {e}"));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod script;
|
||||
|
||||
pub use cagire_forth::{Word, WORDS};
|
||||
pub use cagire_forth::{Word, WordCompile, WORDS};
|
||||
pub use cagire_project::{
|
||||
load, save, Bank, LaunchQuantization, Pattern, PatternSpeed, Project, SyncMode, MAX_BANKS,
|
||||
MAX_PATTERNS,
|
||||
|
||||
@@ -14,6 +14,12 @@ pub struct DirEntry {
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn is_cagire(&self) -> bool {
|
||||
!self.is_dir && self.name.ends_with(".cagire")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct FileBrowserState {
|
||||
pub mode: FileBrowserMode,
|
||||
|
||||
@@ -109,7 +109,10 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
let query = app.ui.dict_search_query.to_lowercase();
|
||||
WORDS
|
||||
.iter()
|
||||
.filter(|w| w.name.to_lowercase().contains(&query))
|
||||
.filter(|w| {
|
||||
w.name.to_lowercase().contains(&query)
|
||||
|| w.aliases.iter().any(|a| a.to_lowercase().contains(&query))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let category = CATEGORIES[app.ui.dict_category];
|
||||
@@ -144,12 +147,25 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
||||
.fg(Color::Green)
|
||||
.bg(name_bg)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let name_line = format!(" {}", word.name);
|
||||
let padding = " ".repeat(content_width.saturating_sub(name_line.chars().count()));
|
||||
let alias_style = Style::new().fg(Color::DarkGray).bg(name_bg);
|
||||
let name_text = if word.aliases.is_empty() {
|
||||
format!(" {}", word.name)
|
||||
} else {
|
||||
format!(" {} ({})", word.name, word.aliases.join(", "))
|
||||
};
|
||||
let padding = " ".repeat(content_width.saturating_sub(name_text.chars().count()));
|
||||
if word.aliases.is_empty() {
|
||||
lines.push(RLine::from(Span::styled(
|
||||
format!("{name_line}{padding}"),
|
||||
format!("{name_text}{padding}"),
|
||||
name_style,
|
||||
)));
|
||||
} else {
|
||||
lines.push(RLine::from(vec![
|
||||
Span::styled(format!(" {}", word.name), name_style),
|
||||
Span::styled(format!(" ({})", word.aliases.join(", ")), alias_style),
|
||||
Span::styled(padding, name_style),
|
||||
]));
|
||||
}
|
||||
|
||||
let stack_style = Style::new().fg(Color::Magenta);
|
||||
lines.push(RLine::from(vec![
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
use crate::model::SourceSpan;
|
||||
use crate::model::{SourceSpan, WordCompile, WORDS};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
@@ -45,223 +45,49 @@ pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
}
|
||||
|
||||
const STACK_OPS: &[&str] = &["dup", "dupn", "drop", "swap", "over", "rot", "nip", "tuck"];
|
||||
const OPERATORS: &[&str] = &[
|
||||
"+", "-", "*", "/", "mod", "neg", "abs", "min", "max", "=", "<>", "<", ">", "<=", ">=", "and",
|
||||
"or", "not", "ceil", "floor", "round", "mtof", "ftom",
|
||||
];
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"if",
|
||||
"else",
|
||||
"then",
|
||||
"emit",
|
||||
"rand",
|
||||
"rrand",
|
||||
"seed",
|
||||
"cycle",
|
||||
"choose",
|
||||
"chance",
|
||||
"[",
|
||||
"]",
|
||||
"zoom",
|
||||
"scale!",
|
||||
"stack",
|
||||
"echo",
|
||||
"necho",
|
||||
"for",
|
||||
"div",
|
||||
"each",
|
||||
"at",
|
||||
"pop",
|
||||
"adsr",
|
||||
"ad",
|
||||
"?",
|
||||
"!?",
|
||||
"<<",
|
||||
">>",
|
||||
"|",
|
||||
"@",
|
||||
"!",
|
||||
"pcycle",
|
||||
"tempo!",
|
||||
"prob",
|
||||
"sometimes",
|
||||
"often",
|
||||
"rarely",
|
||||
"almostAlways",
|
||||
"almostNever",
|
||||
"always",
|
||||
"never",
|
||||
"coin",
|
||||
"fill",
|
||||
"iter",
|
||||
"every",
|
||||
"gt",
|
||||
"lt",
|
||||
":",
|
||||
";",
|
||||
"apply",
|
||||
"clear",
|
||||
];
|
||||
const SOUND: &[&str] = &["sound", "s"];
|
||||
const CONTEXT: &[&str] = &[
|
||||
"step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs", "stepdur",
|
||||
];
|
||||
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",
|
||||
"eqlo",
|
||||
"eqmid",
|
||||
"eqhi",
|
||||
"tilt",
|
||||
"width",
|
||||
"haas",
|
||||
"llpf",
|
||||
"llpq",
|
||||
"lhpf",
|
||||
"lhpq",
|
||||
"lbpf",
|
||||
"lbpq",
|
||||
"sub",
|
||||
"suboct",
|
||||
"subwave",
|
||||
"bank",
|
||||
"loop",
|
||||
];
|
||||
fn lookup_word_kind(word: &str) -> Option<TokenKind> {
|
||||
for w in WORDS {
|
||||
if w.name == word || w.aliases.contains(&word) {
|
||||
return Some(match &w.compile {
|
||||
WordCompile::Param => TokenKind::Param,
|
||||
WordCompile::Context(_) => TokenKind::Context,
|
||||
_ => match w.category {
|
||||
"Stack" => TokenKind::StackOp,
|
||||
"Arithmetic" | "Comparison" | "Music" => TokenKind::Operator,
|
||||
"Logic" if matches!(w.name, "and" | "or" | "not" | "xor" | "nand" | "nor") => {
|
||||
TokenKind::Operator
|
||||
}
|
||||
"Sound" => TokenKind::Sound,
|
||||
_ => TokenKind::Keyword,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_note(word: &str) -> bool {
|
||||
let bytes = word.as_bytes();
|
||||
if bytes.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
let base = matches!(bytes[0], b'c' | b'd' | b'e' | b'f' | b'g' | b'a' | b'b');
|
||||
if !base {
|
||||
return false;
|
||||
}
|
||||
match bytes[1] {
|
||||
b'#' | b's' | b'b' => bytes.len() > 2 && bytes[2..].iter().all(|b| b.is_ascii_digit()),
|
||||
b'0'..=b'9' => bytes[1..].iter().all(|b| b.is_ascii_digit()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
const INTERVALS: &[&str] = &[
|
||||
"P1", "unison", "m2", "M2", "m3", "M3", "P4", "aug4", "dim5", "tritone", "P5", "m6", "M6",
|
||||
"m7", "M7", "P8", "oct", "m9", "M9", "m10", "M10", "P11", "aug11", "P12", "m13", "M13", "m14",
|
||||
"M14", "P15",
|
||||
];
|
||||
|
||||
const NOTES: &[&str] = &[
|
||||
"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "d0", "d1", "d2", "d3", "d4", "d5",
|
||||
"d6", "d7", "d8", "d9", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "f0", "f1",
|
||||
"f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7",
|
||||
"g8", "g9", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "b0", "b1", "b2", "b3",
|
||||
"b4", "b5", "b6", "b7", "b8", "b9", "cs0", "cs1", "cs2", "cs3", "cs4", "cs5", "cs6", "cs7",
|
||||
"cs8", "cs9", "ds0", "ds1", "ds2", "ds3", "ds4", "ds5", "ds6", "ds7", "ds8", "ds9", "es0",
|
||||
"es1", "es2", "es3", "es4", "es5", "es6", "es7", "es8", "es9", "fs0", "fs1", "fs2", "fs3",
|
||||
"fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "gs0", "gs1", "gs2", "gs3", "gs4", "gs5", "gs6",
|
||||
"gs7", "gs8", "gs9", "as0", "as1", "as2", "as3", "as4", "as5", "as6", "as7", "as8", "as9",
|
||||
"bs0", "bs1", "bs2", "bs3", "bs4", "bs5", "bs6", "bs7", "bs8", "bs9", "cb0", "cb1", "cb2",
|
||||
"cb3", "cb4", "cb5", "cb6", "cb7", "cb8", "cb9", "db0", "db1", "db2", "db3", "db4", "db5",
|
||||
"db6", "db7", "db8", "db9", "eb0", "eb1", "eb2", "eb3", "eb4", "eb5", "eb6", "eb7", "eb8",
|
||||
"eb9", "fb0", "fb1", "fb2", "fb3", "fb4", "fb5", "fb6", "fb7", "fb8", "fb9", "gb0", "gb1",
|
||||
"gb2", "gb3", "gb4", "gb5", "gb6", "gb7", "gb8", "gb9", "ab0", "ab1", "ab2", "ab3", "ab4",
|
||||
"ab5", "ab6", "ab7", "ab8", "ab9", "bb0", "bb1", "bb2", "bb3", "bb4", "bb5", "bb6", "bb7",
|
||||
"bb8", "bb9", "c#0", "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7", "c#8", "c#9", "d#0",
|
||||
"d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7", "d#8", "d#9", "e#0", "e#1", "e#2", "e#3",
|
||||
"e#4", "e#5", "e#6", "e#7", "e#8", "e#9", "f#0", "f#1", "f#2", "f#3", "f#4", "f#5", "f#6",
|
||||
"f#7", "f#8", "f#9", "g#0", "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7", "g#8", "g#9",
|
||||
"a#0", "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7", "a#8", "a#9", "b#0", "b#1", "b#2",
|
||||
"b#3", "b#4", "b#5", "b#6", "b#7", "b#8", "b#9",
|
||||
];
|
||||
|
||||
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut chars = line.char_indices().peekable();
|
||||
@@ -329,36 +155,15 @@ fn classify_word(word: &str) -> TokenKind {
|
||||
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;
|
||||
if let Some(kind) = lookup_word_kind(word) {
|
||||
return kind;
|
||||
}
|
||||
|
||||
if INTERVALS.contains(&word) {
|
||||
return TokenKind::Interval;
|
||||
}
|
||||
|
||||
let lower = word.to_ascii_lowercase();
|
||||
if NOTES.contains(&lower.as_str()) {
|
||||
if is_note(&word.to_ascii_lowercase()) {
|
||||
return TokenKind::Note;
|
||||
}
|
||||
|
||||
|
||||
@@ -431,10 +431,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
FileBrowserMode::Save => ("Save As", Color::Green),
|
||||
FileBrowserMode::Load => ("Load From", Color::Blue),
|
||||
};
|
||||
let entries: Vec<(String, bool)> = state
|
||||
let entries: Vec<(String, bool, bool)> = state
|
||||
.entries
|
||||
.iter()
|
||||
.map(|e| (e.name.clone(), e.is_dir))
|
||||
.map(|e| (e.name.clone(), e.is_dir, e.is_cagire()))
|
||||
.collect();
|
||||
FileBrowserModal::new(title, &state.input, &entries)
|
||||
.selected(state.selected)
|
||||
@@ -483,10 +483,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
Modal::AddSamplePath(state) => {
|
||||
use crate::widgets::FileBrowserModal;
|
||||
let entries: Vec<(String, bool)> = state
|
||||
let entries: Vec<(String, bool, bool)> = state
|
||||
.entries
|
||||
.iter()
|
||||
.map(|e| (e.name.clone(), e.is_dir))
|
||||
.map(|e| (e.name.clone(), e.is_dir, e.is_cagire()))
|
||||
.collect();
|
||||
FileBrowserModal::new("Add Sample Path", &state.input, &entries)
|
||||
.selected(state.selected)
|
||||
|
||||
Reference in New Issue
Block a user