mumu 0.9.1

Lava Mumu is a language for those in the now and that know
Documentation
// src/modules/repl/input.rs

use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use std::collections::VecDeque;
use std::io::{self, Write};
use std::time::Duration;

use crate::parser::interpreter::Interpreter;
use super::autocomplete::{gather_completions_for_prefix, last_word_for_autocomplete};

pub struct EphemeralCompletion {
    pub candidates: Vec<String>,
    pub cycle_idx: usize,
    pub prefix_start: usize,
    pub prefix_len: usize,
}

impl EphemeralCompletion {
    pub fn new(candidates: Vec<String>, prefix_start: usize, prefix_len: usize) -> Self {
        EphemeralCompletion {
            candidates,
            cycle_idx: 0,
            prefix_start,
            prefix_len,
        }
    }
    pub fn current_suffix(&self) -> String {
        let cand = &self.candidates[self.cycle_idx];
        if cand.len() > self.prefix_len {
            cand[self.prefix_len..].to_string()
        } else {
            "".to_string()
        }
    }
    pub fn chosen(&self) -> &str {
        &self.candidates[self.cycle_idx]
    }
}

pub struct ReadLineState {
    pub buffer: Vec<char>,
    pub cursor_pos: usize,
    pub ephemeral: Option<EphemeralCompletion>,
}

impl ReadLineState {
    pub fn new() -> Self {
        ReadLineState {
            buffer: Vec::new(),
            cursor_pos: 0,
            ephemeral: None,
        }
    }
    pub fn typed_line(&self) -> String {
        self.buffer.iter().collect()
    }
}

fn user_broke_ephemeral(code: &KeyCode) -> bool {
    match code {
        KeyCode::Tab | KeyCode::Right => false,
        _ => true,
    }
}

