modalkit 0.0.25

A library for building applications that use modal editing
Documentation
//! # Editing environments
//!
//! ## Overview
//!
//! This module contains components for recreating different flavors of editing environments.
//!
use crossterm::event::KeyModifiers;

use crate::{
    key::TerminalKey,
    keybindings::{EdgeEvent, EdgePathPart, InputKey, InputKeyClass},
    prelude::Char,
};

#[macro_use]
mod macros;

mod keyparse;

pub mod emacs;
pub mod mixed;
pub mod vim;

pub(crate) type CommonEdgeEvent = EdgeEvent<TerminalKey, CommonKeyClass>;
pub(crate) type CommonEdgePathPart = EdgePathPart<TerminalKey, CommonKeyClass>;
pub(crate) type CommonEdgePath = Vec<CommonEdgePathPart>;

/// Classes of characters that input keys can belong to.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CommonKeyClass {
    /// A number between 0 and 9.
    Count,

    /// A character representing a register.
    Register,

    /// A character representing a mark.
    Mark,

    /// A number between 0 and 7.
    Octal,

    /// A number between 0 and 9.
    Decimal,

    /// A number between 0 and F.
    Hexadecimal,

    /// The first character in a digraph sequence.
    Digraph1,

    /// The second character in a digraph sequence.
    Digraph2,
}

impl InputKeyClass<TerminalKey> for CommonKeyClass {
    fn memberships(ke: &TerminalKey) -> Vec<Self> {
        let mut classes = Vec::new();

        if let Some(('0'..='9', mods)) = ke.get_char_mods() {
            if !mods.contains(KeyModifiers::CONTROL) {
                classes.push(CommonKeyClass::Count);
            }
        }

        if let Some(c) = ke.get_char() {
            if is_register_char(c) {
                classes.push(CommonKeyClass::Register);
            }

            if is_mark_char(c) {
                classes.push(CommonKeyClass::Mark);
            }

            if let '0'..='7' = c {
                classes.push(CommonKeyClass::Octal);
            }

            if let '0'..='9' = c {
                classes.push(CommonKeyClass::Decimal);
            }

            if let '0'..='9' | 'a'..='f' | 'A'..='F' = c {
                classes.push(CommonKeyClass::Hexadecimal);
            }

            classes.push(CommonKeyClass::Digraph1);
            classes.push(CommonKeyClass::Digraph2);
        }

        return classes;
    }
}

#[inline]
fn is_register_char(c: char) -> bool {
    match c {
        'a'..='z' => true,
        'A'..='Z' => true,
        '0'..='9' => true,
        '"' => true,
        '-' => true,
        '#' => true,
        '_' => true,
        '%' => true,
        ':' => true,
        '.' => true,
        '/' => true,
        '*' => true,
        '+' => true,
        _ => false,
    }
}

#[inline]
fn is_mark_char(c: char) -> bool {
    match c {
        'a'..='z' => true,
        'A'..='Z' => true,
        '0'..='9' => true,
        '\'' | '`' => true,
        '<' | '>' => true,
        '[' | ']' => true,
        '"' => true,
        '^' => true,
        '.' => true,

        _ => false,
    }
}

/// Fields for tracking entered codepoints, literals and digraphs.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct CharacterContext {
    dec: Option<u32>,
    oct: Option<u32>,
    hex: Option<u32>,
    any: Option<TerminalKey>,
    digraph1: Option<char>,
    digraph2: Option<char>,
}

impl CharacterContext {
    fn get_typed(&self) -> Option<Char> {
        if let Some((d1, d2)) = self.get_digraph() {
            let c = Char::Digraph(d1, d2);

            Some(c)
        } else if let Some(cp) = self.get_codepoint() {
            let c = char::from_u32(cp)?;
            let c = Char::Single(c);

            Some(c)
        } else if let Some(c) = self.get_literal_char() {
            let c = Char::Single(c);

            Some(c)
        } else if let Some(s) = self.get_literal_string() {
            let c = Char::CtrlSeq(s);

            Some(c)
        } else {
            None
        }
    }

    fn get_digraph(&self) -> Option<(char, char)> {
        if let (Some(a), Some(b)) = (self.digraph1, self.digraph2) {
            Some((a, b))
        } else {
            None
        }
    }

    fn get_codepoint(&self) -> Option<u32> {
        self.hex.or(self.dec).or(self.oct)
    }

    fn get_literal_char(&self) -> Option<char> {
        self.any.as_ref().and_then(TerminalKey::get_literal_char)
    }

    fn get_literal_string(&self) -> Option<String> {
        self.any.as_ref().map(ToString::to_string)
    }
}

/// Configure an [InputBindings] instance so that it's suitable for shell-like contexts.
///
/// For example, the Enter key usually types a newline when inserting text, but would be expected
/// to instead submit text in a shell context.
///
/// [InputBindings]: crate::keybindings::InputBindings
pub trait ShellBindings {
    /// Configure bindings so that they can used in a shell-like context.
    fn shell(self) -> Self;
}