Feat: comfort features
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ratatui::style::{Modifier, Style};
|
||||
|
||||
use crate::model::{lookup_word, SourceSpan, WordCompile};
|
||||
use crate::theme;
|
||||
|
||||
static EMPTY_SET: LazyLock<HashSet<String>> = LazyLock::new(HashSet::new);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
Number,
|
||||
@@ -20,6 +25,7 @@ pub enum TokenKind {
|
||||
Emit,
|
||||
Vary,
|
||||
Generator,
|
||||
UserDefined,
|
||||
Default,
|
||||
}
|
||||
|
||||
@@ -42,6 +48,7 @@ impl TokenKind {
|
||||
TokenKind::Variable => theme.syntax.variable,
|
||||
TokenKind::Vary => theme.syntax.vary,
|
||||
TokenKind::Generator => theme.syntax.generator,
|
||||
TokenKind::UserDefined => theme.syntax.user_defined,
|
||||
TokenKind::Default => theme.syntax.default,
|
||||
};
|
||||
let style = Style::default().fg(fg).bg(bg);
|
||||
@@ -114,7 +121,7 @@ const INTERVALS: &[&str] = &[
|
||||
"M14", "P15",
|
||||
];
|
||||
|
||||
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||
pub fn tokenize_line(line: &str, user_words: &HashSet<String>) -> Vec<Token> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut chars = line.char_indices().peekable();
|
||||
|
||||
@@ -160,14 +167,14 @@ pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||
}
|
||||
|
||||
let word = &line[start..end];
|
||||
let (kind, varargs) = classify_word(word);
|
||||
let (kind, varargs) = classify_word(word, user_words);
|
||||
tokens.push(Token { start, end, kind, varargs });
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
fn classify_word(word: &str) -> (TokenKind, bool) {
|
||||
fn classify_word(word: &str, user_words: &HashSet<String>) -> (TokenKind, bool) {
|
||||
if word.parse::<f64>().is_ok() || word.parse::<i64>().is_ok() {
|
||||
return (TokenKind::Number, false);
|
||||
}
|
||||
@@ -188,19 +195,24 @@ fn classify_word(word: &str) -> (TokenKind, bool) {
|
||||
return (TokenKind::Variable, false);
|
||||
}
|
||||
|
||||
if user_words.contains(word) {
|
||||
return (TokenKind::UserDefined, false);
|
||||
}
|
||||
|
||||
(TokenKind::Default, false)
|
||||
}
|
||||
|
||||
pub fn highlight_line(line: &str) -> Vec<(Style, String)> {
|
||||
highlight_line_with_runtime(line, &[], &[])
|
||||
highlight_line_with_runtime(line, &[], &[], &EMPTY_SET)
|
||||
}
|
||||
|
||||
pub fn highlight_line_with_runtime(
|
||||
line: &str,
|
||||
executed_spans: &[SourceSpan],
|
||||
selected_spans: &[SourceSpan],
|
||||
user_words: &HashSet<String>,
|
||||
) -> Vec<(Style, String)> {
|
||||
let tokens = tokenize_line(line);
|
||||
let tokens = tokenize_line(line, user_words);
|
||||
let mut result = Vec::new();
|
||||
let mut last_end = 0;
|
||||
let gap_style = TokenKind::gap_style();
|
||||
|
||||
@@ -90,6 +90,11 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
let is_edit = idx == app.editor_ctx.bank;
|
||||
let is_playing = banks_with_playback.contains(&idx);
|
||||
let is_staged = banks_with_staged.contains(&idx);
|
||||
let is_in_range = is_focused
|
||||
&& app
|
||||
.patterns_nav
|
||||
.bank_selection_range()
|
||||
.is_some_and(|r| r.contains(&idx));
|
||||
|
||||
// Check if any pattern in this bank is muted/soloed (applied)
|
||||
let has_muted = (0..MAX_PATTERNS).any(|p| app.mute.is_muted(idx, p));
|
||||
@@ -102,6 +107,8 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
|
||||
|
||||
let (bg, fg, prefix) = if is_cursor {
|
||||
(theme.selection.cursor, theme.selection.cursor_fg, "")
|
||||
} else if is_in_range {
|
||||
(theme.selection.in_range_bg, theme.selection.in_range_fg, "")
|
||||
} else if is_playing {
|
||||
if has_staged_mute_solo {
|
||||
(theme.list.staged_play_bg, theme.list.staged_play_fg, ">*")
|
||||
@@ -277,6 +284,11 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
let is_playing = playing_patterns.contains(&idx);
|
||||
let is_staged_play = staged_to_play.contains(&idx);
|
||||
let is_staged_stop = staged_to_stop.contains(&idx);
|
||||
let is_in_range = is_focused
|
||||
&& app
|
||||
.patterns_nav
|
||||
.pattern_selection_range()
|
||||
.is_some_and(|r| r.contains(&idx));
|
||||
|
||||
// Current applied mute/solo state
|
||||
let is_muted = app.mute.is_muted(bank, idx);
|
||||
@@ -294,6 +306,8 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
||||
|
||||
let (bg, fg, prefix) = if is_cursor {
|
||||
(theme.selection.cursor, theme.selection.cursor_fg, "")
|
||||
} else if is_in_range {
|
||||
(theme.selection.in_range_bg, theme.selection.in_range_fg, "")
|
||||
} else if is_playing {
|
||||
// Playing patterns
|
||||
if is_staged_stop {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
@@ -14,7 +15,7 @@ use crate::state::{
|
||||
EditorTarget, EuclideanField, FlashKind, Modal, PanelFocus, PatternField, SidePanel,
|
||||
};
|
||||
use crate::theme;
|
||||
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
|
||||
use crate::views::highlight::{self, highlight_line_with_runtime};
|
||||
use crate::widgets::{
|
||||
ConfirmModal, ModalFrame, NavMinimap, NavTile, SampleBrowser, TextInputModal,
|
||||
};
|
||||
@@ -460,6 +461,7 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term: Rect) -> Option<Rect> {
|
||||
let theme = theme::get();
|
||||
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
||||
let inner = match &app.ui.modal {
|
||||
Modal::None => return None,
|
||||
Modal::ConfirmQuit { selected } => {
|
||||
@@ -488,6 +490,16 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
ConfirmModal::new("Confirm", &format!("Reset bank {}?", bank + 1), *selected)
|
||||
.render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmResetPatterns {
|
||||
patterns, selected, ..
|
||||
} => {
|
||||
let label = format!("Reset {} patterns?", patterns.len());
|
||||
ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::ConfirmResetBanks { banks, selected } => {
|
||||
let label = format!("Reset {} banks?", banks.len());
|
||||
ConfirmModal::new("Confirm", &label, *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::FileBrowser(state) => {
|
||||
use crate::state::file_browser::FileBrowserMode;
|
||||
use crate::widgets::FileBrowserModal;
|
||||
@@ -621,9 +633,9 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
line_start,
|
||||
line_str.len(),
|
||||
);
|
||||
highlight_line_with_runtime(line_str, &exec, &sel)
|
||||
highlight_line_with_runtime(line_str, &exec, &sel, &user_words)
|
||||
} else {
|
||||
highlight_line(line_str)
|
||||
highlight_line_with_runtime(line_str, &[], &[], &user_words)
|
||||
};
|
||||
line_start += line_str.len() + 1;
|
||||
let spans: Vec<Span> = tokens
|
||||
@@ -700,7 +712,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
),
|
||||
None => (Vec::new(), Vec::new()),
|
||||
};
|
||||
highlight::highlight_line_with_runtime(line, &exec, &sel)
|
||||
highlight::highlight_line_with_runtime(line, &exec, &sel, &user_words)
|
||||
};
|
||||
|
||||
let show_search = app.editor_ctx.editor.search_active()
|
||||
|
||||
Reference in New Issue
Block a user