pub fn read_line_with_arrows(
    verbose: bool,
    history: &mut VecDeque<String>,
    prompt: &str,
    interp: &mut Interpreter,
    redraw_full: &dyn Fn(&mut io::Stdout, &str, &ReadLineState, bool, &mut Interpreter) -> Result<(), String>,
) -> Result<String, String> {
    let mut stdout = io::stdout();
    let mut local_state = ReadLineState::new();
    let mut history_idx: Option<usize> = None;
    let mut partial_new_line = Vec::new();

    redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;

    loop {
        if crossterm::event::poll(Duration::from_millis(50)).map_err(|e| e.to_string())? {
            match crossterm::event::read().map_err(|e| e.to_string())? {
                Event::Key(KeyEvent { code, modifiers, .. }) => {
                    match code {
                        KeyCode::Char('l') if modifiers.contains(KeyModifiers::CONTROL) => {
                            use crossterm::terminal::{Clear, ClearType};
                            use crossterm::queue;
                            use crossterm::cursor::MoveTo;
                            queue!(
                                stdout,
                                Clear(ClearType::All),
                                MoveTo(0, 0)
                            )
                            .map_err(|e| e.to_string())?;

                            stdout.flush().map_err(|e| e.to_string())?;
                            redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;
                            continue;
                        }
                        KeyCode::Enter => {
                            let line_str = local_state.typed_line();
                            if line_str == ":q" || line_str == ":quit" {
                                redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;
                                println!();
                                return Ok(line_str);
                            }
                            redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;
                            println!();
                            return Ok(line_str);
                        }
                        KeyCode::Char('d') | KeyCode::Char('c')
                            if modifiers.contains(KeyModifiers::CONTROL) =>
                        {
                            redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;
                            return Ok("<<<EXIT>>>".to_string());
                        }
                        KeyCode::Tab => {
                            let typed_str = local_state.typed_line();
                            let ephemeral_opt = local_state.ephemeral.take();
                            if let Some(mut ephemeral) = ephemeral_opt {
                                if ephemeral.candidates.len() == 1 {
                                    let (start_idx, prefix) = last_word_for_autocomplete(
                                        &typed_str,
                                        local_state.cursor_pos
                                    );
                                    for _ in 0..prefix.len() {
                                        local_state.buffer.remove(start_idx);
                                    }
                                    let chosen = ephemeral.chosen();
                                    for (i, ch) in chosen.chars().enumerate() {
                                        local_state.buffer.insert(start_idx + i, ch);
                                    }
                                    local_state.cursor_pos = start_idx + chosen.len();
                                } else {
                                    ephemeral.cycle_idx =
                                        (ephemeral.cycle_idx + 1) % ephemeral.candidates.len();
                                    local_state.ephemeral = Some(ephemeral);
                                }
                            } else {
                                let (start_idx, prefix) = last_word_for_autocomplete(
                                    &typed_str,
                                    local_state.cursor_pos
                                );
                                if let Some(cands) = gather_completions_for_prefix(&prefix) {
                                    if !cands.is_empty() {
                                        let ep = EphemeralCompletion::new(
                                            cands,
                                            start_idx,
                                            prefix.len()
                                        );
                                        if ep.candidates.len() == 1 {
                                            let (start_idx2, prefix2) =
                                                last_word_for_autocomplete(
                                                    &typed_str,
                                                    local_state.cursor_pos
                                                );
                                            for _ in 0..prefix2.len() {
                                                local_state.buffer.remove(start_idx2);
                                            }
                                            let chosen = ep.chosen();
                                            for (i, ch) in chosen.chars().enumerate() {
                                                local_state.buffer.insert(start_idx2 + i, ch);
                                            }
                                            local_state.cursor_pos = start_idx2 + chosen.len();
                                        } else {
                                            local_state.ephemeral = Some(ep);
                                        }
                                    }
                                }
                            }
                        }
                        KeyCode::Right => {
                            let typed_str = local_state.typed_line();
                            let ephemeral_opt = local_state.ephemeral.take();
                            if let Some(ephemeral) = ephemeral_opt {
                                let (start_idx, prefix) = last_word_for_autocomplete(
                                    &typed_str,
                                    local_state.cursor_pos
                                );
                                for _ in 0..prefix.len() {
                                    local_state.buffer.remove(start_idx);
                                }
                                let chosen = ephemeral.chosen();
                                for (i, ch) in chosen.chars().enumerate() {
                                    local_state.buffer.insert(start_idx + i, ch);
                                }
                                local_state.cursor_pos = start_idx + chosen.len();
                            } else {
                                if local_state.cursor_pos < local_state.buffer.len() {
                                    local_state.cursor_pos += 1;
                                }
                            }
                            history_idx = None;
                        }
                        _ => {
                            if user_broke_ephemeral(&code) {
                                local_state.ephemeral = None;
                            }
                            match code {
                                KeyCode::Left => {
                                    if local_state.cursor_pos > 0 {
                                        local_state.cursor_pos -= 1;
                                    }
                                    history_idx = None;
                                }
                                KeyCode::Up => {
                                    if !history.is_empty() {
                                        match history_idx {
                                            None => {
                                                partial_new_line = local_state.buffer.clone();
                                                let new_idx = history.len() - 1;
                                                history_idx = Some(new_idx);
                                                let hline = &history[new_idx];
                                                local_state.buffer = hline.chars().collect();
                                                local_state.cursor_pos =
                                                    local_state.buffer.len();
                                            }
                                            Some(old) => {
                                                if old > 0 {
                                                    let new_i = old - 1;
                                                    history_idx = Some(new_i);
                                                    let hline = &history[new_i];
                                                    local_state.buffer =
                                                        hline.chars().collect();
                                                    local_state.cursor_pos =
                                                        local_state.buffer.len();
                                                }
                                            }
                                        }
                                    }
                                }
                                KeyCode::Down => {
                                    if !history.is_empty() {
                                        match history_idx {
                                            None => {}
                                            Some(old) => {
                                                if old < history.len() - 1 {
                                                    let new_i = old + 1;
                                                    history_idx = Some(new_i);
                                                    let hline = &history[new_i];
                                                    local_state.buffer =
                                                        hline.chars().collect();
                                                    local_state.cursor_pos =
                                                        local_state.buffer.len();
                                                } else {
                                                    history_idx = None;
                                                    local_state.buffer =
                                                        partial_new_line.clone();
                                                    local_state.cursor_pos =
                                                        local_state.buffer.len();
                                                }
                                            }
                                        }
                                    }
                                }
                                KeyCode::Backspace => {
                                    if local_state.cursor_pos > 0 {
                                        local_state.buffer.remove(local_state.cursor_pos - 1);
                                        local_state.cursor_pos -= 1;
                                        history_idx = None;
                                    }
                                }
                                KeyCode::Char(ch) => {
                                    local_state.buffer.insert(local_state.cursor_pos, ch);
                                    local_state.cursor_pos += 1;
                                    history_idx = None;
                                }
                                _ => {}
                            }
                        }
                    }
                    redraw_full(&mut stdout, prompt, &local_state, verbose, interp)?;
                }
                _ => {}
            }
        }
    }
}