From ff08e3c2c249021f763c1d3b62574fff55099224 Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Wed, 1 Apr 2026 00:26:55 -0700 Subject: [PATCH] chore: Revert refactors to give claude a clean slate --- src/main.rs | 322 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 221 insertions(+), 101 deletions(-) diff --git a/src/main.rs b/src/main.rs index 00b8c22..58526be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -280,6 +280,8 @@ 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([ @@ -293,7 +295,12 @@ 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); - draw_bottom_bar(f, main_chunks[3], app); + + if is_cmd_mode { + draw_command_bar(f, main_chunks[3], app); + } else { + draw_status(f, main_chunks[3], app); + } // Overlays (rendered last so they appear on top) if matches!(app.mode, AppMode::Help) { @@ -305,47 +312,92 @@ fn draw(f: &mut Frame, app: &App) { } } if matches!(app.mode, AppMode::ExportPrompt { .. }) { - draw_export_prompt(f, app); + draw_export_prompt(f, size, app); } if app.is_empty_model() && matches!(app.mode, AppMode::Normal | AppMode::CommandMode { .. }) { - draw_welcome(f, main_chunks[1]); + draw_welcome(f, main_chunks[1], app); } } 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 line = fill_line(title, right, area.width); - f.render_widget(Paragraph::new(line).style(Style::default().fg(Color::Black).bg(Color::Blue).add_modifier(Modifier::BOLD)), area); + 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, + ); } fn draw_content(f: &mut Frame, area: Rect, app: &App) { - if app.formula_panel_open || app.category_panel_open || app.view_panel_open { + let side_open = app.formula_panel_open || app.category_panel_open || app.view_panel_open; + + if side_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 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 panel_count = [ + app.formula_panel_open, + app.category_panel_open, + app.view_panel_open, + ] + .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 { - f.render_widget(FormulaPanel::new(&app.model, &app.mode, app.formula_cursor), Rect::new(side.x, y, side.width, ph)); + let a = Rect::new(side.x, y, side.width, ph); + f.render_widget( + FormulaPanel::new(&app.model, &app.mode, app.formula_cursor), + a, + ); y += ph; } if app.category_panel_open { - f.render_widget(CategoryPanel::new(&app.model, &app.mode, app.cat_panel_cursor), Rect::new(side.x, y, side.width, ph)); + let a = Rect::new(side.x, y, side.width, ph); + f.render_widget( + CategoryPanel::new(&app.model, &app.mode, app.cat_panel_cursor), + a, + ); y += ph; } if app.view_panel_open { - f.render_widget(ViewPanel::new(&app.model, &app.mode, app.view_panel_cursor), Rect::new(side.x, y, side.width, ph)); + let a = Rect::new(side.x, y, side.width, ph); + f.render_widget( + ViewPanel::new(&app.model, &app.mode, app.view_panel_cursor), + a, + ); } } 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, + ); } } @@ -353,81 +405,8 @@ 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 = 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 line = fill_line(left, &view_badge, area.width); - 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 popup_area = centered_popup(f.area(), 64, 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 popup = centered_popup(area, 58, 20); - - 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 { + let mode_badge = match &app.mode { AppMode::Normal => "NORMAL", AppMode::Editing { .. } => "INSERT", AppMode::FormulaEdit { .. } => "FORMULA", @@ -442,27 +421,168 @@ fn mode_name(mode: &AppMode) -> &'static str { AppMode::CommandMode { .. } => "COMMAND", AppMode::Help => "HELP", AppMode::Quit => "QUIT", - } -} + }; -fn mode_style(mode: &AppMode) -> Style { - match 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 = match &app.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 fill_line(left: String, right: &str, width: u16) -> String { - let pad = " ".repeat((width as usize).saturating_sub(left.len() + right.len())); - format!("{left}{pad}{right}") +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 centered_popup(area: Rect, width: u16, height: u16) -> Rect { - let w = width.min(area.width); - let h = height.min(area.height); +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; - Rect::new(x, y, w, h) + 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, + ), + ); + } }