sledoview 1.1.1

A CLI tool for viewing and managing SLED database files
Documentation
use crate::commands::Command;
use crate::db::SledViewer;
use anyhow::Result;
use colored::*;
use rustyline::completion::{Completer, Pair};
use rustyline::config::Configurer;
use rustyline::error::ReadlineError;
use rustyline::history::FileHistory;
use rustyline::{CompletionType, Context, Editor, Helper};

struct SledCompleter {
    keys: Vec<String>,
}

impl SledCompleter {
    fn new() -> Self {
        Self { keys: Vec::new() }
    }

    fn update_keys(&mut self, keys: Vec<String>) {
        self.keys = keys;
    }
}

impl Completer for SledCompleter {
    type Candidate = Pair;

    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &Context<'_>,
    ) -> rustyline::Result<(usize, Vec<Pair>)> {
        let line_up_to_cursor = &line[..pos];
        
        // Parse the command to see if we can complete keys
        let parts: Vec<&str> = line_up_to_cursor.split_whitespace().collect();
        
        if parts.len() >= 2 {
            let command = parts[0].to_lowercase();
            if command == "get" || (command == "list" && parts.len() >= 2 && parts[1] != "regex") || (command == "search" && parts.len() >= 2 && parts[1] != "regex") {
                // We're completing a key - find the current word being typed
                let current_word = if let Some(last_space) = line_up_to_cursor.rfind(' ') {
                    &line_up_to_cursor[last_space + 1..]
                } else {
                    ""
                };
                
                let mut candidates = Vec::new();
                for key in &self.keys {
                    if key.starts_with(current_word) {
                        candidates.push(Pair {
                            display: key.clone(),
                            replacement: key.clone(),
                        });
                    }
                }
                
                // Calculate the start position for replacement
                let start = if let Some(last_space) = line_up_to_cursor.rfind(' ') {
                    last_space + 1
                } else {
                    0
                };
                
                return Ok((start, candidates));
            }
        }
        
        // Fallback to command completion
        let commands = vec!["count", "list", "get", "search", "help", "exit", "quit"];
        let mut candidates = Vec::new();
        
        if let Some(word_start) = line_up_to_cursor.rfind(' ') {
            let word = &line_up_to_cursor[word_start + 1..];
            for cmd in commands {
                if cmd.starts_with(word) {
                    candidates.push(Pair {
                        display: cmd.to_string(),
                        replacement: cmd.to_string(),
                    });
                }
            }
            Ok((word_start + 1, candidates))
        } else {
            for cmd in commands {
                if cmd.starts_with(line_up_to_cursor) {
                    candidates.push(Pair {
                        display: cmd.to_string(),
                        replacement: cmd.to_string(),
                    });
                }
            }
            Ok((0, candidates))
        }
    }
}

// Minimal Helper implementation
impl Helper for SledCompleter {
    type Hint = String;
    
    fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<String> {
        None
    }
    
    fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
        &'s self,
        prompt: &'p str,
        _default: bool,
    ) -> std::borrow::Cow<'b, str> {
        std::borrow::Cow::Borrowed(prompt)
    }
    
    fn highlight<'l>(
        &self,
        line: &'l str,
        _pos: usize,
    ) -> std::borrow::Cow<'l, str> {
        std::borrow::Cow::Borrowed(line)
    }
    
    fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool {
        false
    }
    
    fn validate(
        &self,
        _ctx: &mut rustyline::validate::ValidationContext,
    ) -> rustyline::Result<rustyline::validate::ValidationResult> {
        Ok(rustyline::validate::ValidationResult::Valid(None))
    }
    
    fn validate_while_typing(&self) -> bool {
        false
    }
}

pub struct Repl {
    editor: Editor<SledCompleter, FileHistory>,
    viewer: SledViewer,
}

impl Repl {
    pub fn new(viewer: SledViewer) -> Self {
        let mut editor = Editor::<SledCompleter, FileHistory>::new().expect("Failed to create readline editor");
        let helper = SledCompleter::new();
        editor.set_helper(Some(helper));
        editor.set_completion_type(CompletionType::Circular);
        
        Self { editor, viewer }
    }

    pub fn run(&mut self) -> Result<()> {
        println!();
        println!(
            "{}",
            "Interactive SLED Database Client".bright_cyan().bold()
        );
        println!(
            "{}",
            "Type 'help' for available commands or 'exit' to quit.".bright_black()
        );
        println!(
            "{}",
            "Use TAB to autocomplete key names.".bright_black()
        );
        println!();

        // Load all keys for completion
        if let Ok(keys) = self.viewer.list_keys("*", false) {
            if let Some(helper) = self.editor.helper_mut() {
                helper.update_keys(keys);
            }
        }

        loop {
            let readline = self.editor.readline("> ");

            match readline {
                Ok(line) => {
                    let line = line.trim();
                    if line.is_empty() {
                        continue;
                    }

                    // Add to history
                    if let Err(e) = self.editor.add_history_entry(line) {
                        eprintln!("Warning: Failed to add to history: {e}");
                    }

                    match Command::parse(line) {
                        Some(Command::Exit) => {
                            println!("{}", "Goodbye!".bright_green());
                            break;
                        }
                        Some(command) => {
                            if let Err(e) = command.execute(&self.viewer) {
                                println!(
                                    "{} {}",
                                    "Error:".bright_red().bold(),
                                    e.to_string().red()
                                );
                            }
                            // Reload keys after any command in case database changed
                            if let Ok(keys) = self.viewer.list_keys("*", false) {
                                if let Some(helper) = self.editor.helper_mut() {
                                    helper.update_keys(keys);
                                }
                            }
                        }
                        None => {
                            println!(
                                "{} Unknown command: '{}'. Type 'help' for available commands.",
                                "Error:".bright_red().bold(),
                                line.bright_yellow()
                            );
                        }
                    }
                }
                Err(ReadlineError::Interrupted) => {
                    println!("{}", "Goodbye!".bright_green());
                    break;
                }
                Err(ReadlineError::Eof) => {
                    println!("{}", "Goodbye!".bright_green());
                    break;
                }
                Err(err) => {
                    println!("{} {}", "Error:".bright_red().bold(), err);
                    break;
                }
            }
        }

        Ok(())
    }
}