orchestral-cli 0.2.0

Command line interface for running Orchestral workflows
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

#[derive(Debug, Default, Clone)]
pub struct Composer {
    pub buffer: String,
    pub cursor: usize,
}

impl Composer {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert_text(&mut self, text: &str) -> bool {
        if text.is_empty() {
            return false;
        }
        if self.cursor > self.buffer.len() || !self.buffer.is_char_boundary(self.cursor) {
            return false;
        }
        self.buffer.insert_str(self.cursor, text);
        self.cursor += text.len();
        true
    }

    pub fn on_key(&mut self, key: KeyEvent) -> bool {
        match key.code {
            KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
                if self.cursor <= self.buffer.len() {
                    self.buffer.insert(self.cursor, c);
                    self.cursor += c.len_utf8();
                    true
                } else {
                    false
                }
            }
            KeyCode::Backspace => {
                if self.cursor == 0 {
                    return false;
                }
                let prev_idx = prev_grapheme_boundary(&self.buffer, self.cursor);
                self.buffer.drain(prev_idx..self.cursor);
                self.cursor = prev_idx;
                true
            }
            KeyCode::Delete => {
                if self.cursor >= self.buffer.len() {
                    return false;
                }
                let next_idx = next_grapheme_boundary(&self.buffer, self.cursor);
                self.buffer.drain(self.cursor..next_idx);
                true
            }
            KeyCode::Left => {
                if self.cursor == 0 {
                    return false;
                }
                self.cursor = prev_grapheme_boundary(&self.buffer, self.cursor);
                true
            }
            KeyCode::Right => {
                if self.cursor >= self.buffer.len() {
                    return false;
                }
                self.cursor = next_grapheme_boundary(&self.buffer, self.cursor);
                true
            }
            KeyCode::Home => {
                if self.cursor == 0 {
                    return false;
                }
                self.cursor = 0;
                true
            }
            KeyCode::End => {
                if self.cursor == self.buffer.len() {
                    return false;
                }
                self.cursor = self.buffer.len();
                true
            }
            _ => false,
        }
    }

    pub fn take_buffer(&mut self) -> String {
        let out = self.buffer.trim().to_string();
        self.buffer.clear();
        self.cursor = 0;
        out
    }
}

fn prev_grapheme_boundary(text: &str, idx: usize) -> usize {
    let mut i = idx.saturating_sub(1);
    while !text.is_char_boundary(i) {
        i = i.saturating_sub(1);
    }
    i
}

fn next_grapheme_boundary(text: &str, idx: usize) -> usize {
    let mut i = idx.saturating_add(1).min(text.len());
    while i < text.len() && !text.is_char_boundary(i) {
        i += 1;
    }
    i
}