A ton of bug fixes

This commit is contained in:
2026-01-28 01:09:23 +01:00
parent a9ce70d292
commit 322885b908
13 changed files with 400 additions and 130 deletions

View File

@@ -1,9 +1,10 @@
use minimad::{Composite, CompositeStyle, Compound, Line};
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::{Line as RLine, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
use ratatui::Frame;
use tui_big_text::{BigText, PixelSize};
use crate::app::App;
use crate::views::highlight;
@@ -11,8 +12,10 @@ use crate::views::highlight;
// To add a new help topic: drop a .md file in docs/ and add one line here.
const DOCS: &[(&str, &str)] = &[
("Welcome", include_str!("../../docs/welcome.md")),
("Audio Engine", include_str!("../../docs/audio_engine.md")),
("Keybindings", include_str!("../../docs/keybindings.md")),
("Sequencer", include_str!("../../docs/sequencer.md")),
("About", include_str!("../../docs/about.md")),
];
pub fn topic_count() -> usize {
@@ -47,8 +50,36 @@ fn render_topics(frame: &mut Frame, app: &App, area: Rect) {
frame.render_widget(list, area);
}
const WELCOME_TOPIC: usize = 0;
const BIG_TITLE_HEIGHT: u16 = 6;
fn render_content(frame: &mut Frame, app: &App, area: Rect) {
let (name, md) = DOCS[app.ui.help_topic];
let (_, md) = DOCS[app.ui.help_topic];
let is_welcome = app.ui.help_topic == WELCOME_TOPIC;
let md_area = if is_welcome {
let [title_area, rest] =
Layout::vertical([Constraint::Length(BIG_TITLE_HEIGHT), Constraint::Fill(1)])
.areas(area);
let big_title = BigText::builder()
.pixel_size(PixelSize::Quadrant)
.style(Style::new().cyan().bold())
.lines(vec!["CAGIRE".into()])
.centered()
.build();
let subtitle = Paragraph::new(RLine::from(Span::styled(
"A Forth Sequencer",
Style::new().fg(Color::White),
)))
.alignment(ratatui::layout::Alignment::Center);
let [big_area, subtitle_area] =
Layout::vertical([Constraint::Length(4), Constraint::Length(2)]).areas(title_area);
frame.render_widget(big_title, big_area);
frame.render_widget(subtitle, subtitle_area);
rest
} else {
area
};
let query = &app.ui.help_search_query;
let has_query = !query.is_empty();
let query_lower = query.to_lowercase();
@@ -57,7 +88,7 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
let has_search_bar = app.ui.help_search_active || has_query;
let search_bar_height: u16 = u16::from(has_search_bar);
let visible_height = area.height.saturating_sub(6 + search_bar_height) as usize;
let visible_height = md_area.height.saturating_sub(6 + search_bar_height) as usize;
let max_scroll = lines.len().saturating_sub(visible_height);
let scroll = app.ui.help_scroll().min(max_scroll);
@@ -76,18 +107,17 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
let content_area = if has_search_bar {
let [content, search] =
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(area);
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(md_area);
render_search_bar(frame, app, search);
content
} else {
area
md_area
};
let para = Paragraph::new(visible)
.block(
Block::default()
.borders(Borders::ALL)
.title(name)
.padding(Padding::new(2, 2, 2, 2)),
)
.wrap(Wrap { trim: false });
@@ -158,8 +188,52 @@ fn code_border_style() -> Style {
Style::new().fg(Color::Rgb(60, 60, 70))
}
fn preprocess_underscores(md: &str) -> String {
let mut out = String::with_capacity(md.len());
for line in md.lines() {
let mut result = String::with_capacity(line.len());
let mut chars = line.char_indices().peekable();
let bytes = line.as_bytes();
while let Some((i, c)) = chars.next() {
if c == '`' {
result.push(c);
for (_, ch) in chars.by_ref() {
result.push(ch);
if ch == '`' {
break;
}
}
continue;
}
if c == '_' {
let before_is_space = i == 0 || bytes[i - 1] == b' ';
if before_is_space {
if let Some(end) = line[i + 1..].find('_') {
let inner = &line[i + 1..i + 1 + end];
if !inner.is_empty() {
result.push('*');
result.push_str(inner);
result.push('*');
for _ in 0..end {
chars.next();
}
chars.next(); // skip closing _
continue;
}
}
}
}
result.push(c);
}
out.push_str(&result);
out.push('\n');
}
out
}
fn parse_markdown(md: &str) -> Vec<RLine<'static>> {
let text = minimad::Text::from(md);
let processed = preprocess_underscores(md);
let text = minimad::Text::from(processed.as_str());
let mut lines = Vec::new();
let mut code_line_nr: usize = 0;
@@ -218,13 +292,13 @@ fn composite_to_line(composite: Composite) -> RLine<'static> {
}
for compound in composite.compounds {
spans.push(compound_to_span(compound, base_style));
compound_to_spans(compound, base_style, &mut spans);
}
RLine::from(spans)
}
fn compound_to_span(compound: Compound, base: Style) -> Span<'static> {
fn compound_to_spans(compound: Compound, base: Style, out: &mut Vec<Span<'static>>) {
let mut style = base;
if compound.bold {
@@ -240,5 +314,39 @@ fn compound_to_span(compound: Compound, base: Style) -> Span<'static> {
style = style.add_modifier(Modifier::CROSSED_OUT);
}
Span::styled(compound.src.to_string(), style)
let src = compound.src.to_string();
let link_style = Style::new()
.fg(Color::Rgb(120, 200, 180))
.add_modifier(Modifier::UNDERLINED);
let mut rest = src.as_str();
while let Some(start) = rest.find('[') {
let after_bracket = &rest[start + 1..];
if let Some(text_end) = after_bracket.find("](") {
let url_start = start + 1 + text_end + 2;
if let Some(url_end) = rest[url_start..].find(')') {
if start > 0 {
out.push(Span::styled(rest[..start].to_string(), style));
}
let text = &rest[start + 1..start + 1 + text_end];
let url = &rest[url_start..url_start + url_end];
if text == url {
out.push(Span::styled(url.to_string(), link_style));
} else {
out.push(Span::styled(text.to_string(), link_style));
out.push(Span::styled(
format!(" ({url})"),
Style::new().fg(Color::Rgb(100, 100, 100)),
));
}
rest = &rest[url_start + url_end + 1..];
continue;
}
}
out.push(Span::styled(rest[..start + 1].to_string(), style));
rest = &rest[start + 1..];
}
if !rest.is_empty() {
out.push(Span::styled(rest.to_string(), style));
}
}