sql-cli 1.72.0

SQL query tool for CSV/JSON with both interactive TUI and non-interactive CLI modes - perfect for exploration and automation
Documentation
use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    terminal::{disable_raw_mode, enable_raw_mode},
};
use sql_cli::app_state_container::SelectionMode;
use sql_cli::buffer::AppMode;
use sql_cli::ui::input::actions::{Action, ActionContext};
use sql_cli::ui::key_handling::{ChordResult, KeyChordHandler, KeyMapper};
use std::io::{self, Write};

fn format_key(key: &KeyEvent) -> String {
    let mut result = String::new();

    // Add modifiers
    if key.modifiers.contains(KeyModifiers::CONTROL) {
        result.push_str("Ctrl+");
    }
    if key.modifiers.contains(KeyModifiers::ALT) {
        result.push_str("Alt+");
    }
    if key.modifiers.contains(KeyModifiers::SHIFT) {
        result.push_str("Shift+");
    }

    // Add key code
    match key.code {
        KeyCode::Char(c) => result.push(c),
        KeyCode::F(n) => result.push_str(&format!("F{n}")),
        KeyCode::Up => result.push(''),
        KeyCode::Down => result.push(''),
        KeyCode::Left => result.push(''),
        KeyCode::Right => result.push(''),
        KeyCode::PageUp => result.push_str("PgUp"),
        KeyCode::PageDown => result.push_str("PgDn"),
        KeyCode::Home => result.push_str("Home"),
        KeyCode::End => result.push_str("End"),
        KeyCode::Enter => result.push_str("Enter"),
        KeyCode::Tab => result.push_str("Tab"),
        KeyCode::Backspace => result.push_str("Bksp"),
        KeyCode::Delete => result.push_str("Del"),
        KeyCode::Esc => result.push_str("Esc"),
        _ => result.push('?'),
    }

    result
}

fn format_action(action: &Action) -> String {
    match action {
        Action::Navigate(nav) => format!("Navigate({nav:?})"),
        Action::ToggleSelectionMode => "ToggleSelectionMode".to_string(),
        Action::Quit => "Quit".to_string(),
        Action::ForceQuit => "ForceQuit".to_string(),
        Action::ShowHelp => "ShowHelp".to_string(),
        Action::ShowDebugInfo => "ShowDebugInfo".to_string(),
        Action::ToggleColumnPin => "ToggleColumnPin".to_string(),
        Action::Sort(col) => format!("Sort({col:?})"),
        Action::ExitCurrentMode => "ExitCurrentMode".to_string(),
        _ => format!("{action:?}"),
    }
}

fn main() -> io::Result<()> {
    println!("╔══════════════════════════════════════════════════════════════╗");
    println!("║         SQL CLI Action System Logger (Simple Version)        ║");
    println!("╠══════════════════════════════════════════════════════════════╣");
    println!("║ Press keys to see how they map to actions.                  ║");
    println!("║ Try: j, k, h, l, 5j, v, p, s, F1, arrows, etc.             ║");
    println!("║ Press Ctrl+C or 'q' to quit.                                ║");
    println!("╚══════════════════════════════════════════════════════════════╝");
    println!();

    enable_raw_mode()?;

    let mut key_mapper = KeyMapper::new();
    let mut chord_handler = KeyChordHandler::new();
    let mut count_display = String::new();

    loop {
        if event::poll(std::time::Duration::from_millis(100))? {
            if let Event::Key(key) = event::read()? {
                // Filter out key release events
                if key.kind != crossterm::event::KeyEventKind::Press {
                    continue;
                }

                let key_str = format_key(&key);

                // Debug: show if chord is active before processing
                if chord_handler.is_chord_mode_active() {
                    println!("{key_str:6} │ [Chord active, processing...]");
                }

                // Process through chord handler first
                let chord_result = chord_handler.process_key(key);

                match chord_result {
                    ChordResult::CompleteChord(action) => {
                        println!("{key_str:6} │ CHORD COMPLETE => {action:?}");
                        io::stdout().flush().unwrap();

                        // Check for quit action
                        if matches!(action, sql_cli::ui::input::actions::Action::Quit) {
                            break;
                        }
                        continue;
                    }
                    ChordResult::PartialChord(description) => {
                        println!("{key_str:6} │ CHORD: {description}");
                        io::stdout().flush().unwrap();
                        continue;
                    }
                    ChordResult::Cancelled => {
                        println!("{key_str:6} │ CHORD CANCELLED");
                        io::stdout().flush().unwrap();
                        continue;
                    }
                    ChordResult::SingleKey(_) => {
                        // Continue with normal processing
                    }
                }

                // Build context
                let context = ActionContext {
                    mode: AppMode::Results,
                    selection_mode: SelectionMode::Row,
                    has_results: true,
                    has_filter: false,
                    has_search: false,
                    row_count: 100,
                    column_count: 10,
                    current_row: 5,
                    current_column: 2,
                };

                // Check count buffer before
                let count_before = key_mapper.get_count_buffer().to_string();
                let was_collecting = !count_before.is_empty();

                // Map the key
                let action = key_mapper.map_key(key, &context);

                // Check count buffer after
                let count_after = key_mapper.get_count_buffer().to_string();
                let is_collecting = !count_after.is_empty();

                // Format output
                if was_collecting && !is_collecting && action.is_some() {
                    // Count was applied
                    println!(
                        "{:6} │ Count: {:3} │ => {}",
                        key_str,
                        count_before,
                        format_action(action.as_ref().unwrap())
                    );
                } else if is_collecting {
                    // Building count
                    count_display = count_after.clone();
                    println!("{key_str:6} │ Building count: {count_display}");
                } else if let Some(ref act) = action {
                    // Normal action
                    println!("{:6} │ => {}", key_str, format_action(act));
                } else {
                    // No mapping
                    println!("{key_str:6} │ (no mapping in Results mode)");
                }

                // Flush stdout to ensure each line appears immediately
                io::stdout().flush().unwrap();

                // Check for quit
                if matches!(action, Some(Action::Quit | Action::ForceQuit)) {
                    break;
                }
            }
        }
    }

    disable_raw_mode()?;
    println!();
    println!("Goodbye!");
    Ok(())
}