diff --git a/client/config.toml b/client/config.toml index 3edda0c..12827cb 100644 --- a/client/config.toml +++ b/client/config.toml @@ -50,7 +50,7 @@ move_line_end = ["$"] move_first_line = ["gg"] move_last_line = ["x"] enter_highlight_mode = ["v"] -# highlight_mode_full_line = ["ctrl+v"] +enter_highlight_mode_linewise = ["ctrl+v"] [keybindings.highlight] exit_highlight_mode = ["esc"] diff --git a/client/src/components/auth/login.rs b/client/src/components/auth/login.rs index 599935f..e8bb761 100644 --- a/client/src/components/auth/login.rs +++ b/client/src/components/auth/login.rs @@ -21,6 +21,7 @@ pub fn render_login( app_state: &AppState, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, ) { // Main container @@ -59,6 +60,7 @@ pub fn render_login( theme, is_edit_mode, is_highlight_mode, + is_linewise_highlight, highlight_anchor, ); diff --git a/client/src/components/auth/register.rs b/client/src/components/auth/register.rs index e28431a..9898aa5 100644 --- a/client/src/components/auth/register.rs +++ b/client/src/components/auth/register.rs @@ -23,6 +23,7 @@ pub fn render_register( app_state: &AppState, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, ) { let block = Block::default() @@ -67,6 +68,7 @@ pub fn render_register( theme, is_edit_mode, is_highlight_mode, + is_linewise_highlight, highlight_anchor, ); diff --git a/client/src/components/form/form.rs b/client/src/components/form/form.rs index 4a0e900..c60825d 100644 --- a/client/src/components/form/form.rs +++ b/client/src/components/form/form.rs @@ -19,6 +19,7 @@ pub fn render_form( theme: &Theme, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, total_count: u64, current_position: u64, @@ -65,6 +66,7 @@ pub fn render_form( theme, is_edit_mode, is_highlight_mode, + is_linewise_highlight, highlight_anchor, ); } diff --git a/client/src/components/handlers/canvas.rs b/client/src/components/handlers/canvas.rs index 89b210e..73cb818 100644 --- a/client/src/components/handlers/canvas.rs +++ b/client/src/components/handlers/canvas.rs @@ -9,27 +9,26 @@ use ratatui::{ }; use crate::config::colors::themes::Theme; use crate::state::pages::canvas_state::CanvasState; -use std::cmp::{min, max}; // Import min and max +use std::cmp::{min, max}; pub fn render_canvas( f: &mut Frame, area: Rect, form_state: &impl CanvasState, fields: &[&str], - current_field_idx: &usize, // Renamed for clarity + current_field_idx: &usize, inputs: &[&String], theme: &Theme, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, ) -> Option { - // Split area into columns let columns = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) .split(area); - // Input container styling let border_style = if form_state.has_unsaved_changes() { Style::default().fg(theme.warning) } else if is_edit_mode { @@ -42,7 +41,6 @@ pub fn render_canvas( .border_style(border_style) .style(Style::default().bg(theme.bg)); - // Input block dimensions let input_block = Rect { x: columns[1].x, y: columns[1].y, @@ -52,7 +50,6 @@ pub fn render_canvas( f.render_widget(&input_container, input_block); - // Input rows layout let input_area = input_container.inner(input_block); let input_rows = Layout::default() .direction(Direction::Vertical) @@ -82,87 +79,88 @@ pub fn render_canvas( let text = input.as_str(); let text_len = text.chars().count(); - let line: Line; // Determine the line content with spans + let line: Line; if is_highlight_mode && highlight_anchor.is_some() { let (anchor_field, anchor_char) = highlight_anchor.unwrap(); - - // Determine the actual start and end fields/chars for rendering let start_field = min(anchor_field, *current_field_idx); let end_field = max(anchor_field, *current_field_idx); - let (start_char, end_char) = if anchor_field == *current_field_idx { - // Single line selection - (min(anchor_char, current_cursor_pos), max(anchor_char, current_cursor_pos)) - } else if anchor_field < *current_field_idx { - // Anchor is above cursor - (anchor_char, current_cursor_pos) - } else { - // Anchor is below cursor - (current_cursor_pos, anchor_char) - }; - - // Style for highlighted text - let highlight_style = Style::default().fg(theme.highlight).bg(theme.highlight_bg).add_modifier(Modifier::BOLD); - // Style for normal text within the highlight range (active field color) + 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); - // Style for normal text outside highlight range (inactive field color) let normal_style_outside = Style::default().fg(theme.fg); - if i >= start_field && i <= end_field { - // This line is within the highlight range - - if start_field == end_field { // Case 1: Single Line Highlight - let safe_start = start_char.min(text_len); - let safe_end = end_char.min(text_len); - let before: String = text.chars().take(safe_start).collect(); - let highlighted: String = text.chars().skip(safe_start).take(safe_end - safe_start).collect(); - let after: String = text.chars().skip(safe_end).collect(); - line = Line::from(vec![ - Span::styled(before, normal_style_in_highlight), - Span::styled(highlighted, highlight_style), - Span::styled(after, normal_style_in_highlight), - ]); - } else if i == start_field { // Case 2: Multi-Line Highlight - Start Line - 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), // Use active color before highlight starts - Span::styled(highlighted, highlight_style), - ]); - } else if i == end_field { // Case 4: Multi-Line Highlight - End Line - let safe_end = end_char.min(text_len); - let highlighted: String = text.chars().take(safe_end).collect(); - let after: String = text.chars().skip(safe_end).collect(); - line = Line::from(vec![ - Span::styled(highlighted, highlight_style), - Span::styled(after, normal_style_in_highlight), // Use active color after highlight ends - ]); - } else { // Case 3: Multi-Line Highlight - Middle Line - line = Line::from(Span::styled(text, highlight_style)); // Highlight whole line + if is_linewise_highlight { + if i >= start_field && i <= end_field { + line = Line::from(Span::styled(text, highlight_style)); + } else { + line = Line::from(Span::styled( + text, + if is_active { normal_style_in_highlight } else { normal_style_outside } + )); + } + } else { + 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) + }; + + if i >= start_field && i <= end_field { + if start_field == end_field { + let safe_start = start_char.min(text_len); + let safe_end = end_char.min(text_len); + let before: String = text.chars().take(safe_start).collect(); + let highlighted: String = text.chars().skip(safe_start).take(safe_end - safe_start).collect(); + let after: String = text.chars().skip(safe_end).collect(); + line = Line::from(vec![ + Span::styled(before, normal_style_in_highlight), + Span::styled(highlighted, highlight_style), + Span::styled(after, normal_style_in_highlight), + ]); + } else if i == start_field { + 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 { + let safe_end = end_char.min(text_len); + let highlighted: String = text.chars().take(safe_end).collect(); + let after: String = text.chars().skip(safe_end).collect(); + line = Line::from(vec![ + Span::styled(highlighted, highlight_style), + Span::styled(after, normal_style_in_highlight), + ]); + } else { + line = Line::from(Span::styled(text, highlight_style)); + } + } else { + line = Line::from(Span::styled( + text, + if is_active { normal_style_in_highlight } else { normal_style_outside } + )); } - } else { // Case 5: Line Outside Highlight Range - line = Line::from(Span::styled( - text, - if is_active { normal_style_in_highlight } else { normal_style_outside } - )); } } else { - // 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) } )); }; - let input_display = Paragraph::new(line) - .alignment(Alignment::Left); + 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]); - // Set cursor position (using current_field_idx directly) 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)); @@ -171,4 +169,3 @@ pub fn render_canvas( active_field_input_rect } - diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 0791ccf..5021510 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -54,6 +54,7 @@ pub struct EventHandler { pub command_message: String, pub is_edit_mode: bool, pub is_highlight_mode: bool, + pub is_linewise_highlight: bool, pub highlight_anchor: Option<(usize, usize)>, pub edit_mode_cooldown: bool, pub ideal_cursor_column: usize, @@ -69,6 +70,7 @@ impl EventHandler { command_message: String::new(), is_edit_mode: false, is_highlight_mode: false, + is_linewise_highlight: false, highlight_anchor: None, edit_mode_cooldown: false, ideal_cursor_column: 0, @@ -216,9 +218,24 @@ impl EventHandler { }, AppMode::ReadOnly => { - if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode") + // Check for Linewise highlight first + if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode_linewise") && ModeManager::can_enter_highlight_mode(current_mode) { self.is_highlight_mode = true; + self.is_linewise_highlight = true; // Set linewise flag + let current_field_index = if app_state.ui.show_login { login_state.current_field() } + else if app_state.ui.show_register { register_state.current_field() } + else { form_state.current_field() }; + // For linewise, anchor char doesn't matter, use 0 + self.highlight_anchor = Some((current_field_index, 0)); + self.command_message = "-- LINE HIGHLIGHT --".to_string(); + return Ok(EventOutcome::Ok(self.command_message.clone())); + } + // Check for Character-wise highlight + else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode") + && ModeManager::can_enter_highlight_mode(current_mode) { + self.is_highlight_mode = true; + self.is_linewise_highlight = false; // Ensure linewise is false let current_field_index = if app_state.ui.show_login { login_state.current_field() } else if app_state.ui.show_register { register_state.current_field() } else { form_state.current_field() }; @@ -229,8 +246,8 @@ impl EventHandler { self.command_message = "-- HIGHLIGHT --".to_string(); return Ok(EventOutcome::Ok(self.command_message.clone())); } - - if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_edit_mode_before") + // Check for entering edit mode (before cursor) + else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_edit_mode_before") && ModeManager::can_enter_edit_mode(current_mode) { self.is_edit_mode = true; self.edit_mode_cooldown = true; @@ -238,8 +255,8 @@ impl EventHandler { terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; return Ok(EventOutcome::Ok(self.command_message.clone())); } - - if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_edit_mode_after") + // Check for entering edit mode (after cursor) + else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_edit_mode_after") && ModeManager::can_enter_edit_mode(current_mode) { let current_input = if app_state.ui.show_login || app_state.ui.show_register{ login_state.get_current_input() @@ -267,21 +284,17 @@ impl EventHandler { terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; return Ok(EventOutcome::Ok(self.command_message.clone())); } - - if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) { - if action == "enter_command_mode" && ModeManager::can_enter_command_mode(current_mode) { + // Check for entering command mode + else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_command_mode") + && ModeManager::can_enter_command_mode(current_mode) { self.command_mode = true; self.command_input.clear(); self.command_message.clear(); return Ok(EventOutcome::Ok(String::new())); - } } - if let Some(action) = config.get_action_for_key_in_mode( - &config.keybindings.common, - key_code, - modifiers - ) { + // Check for common actions (save, quit, etc.) only if no mode change happened + if let Some(action) = config.get_common_action(key_code, modifiers) { match action { "save" | "force_quit" | "save_and_quit" | "revert" => { return common_mode::handle_core_action( @@ -302,6 +315,7 @@ impl EventHandler { } } + // If no mode change or specific common action handled, delegate to read_only handler let (_should_exit, message) = read_only::handle_read_only_event( app_state, key, @@ -317,8 +331,9 @@ impl EventHandler { &mut self.edit_mode_cooldown, &mut self.ideal_cursor_column, ).await?; + // Note: handle_read_only_event should ignore mode entry keys internally now return Ok(EventOutcome::Ok(message)); - }, + }, // End AppMode::ReadOnly AppMode::Highlight => { if config.get_highlight_action_for_key(key_code, modifiers) == Some("exit_highlight_mode") { diff --git a/client/src/state/pages/form.rs b/client/src/state/pages/form.rs index de6adbf..4d3647c 100644 --- a/client/src/state/pages/form.rs +++ b/client/src/state/pages/form.rs @@ -34,6 +34,7 @@ impl FormState { theme: &Theme, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, total_count: u64, current_position: u64, @@ -51,6 +52,7 @@ impl FormState { theme, is_edit_mode, is_highlight_mode, + is_linewise_highlight, highlight_anchor, total_count, current_position, diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index cbbcdcb..13b9f94 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -34,6 +34,7 @@ pub fn render_ui( theme: &Theme, is_edit_mode: bool, is_highlight_mode: bool, + is_linewise_highlight: bool, highlight_anchor: Option<(usize, usize)>, total_count: u64, current_position: u64, @@ -95,6 +96,7 @@ pub fn render_ui( app_state, register_state.current_field < 4, is_highlight_mode, + is_linewise_highlight, highlight_anchor, ); } else if app_state.ui.show_login { @@ -106,6 +108,7 @@ pub fn render_ui( app_state, login_state.current_field < 2, is_highlight_mode, + is_linewise_highlight, highlight_anchor, ); } else if app_state.ui.show_admin { @@ -172,6 +175,7 @@ pub fn render_ui( theme, is_edit_mode, is_highlight_mode, + is_linewise_highlight, highlight_anchor, total_count, current_position, diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 285ace6..a59f14d 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -92,6 +92,7 @@ pub async fn run_ui() -> Result<(), Box> { &theme, is_edit_mode, event_handler.is_highlight_mode, + event_handler.is_linewise_highlight, event_handler.highlight_anchor, app_state.total_count, app_state.current_position,