From d91590835424cc104db23bc45c965aff103a32ce Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Wed, 1 Apr 2026 00:20:19 -0700 Subject: [PATCH] refactor: unsloth/Qwen3-Coder-Next-GGUF:Q5_K_M refactors the drawing helper --- src/main.rs | 328 +++++++++++++++++----------------------------------- 1 file changed, 104 insertions(+), 224 deletions(-) diff --git a/src/main.rs b/src/main.rs index 58526be..607d42f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -280,8 +280,6 @@ fn run_tui( fn draw(f: &mut Frame, app: &App) { let size = f.area(); - let is_cmd_mode = matches!(app.mode, AppMode::CommandMode { .. }); - let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -295,12 +293,7 @@ fn draw(f: &mut Frame, app: &App) { draw_title(f, main_chunks[0], app); draw_content(f, main_chunks[1], app); draw_tile_bar(f, main_chunks[2], app); - - if is_cmd_mode { - draw_command_bar(f, main_chunks[3], app); - } else { - draw_status(f, main_chunks[3], app); - } + draw_bottom_bar(f, main_chunks[3], app); // Overlays (rendered last so they appear on top) if matches!(app.mode, AppMode::Help) { @@ -312,92 +305,48 @@ fn draw(f: &mut Frame, app: &App) { } } if matches!(app.mode, AppMode::ExportPrompt { .. }) { - draw_export_prompt(f, size, app); + draw_export_prompt(f, app); } if app.is_empty_model() && matches!(app.mode, AppMode::Normal | AppMode::CommandMode { .. }) { - draw_welcome(f, main_chunks[1], app); + draw_welcome(f, main_chunks[1]); } } fn draw_title(f: &mut Frame, area: Rect, app: &App) { let dirty = if app.dirty { " [+]" } else { "" }; - let file = app - .file_path - .as_ref() - .and_then(|p| p.file_name()) - .and_then(|n| n.to_str()) - .map(|n| format!(" ({n})")) - .unwrap_or_default(); + let file = app.file_path.as_ref().and_then(|p| p.file_name()).and_then(|n| n.to_str()).map(|n| format!(" ({n})")).unwrap_or_default(); let title = format!(" improvise · {}{}{} ", app.model.name, file, dirty); let right = " ?:help :q quit "; let pad = " ".repeat((area.width as usize).saturating_sub(title.len() + right.len())); let line = format!("{title}{pad}{right}"); - f.render_widget( - Paragraph::new(line).style( - Style::default() - .fg(Color::Black) - .bg(Color::Blue) - .add_modifier(Modifier::BOLD), - ), - area, - ); + f.render_widget(Paragraph::new(line).style(Style::default().fg(Color::Black).bg(Color::Blue).add_modifier(Modifier::BOLD)), area); } fn draw_content(f: &mut Frame, area: Rect, app: &App) { - let side_open = app.formula_panel_open || app.category_panel_open || app.view_panel_open; - - if side_open { + if app.formula_panel_open || app.category_panel_open || app.view_panel_open { let side_w = 32u16; - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Min(40), Constraint::Length(side_w)]) - .split(area); - - f.render_widget( - GridWidget::new(&app.model, &app.mode, &app.search_query), - chunks[0], - ); + let chunks = Layout::default().direction(Direction::Horizontal).constraints([Constraint::Min(40), Constraint::Length(side_w)]).split(area); + f.render_widget(GridWidget::new(&app.model, &app.mode, &app.search_query), chunks[0]); let side = chunks[1]; - let panel_count = [ - app.formula_panel_open, - app.category_panel_open, - app.view_panel_open, - ] - .iter() - .filter(|&&b| b) - .count() as u16; + let open_panels = [app.formula_panel_open, app.category_panel_open, app.view_panel_open]; + let panel_count = open_panels.iter().filter(|&&b| b).count() as u16; let ph = side.height / panel_count.max(1); let mut y = side.y; if app.formula_panel_open { - let a = Rect::new(side.x, y, side.width, ph); - f.render_widget( - FormulaPanel::new(&app.model, &app.mode, app.formula_cursor), - a, - ); + f.render_widget(FormulaPanel::new(&app.model, &app.mode, app.formula_cursor), Rect::new(side.x, y, side.width, ph)); y += ph; } if app.category_panel_open { - let a = Rect::new(side.x, y, side.width, ph); - f.render_widget( - CategoryPanel::new(&app.model, &app.mode, app.cat_panel_cursor), - a, - ); + f.render_widget(CategoryPanel::new(&app.model, &app.mode, app.cat_panel_cursor), Rect::new(side.x, y, side.width, ph)); y += ph; } if app.view_panel_open { - let a = Rect::new(side.x, y, side.width, ph); - f.render_widget( - ViewPanel::new(&app.model, &app.mode, app.view_panel_cursor), - a, - ); + f.render_widget(ViewPanel::new(&app.model, &app.mode, app.view_panel_cursor), Rect::new(side.x, y, side.width, ph)); } } else { - f.render_widget( - GridWidget::new(&app.model, &app.mode, &app.search_query), - area, - ); + f.render_widget(GridWidget::new(&app.model, &app.mode, &app.search_query), area); } } @@ -405,8 +354,91 @@ fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) { f.render_widget(TileBar::new(&app.model, &app.mode), area); } +fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) { + match app.mode { + AppMode::CommandMode { ref buffer } => draw_command_bar(f, area, buffer), + _ => draw_status(f, area, app), + } +} + fn draw_status(f: &mut Frame, area: Rect, app: &App) { - let mode_badge = match &app.mode { + let mode_badge = mode_name(&app.mode); + let search_part = if app.search_mode { format!(" /{}▌", app.search_query) } else { String::new() }; + let msg = if !app.status_msg.is_empty() { app.status_msg.as_str() } else { app.hint_text() }; + let yank_indicator = if app.yanked.is_some() { " [yank]" } else { "" }; + let view_badge = format!(" {}{} ", app.model.active_view, yank_indicator); + let left = format!(" {mode_badge}{search_part} {msg}"); + let right = view_badge; + let pad = " ".repeat((area.width as usize).saturating_sub(left.len() + right.len())); + let line = format!("{left}{pad}{right}"); + let badge_style = mode_style(&app.mode); + f.render_widget(Paragraph::new(line).style(badge_style), area); +} + +fn draw_command_bar(f: &mut Frame, area: Rect, buffer: &str) { + f.render_widget(Paragraph::new(format!(":{buffer}▌")).style(Style::default().fg(Color::White).bg(Color::Black)), area); +} + +fn draw_export_prompt(f: &mut Frame, app: &App) { + let area = f.area(); + let popup_w = 64u16.min(area.width); + let x = area.x + area.width.saturating_sub(popup_w) / 2; + let y = area.y + area.height / 2; + let popup_area = Rect::new(x, y, popup_w, 3); + + f.render_widget(Clear, popup_area); + let block = Block::default().borders(Borders::ALL).border_style(Style::default().fg(Color::Yellow)).title(" Export CSV — path (Esc cancel) "); + let inner = block.inner(popup_area); + f.render_widget(block, popup_area); + let buf = if let AppMode::ExportPrompt { buffer } = &app.mode { buffer } else { "" }; + f.render_widget(Paragraph::new(format!("{buf}▌")).style(Style::default().fg(Color::Green)), inner); +} + +fn draw_welcome(f: &mut Frame, area: Rect) { + let w = 58u16.min(area.width.saturating_sub(4)); + let h = 20u16.min(area.height.saturating_sub(2)); + let x = area.x + area.width.saturating_sub(w) / 2; + let y = area.y + area.height.saturating_sub(h) / 2; + let popup = Rect::new(x, y, w, h); + + f.render_widget(Clear, popup); + + let block = Block::default().borders(Borders::ALL).border_style(Style::default().fg(Color::Blue)).title(" Welcome to improvise "); + let inner = block.inner(popup); + f.render_widget(block, popup); + + let lines: &[(&str, Style)] = &[ + ("Multi-dimensional data modeling — in your terminal.", Style::default().fg(Color::White)), + ("", Style::default()), + ("Getting started", Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD)), + ("", Style::default()), + (":import Import a JSON file", Style::default().fg(Color::Cyan)), + (":add-cat Add a category (dimension)", Style::default().fg(Color::Cyan)), + (":add-item Add an item to a category", Style::default().fg(Color::Cyan)), + (":formula Add a formula, e.g.:", Style::default().fg(Color::Cyan)), + (" Profit = Revenue - Cost", Style::default().fg(Color::Green)), + (":w Save your model", Style::default().fg(Color::Cyan)), + ("", Style::default()), + ("Navigation", Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD)), + ("", Style::default()), + ("F C V Open panels (Formulas/Categories/Views)", Style::default()), + ("T Tile-select: pivot rows ↔ cols ↔ page", Style::default()), + ("i Enter Edit a cell", Style::default()), + ("[ ] Cycle the page-axis filter", Style::default()), + ("? or :help Full key reference", Style::default()), + (":q Quit", Style::default()), + ]; + + for (i, (text, style)) in lines.iter().enumerate() { + if i >= inner.height as usize { break; } + f.render_widget(Paragraph::new(*text).style(*style), Rect::new(inner.x + 1, inner.y + i as u16, inner.width.saturating_sub(2), 1)); + } +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +fn mode_name(mode: &AppMode) -> &'static str { + match mode { AppMode::Normal => "NORMAL", AppMode::Editing { .. } => "INSERT", AppMode::FormulaEdit { .. } => "FORMULA", @@ -421,168 +453,16 @@ fn draw_status(f: &mut Frame, area: Rect, app: &App) { AppMode::CommandMode { .. } => "COMMAND", AppMode::Help => "HELP", AppMode::Quit => "QUIT", - }; + } +} - let search_part = if app.search_mode { - format!(" /{}▌", app.search_query) - } else { - String::new() - }; - - let msg = if !app.status_msg.is_empty() { - app.status_msg.as_str() - } else { - app.hint_text() - }; - - let yank_indicator = if app.yanked.is_some() { " [yank]" } else { "" }; - let view_badge = format!(" {}{} ", app.model.active_view, yank_indicator); - - let left = format!(" {mode_badge}{search_part} {msg}"); - let right = view_badge; - let pad = " ".repeat((area.width as usize).saturating_sub(left.len() + right.len())); - let line = format!("{left}{pad}{right}"); - - let badge_style = match &app.mode { +fn mode_style(mode: &AppMode) -> Style { + match mode { AppMode::Editing { .. } => Style::default().fg(Color::Black).bg(Color::Green), AppMode::CommandMode { .. } => Style::default().fg(Color::Black).bg(Color::Yellow), AppMode::TileSelect { .. } => Style::default().fg(Color::Black).bg(Color::Magenta), _ => Style::default().fg(Color::Black).bg(Color::DarkGray), - }; - - f.render_widget(Paragraph::new(line).style(badge_style), area); -} - -fn draw_command_bar(f: &mut Frame, area: Rect, app: &App) { - let buf = if let AppMode::CommandMode { buffer } = &app.mode { - buffer.as_str() - } else { - "" - }; - let line = format!(":{buf}▌"); - f.render_widget( - Paragraph::new(line).style(Style::default().fg(Color::White).bg(Color::Black)), - area, - ); -} - -fn draw_export_prompt(f: &mut Frame, area: Rect, app: &App) { - let buf = if let AppMode::ExportPrompt { buffer } = &app.mode { - buffer.as_str() - } else { - "" - }; - let popup_w = 64u16.min(area.width); - let x = area.x + area.width.saturating_sub(popup_w) / 2; - let y = area.y + area.height / 2; - let popup_area = Rect::new(x, y, popup_w, 3); - - f.render_widget(Clear, popup_area); - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Yellow)) - .title(" Export CSV — path (Esc cancel) "); - let inner = block.inner(popup_area); - f.render_widget(block, popup_area); - f.render_widget( - Paragraph::new(format!("{buf}▌")).style(Style::default().fg(Color::Green)), - inner, - ); -} - -fn draw_welcome(f: &mut Frame, area: Rect, _app: &App) { - let w = 58u16.min(area.width.saturating_sub(4)); - let h = 20u16.min(area.height.saturating_sub(2)); - let x = area.x + area.width.saturating_sub(w) / 2; - let y = area.y + area.height.saturating_sub(h) / 2; - let popup = Rect::new(x, y, w, h); - - f.render_widget(Clear, popup); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Blue)) - .title(" Welcome to improvise "); - let inner = block.inner(popup); - f.render_widget(block, popup); - - let lines: &[(&str, Style)] = &[ - ( - "Multi-dimensional data modeling — in your terminal.", - Style::default().fg(Color::White), - ), - ("", Style::default()), - ( - "Getting started", - Style::default() - .fg(Color::Blue) - .add_modifier(Modifier::BOLD), - ), - ("", Style::default()), - ( - ":import Import a JSON file", - Style::default().fg(Color::Cyan), - ), - ( - ":add-cat Add a category (dimension)", - Style::default().fg(Color::Cyan), - ), - ( - ":add-item Add an item to a category", - Style::default().fg(Color::Cyan), - ), - ( - ":formula Add a formula, e.g.:", - Style::default().fg(Color::Cyan), - ), - ( - " Profit = Revenue - Cost", - Style::default().fg(Color::Green), - ), - ( - ":w Save your model", - Style::default().fg(Color::Cyan), - ), - ("", Style::default()), - ( - "Navigation", - Style::default() - .fg(Color::Blue) - .add_modifier(Modifier::BOLD), - ), - ("", Style::default()), - ( - "F C V Open panels (Formulas/Categories/Views)", - Style::default(), - ), - ( - "T Tile-select: pivot rows ↔ cols ↔ page", - Style::default(), - ), - ("i Enter Edit a cell", Style::default()), - ( - "[ ] Cycle the page-axis filter", - Style::default(), - ), - ( - "? or :help Full key reference", - Style::default(), - ), - (":q Quit", Style::default()), - ]; - - for (i, (text, style)) in lines.iter().enumerate() { - if i >= inner.height as usize { - break; - } - f.render_widget( - Paragraph::new(*text).style(*style), - Rect::new( - inner.x + 1, - inner.y + i as u16, - inner.width.saturating_sub(2), - 1, - ), - ); } } + +