// src/components/handlers/canvas.rs use ratatui::{ widgets::{Paragraph, Block, Borders}, layout::{Layout, Constraint, Direction, Rect}, style::{Style, Modifier}, text::{Line, Span}, Frame, prelude::Alignment, }; use crate::config::colors::themes::Theme; use crate::state::pages::canvas_state::CanvasState; use crate::state::app::highlight::HighlightState; // Ensure correct import path use std::cmp::{min, max}; pub fn render_canvas( f: &mut Frame, area: Rect, form_state: &impl CanvasState, fields: &[&str], current_field_idx: &usize, inputs: &[&String], theme: &Theme, is_edit_mode: bool, highlight_state: &HighlightState, // Using the enum state ) -> Option { // ... (setup code remains the same) ... let columns = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) .split(area); let border_style = if form_state.has_unsaved_changes() { Style::default().fg(theme.warning) } else if is_edit_mode { Style::default().fg(theme.accent) } else { Style::default().fg(theme.secondary) }; let input_container = Block::default() .borders(Borders::ALL) .border_style(border_style) .style(Style::default().bg(theme.bg)); let input_block = Rect { x: columns[1].x, y: columns[1].y, width: columns[1].width, height: fields.len() as u16 + 2, }; f.render_widget(&input_container, input_block); let input_area = input_container.inner(input_block); let input_rows = Layout::default() .direction(Direction::Vertical) .constraints(vec![Constraint::Length(1); fields.len()]) .split(input_area); let mut active_field_input_rect = None; // Render labels for (i, field) in fields.iter().enumerate() { let label = Paragraph::new(Line::from(Span::styled( format!("{}:", field), Style::default().fg(theme.fg)), )); f.render_widget(label, Rect { x: columns[0].x, y: input_block.y + 1 + i as u16, width: columns[0].width, height: 1, }); } // Render inputs and cursor for (i, input) in inputs.iter().enumerate() { let is_active = i == *current_field_idx; let current_cursor_pos = form_state.current_cursor_pos(); let text = input.as_str(); let text_len = text.chars().count(); let line: Line; // --- Use match on the highlight_state enum --- match highlight_state { HighlightState::Off => { // Not in highlight mode, render normally line = Line::from(Span::styled( text, if is_active { Style::default().fg(theme.highlight) } else { Style::default().fg(theme.fg) } )); } HighlightState::Characterwise { anchor } => { // --- Character-wise Highlight Logic --- let (anchor_field, anchor_char) = *anchor; let start_field = min(anchor_field, *current_field_idx); let end_field = max(anchor_field, *current_field_idx); // Use start_char and end_char consistently let (start_char, end_char) = if anchor_field == *current_field_idx { (min(anchor_char, current_cursor_pos), max(anchor_char, current_cursor_pos)) } else if anchor_field < *current_field_idx { (anchor_char, current_cursor_pos) } else { (current_cursor_pos, anchor_char) }; let highlight_style = Style::default().fg(theme.highlight).bg(theme.highlight_bg).add_modifier(Modifier::BOLD); let normal_style_in_highlight = Style::default().fg(theme.highlight); let normal_style_outside = Style::default().fg(theme.fg); if i >= start_field && i <= end_field { // This line is within the character-wise highlight range if start_field == end_field { // Case 1: Single Line Highlight // Use start_char and end_char here let clamped_start = start_char.min(text_len); let clamped_end = end_char.min(text_len); // Use text_len for slicing logic let before: String = text.chars().take(clamped_start).collect(); let highlighted: String = text.chars().skip(clamped_start).take(clamped_end.saturating_sub(clamped_start) + 1).collect(); // Define 'after' here let after: String = text.chars().skip(clamped_end + 1).collect(); line = Line::from(vec![ Span::styled(before, normal_style_in_highlight), Span::styled(highlighted, highlight_style), Span::styled(after, normal_style_in_highlight), // Use defined 'after' ]); } else if i == start_field { // Case 2: Multi-Line Highlight - Start Line // Use start_char here let safe_start = start_char.min(text_len); let before: String = text.chars().take(safe_start).collect(); let highlighted: String = text.chars().skip(safe_start).collect(); line = Line::from(vec![ Span::styled(before, normal_style_in_highlight), Span::styled(highlighted, highlight_style), ]); } else if i == end_field { // Case 3: Multi-Line Highlight - End Line (Corrected index) // Use end_char here let safe_end_inclusive = if text_len > 0 { end_char.min(text_len - 1) } else { 0 }; let highlighted: String = text.chars().take(safe_end_inclusive + 1).collect(); let after: String = text.chars().skip(safe_end_inclusive + 1).collect(); line = Line::from(vec![ Span::styled(highlighted, highlight_style), Span::styled(after, normal_style_in_highlight), ]); } else { // Case 4: Multi-Line Highlight - Middle Line (Corrected index) line = Line::from(Span::styled(text, highlight_style)); // Highlight whole line } } else { // Case 5: Line Outside Character-wise Highlight Range line = Line::from(Span::styled( text, // Use normal styling (active or inactive) if is_active { normal_style_in_highlight } else { normal_style_outside } )); } } HighlightState::Linewise { anchor_line } => { // --- Linewise Highlight Logic --- let start_field = min(*anchor_line, *current_field_idx); let end_field = max(*anchor_line, *current_field_idx); let highlight_style = Style::default().fg(theme.highlight).bg(theme.highlight_bg).add_modifier(Modifier::BOLD); let normal_style_in_highlight = Style::default().fg(theme.highlight); let normal_style_outside = Style::default().fg(theme.fg); if i >= start_field && i <= end_field { // Highlight the entire line line = Line::from(Span::styled(text, highlight_style)); } else { // Line outside linewise highlight range line = Line::from(Span::styled( text, // Use normal styling (active or inactive) if is_active { normal_style_in_highlight } else { normal_style_outside } )); } } } // End match highlight_state let input_display = Paragraph::new(line).alignment(Alignment::Left); f.render_widget(input_display, input_rows[i]); if is_active { active_field_input_rect = Some(input_rows[i]); let cursor_x = input_rows[i].x + form_state.current_cursor_pos() as u16; let cursor_y = input_rows[i].y; f.set_cursor_position((cursor_x, cursor_y)); } } active_field_input_rect }