use parking_lot::ReentrantMutex;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::Mutex;
#[derive(Debug)]
pub struct ZleState {
pub buffer: String,
pub cursor: usize,
pub mark: usize,
pub numeric_arg: Option<i32>,
pub insert_mode: bool,
pub last_find_char: Option<char>,
pub find_forward: bool,
undo_history: Vec<(String, usize)>,
pub undo_stack: Vec<(String, usize)>,
kill_ring: VecDeque<String>,
kill_ring_max: usize,
pub vi_cmd_mode: bool,
pub keymap: KeymapName,
last_yank_pos: Option<(usize, usize)>,
pub region_active: bool,
}
impl Default for ZleState {
fn default() -> Self {
Self::new()
}
}
impl ZleState {
pub fn new() -> Self {
ZleState {
buffer: String::new(),
cursor: 0,
mark: 0,
numeric_arg: None,
insert_mode: true,
last_find_char: None,
find_forward: true,
undo_history: Vec::new(),
undo_stack: Vec::new(),
kill_ring: VecDeque::new(),
kill_ring_max: 8,
vi_cmd_mode: false,
keymap: KeymapName::Emacs,
last_yank_pos: None,
region_active: false,
}
}
pub fn save_undo(&mut self) {
self.undo_history.push((self.buffer.clone(), self.cursor));
if self.undo_history.len() > 100 {
self.undo_history.remove(0);
}
}
pub fn undo(&mut self) -> bool {
if let Some((buffer, cursor)) = self.undo_history.pop() {
self.undo_stack.push((self.buffer.clone(), self.cursor));
self.buffer = buffer;
self.cursor = cursor;
true
} else {
false
}
}
pub fn redo(&mut self) -> bool {
if let Some((buffer, cursor)) = self.undo_stack.pop() {
self.undo_history.push((self.buffer.clone(), self.cursor));
self.buffer = buffer;
self.cursor = cursor;
true
} else {
false
}
}
pub fn kill_add(&mut self, text: &str) {
self.kill_ring.push_front(text.to_string());
if self.kill_ring.len() > self.kill_ring_max {
self.kill_ring.pop_back();
}
}
pub fn yank(&mut self) -> Option<String> {
if let Some(text) = self.kill_ring.front().cloned() {
let start = self.cursor;
let chars: Vec<char> = self.buffer.chars().collect();
let mut new_buffer = String::new();
for (i, c) in chars.iter().enumerate() {
if i == self.cursor {
new_buffer.push_str(&text);
}
new_buffer.push(*c);
}
if self.cursor >= chars.len() {
new_buffer.push_str(&text);
}
self.buffer = new_buffer;
self.cursor += text.chars().count();
self.last_yank_pos = Some((start, self.cursor));
Some(text)
} else {
None
}
}
pub fn yank_pop(&mut self) -> Option<String> {
if let Some((start, end)) = self.last_yank_pos {
let chars: Vec<char> = self.buffer.chars().collect();
let mut new_buffer = String::new();
for (i, c) in chars.iter().enumerate() {
if i < start || i >= end {
new_buffer.push(*c);
}
}
self.buffer = new_buffer;
self.cursor = start;
if let Some(front) = self.kill_ring.pop_front() {
self.kill_ring.push_back(front);
}
self.yank()
} else {
None
}
}
pub fn kill_yank(&self) -> Option<&str> {
self.kill_ring.front().map(|s| s.as_str())
}
pub fn kill_rotate(&mut self) {
if let Some(front) = self.kill_ring.pop_front() {
self.kill_ring.push_back(front);
}
}
}
pub struct ZleManager {
pub keymaps: HashMap<KeymapName, Keymap>,
user_widgets: HashMap<String, String>,
}
impl Default for ZleManager {
fn default() -> Self {
Self::new()
}
}
impl ZleManager {
pub fn new() -> Self {
let mut mgr = ZleManager {
keymaps: HashMap::new(),
user_widgets: HashMap::new(),
};
mgr.keymaps
.insert(KeymapName::Main, Keymap::emacs_default());
mgr.keymaps
.insert(KeymapName::Emacs, Keymap::emacs_default());
mgr.keymaps
.insert(KeymapName::ViInsert, Keymap::viins_default());
mgr.keymaps
.insert(KeymapName::ViCommand, Keymap::vicmd_default());
mgr.keymaps.insert(KeymapName::Isearch, Keymap::new());
mgr.keymaps.insert(KeymapName::Command, Keymap::new());
mgr.keymaps.insert(KeymapName::MenuSelect, Keymap::new());
mgr
}
pub fn define_widget(&mut self, name: &str, func: &str) {
self.user_widgets.insert(name.to_string(), func.to_string());
}
pub fn get_widget<'a>(&'a self, name: &'a str) -> Option<&'a str> {
if let Some(func) = self.user_widgets.get(name) {
return Some(func);
}
if BUILTIN_WIDGETS.contains(&name) {
return Some(name);
}
None
}
pub fn bind_key(&mut self, keymap: KeymapName, key: &str, widget: &str) {
if let Some(km) = self.keymaps.get_mut(&keymap) {
km.bind(key, widget);
}
}
pub fn unbind_key(&mut self, keymap: KeymapName, key: &str) {
if let Some(km) = self.keymaps.get_mut(&keymap) {
km.unbind(key);
}
}
pub fn execute_widget(
&mut self,
name: &str,
_key: Option<char>,
) -> super::widgets::WidgetResult {
if self.get_widget(name).is_some() {
super::widgets::WidgetResult::Ok
} else {
super::widgets::WidgetResult::Error(format!("Unknown widget: {}", name))
}
}
pub fn list_widgets(&self) -> Vec<&str> {
let mut widgets: Vec<&str> = BUILTIN_WIDGETS.to_vec();
for name in self.user_widgets.keys() {
widgets.push(name.as_str());
}
widgets
}
}
const BUILTIN_WIDGETS: &[&str] = &[
"accept-line",
"accept-and-hold",
"backward-char",
"backward-delete-char",
"backward-kill-line",
"backward-kill-word",
"backward-word",
"beep",
"beginning-of-history",
"beginning-of-line",
"capitalize-word",
"clear-screen",
"complete-word",
"copy-region-as-kill",
"delete-char",
"delete-char-or-list",
"down-case-word",
"down-history",
"down-line-or-history",
"down-line-or-search",
"end-of-history",
"end-of-line",
"exchange-point-and-mark",
"execute-named-cmd",
"expand-or-complete",
"forward-char",
"forward-word",
"history-incremental-search-backward",
"history-incremental-search-forward",
"kill-buffer",
"kill-line",
"kill-region",
"kill-whole-line",
"kill-word",
"overwrite-mode",
"quoted-insert",
"redisplay",
"redo",
"self-insert",
"send-break",
"set-mark-command",
"transpose-chars",
"transpose-words",
"undo",
"up-case-word",
"up-history",
"up-line-or-history",
"up-line-or-search",
"vi-add-eol",
"vi-add-next",
"vi-backward-blank-word",
"vi-backward-char",
"vi-backward-delete-char",
"vi-backward-word",
"vi-change",
"vi-change-eol",
"vi-change-whole-line",
"vi-cmd-mode",
"vi-delete",
"vi-delete-char",
"vi-end-of-line",
"vi-find-next-char",
"vi-find-next-char-skip",
"vi-find-prev-char",
"vi-find-prev-char-skip",
"vi-first-non-blank",
"vi-forward-blank-word",
"vi-forward-char",
"vi-forward-word",
"vi-forward-word-end",
"vi-insert",
"vi-insert-bol",
"vi-join",
"vi-kill-eol",
"vi-open-line-above",
"vi-open-line-below",
"vi-put-after",
"vi-put-before",
"vi-repeat-change",
"vi-repeat-find",
"vi-repeat-search",
"vi-replace",
"vi-replace-chars",
"vi-rev-repeat-find",
"vi-rev-repeat-search",
"vi-substitute",
"vi-yank",
"vi-yank-whole-line",
"which-command",
"yank",
"yank-pop",
];
thread_local! {
static ZLE_MANAGER: RefCell<ZleManager> = RefCell::new(ZleManager::new());
}
pub struct ZleGuard<'a>(std::cell::RefMut<'a, ZleManager>);
impl<'a> std::ops::Deref for ZleGuard<'a> {
type Target = ZleManager;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> std::ops::DerefMut for ZleGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub fn zle() -> ZleGuard<'static> {
ZLE_MANAGER.with(|m| {
ZleGuard(unsafe { std::mem::transmute(m.borrow_mut()) })
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeymapName {
Emacs,
ViInsert,
ViCommand,
Main, Isearch, Command, MenuSelect, }
impl KeymapName {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"emacs" => Some(Self::Emacs),
"viins" => Some(Self::ViInsert),
"vicmd" => Some(Self::ViCommand),
"main" => Some(Self::Main),
"isearch" => Some(Self::Isearch),
"command" => Some(Self::Command),
"menuselect" => Some(Self::MenuSelect),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Emacs => "emacs",
Self::ViInsert => "viins",
Self::ViCommand => "vicmd",
Self::Main => "main",
Self::Isearch => "isearch",
Self::Command => "command",
Self::MenuSelect => "menuselect",
}
}
}
#[derive(Debug, Clone)]
pub struct Keymap {
bindings: HashMap<String, String>,
}
impl Default for Keymap {
fn default() -> Self {
Self::new()
}
}
impl Keymap {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn emacs_default() -> Self {
let mut km = Self::new();
km.bind("^F", "forward-char");
km.bind("^B", "backward-char");
km.bind("^A", "beginning-of-line");
km.bind("^E", "end-of-line");
km.bind("\\ef", "forward-word"); km.bind("\\eb", "backward-word");
km.bind("^D", "delete-char");
km.bind("^H", "backward-delete-char");
km.bind("^?", "backward-delete-char"); km.bind("^K", "kill-line");
km.bind("^U", "backward-kill-line");
km.bind("\\ed", "kill-word"); km.bind("\\e^?", "backward-kill-word"); km.bind("^W", "backward-kill-word");
km.bind("^Y", "yank");
km.bind("\\ey", "yank-pop");
km.bind("^_", "undo");
km.bind("^X^U", "undo");
km.bind("\\e_", "redo");
km.bind("^P", "up-line-or-history");
km.bind("^N", "down-line-or-history");
km.bind("\\e<", "beginning-of-history");
km.bind("\\e>", "end-of-history");
km.bind("^R", "history-incremental-search-backward");
km.bind("^S", "history-incremental-search-forward");
km.bind("^I", "expand-or-complete"); km.bind("\\e\\e", "complete-word");
km.bind("^J", "accept-line"); km.bind("^M", "accept-line"); km.bind("^G", "send-break");
km.bind("^C", "send-break");
km.bind("^L", "clear-screen");
km.bind("^T", "transpose-chars");
km.bind("\\et", "transpose-words");
km.bind("\\ec", "capitalize-word");
km.bind("\\el", "down-case-word");
km.bind("\\eu", "up-case-word");
km.bind("^@", "set-mark-command"); km.bind("^X^X", "exchange-point-and-mark");
km.bind("\\ew", "copy-region-as-kill");
km
}
pub fn viins_default() -> Self {
let mut km = Self::new();
km.bind("^[", "vi-cmd-mode");
km.bind("^H", "backward-delete-char");
km.bind("^?", "backward-delete-char");
km.bind("^W", "backward-kill-word");
km.bind("^U", "backward-kill-line");
km.bind("^J", "accept-line");
km.bind("^M", "accept-line");
km.bind("^I", "expand-or-complete");
km.bind("^P", "up-line-or-history");
km.bind("^N", "down-line-or-history");
km
}
pub fn vicmd_default() -> Self {
let mut km = Self::new();
km.bind("i", "vi-insert");
km.bind("a", "vi-add-next");
km.bind("I", "vi-insert-bol");
km.bind("A", "vi-add-eol");
km.bind("h", "backward-char");
km.bind("l", "forward-char");
km.bind("w", "forward-word");
km.bind("b", "backward-word");
km.bind("0", "beginning-of-line");
km.bind("^", "beginning-of-line");
km.bind("$", "end-of-line");
km.bind("x", "delete-char");
km.bind("X", "backward-delete-char");
km.bind("dd", "kill-whole-line");
km.bind("dw", "kill-word");
km.bind("db", "backward-kill-word");
km.bind("d$", "kill-line");
km.bind("d0", "backward-kill-line");
km.bind("y", "vi-yank");
km.bind("p", "vi-put-after");
km.bind("P", "vi-put-before");
km.bind("k", "up-line-or-history");
km.bind("j", "down-line-or-history");
km.bind("/", "history-incremental-search-backward");
km.bind("?", "history-incremental-search-forward");
km.bind("n", "vi-repeat-search");
km.bind("N", "vi-rev-repeat-search");
km.bind("u", "undo");
km.bind("^R", "redo");
km.bind("^J", "accept-line");
km.bind("^M", "accept-line");
km
}
pub fn bind(&mut self, keys: &str, widget: &str) {
let normalized = Self::normalize_keys(keys);
self.bindings.insert(normalized, widget.to_string());
}
pub fn unbind(&mut self, keys: &str) {
let normalized = Self::normalize_keys(keys);
self.bindings.remove(&normalized);
}
pub fn lookup(&self, keys: &str) -> Option<&str> {
let normalized = Self::normalize_keys(keys);
self.bindings.get(&normalized).map(|s| s.as_str())
}
pub fn has_prefix(&self, keys: &str) -> bool {
let normalized = Self::normalize_keys(keys);
self.bindings
.keys()
.any(|k| k.starts_with(&normalized) && k != &normalized)
}
pub fn list_bindings(&self) -> impl Iterator<Item = (&str, &str)> {
self.bindings.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
fn normalize_keys(keys: &str) -> String {
let mut result = String::new();
let mut chars = keys.chars().peekable();
while let Some(c) = chars.next() {
match c {
'^' => {
if let Some(&next) = chars.peek() {
chars.next();
let ctrl_char = if next == '?' {
'\x7f' } else if next == '@' {
'\x00' } else if next == '[' {
'\x1b' } else {
((next.to_ascii_uppercase() as u8) & 0x1f) as char
};
result.push(ctrl_char);
} else {
result.push(c);
}
}
'\\' => {
if let Some(&next) = chars.peek() {
match next {
'e' | 'E' => {
chars.next();
result.push('\x1b'); }
'C' => {
chars.next();
if chars.peek() == Some(&'-') {
chars.next();
if let Some(&ctrl_char) = chars.peek() {
chars.next();
let ctrl =
((ctrl_char.to_ascii_uppercase() as u8) & 0x1f) as char;
result.push(ctrl);
}
}
}
'M' => {
chars.next();
if chars.peek() == Some(&'-') {
chars.next();
result.push('\x1b'); if let Some(&meta_char) = chars.peek() {
chars.next();
result.push(meta_char);
}
}
}
'n' => {
chars.next();
result.push('\n');
}
't' => {
chars.next();
result.push('\t');
}
'r' => {
chars.next();
result.push('\r');
}
'\\' => {
chars.next();
result.push('\\');
}
_ => {
result.push(c);
}
}
} else {
result.push(c);
}
}
_ => result.push(c),
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_keys() {
assert_eq!(Keymap::normalize_keys("^A"), "\x01");
assert_eq!(Keymap::normalize_keys("^?"), "\x7f");
assert_eq!(Keymap::normalize_keys("\\ef"), "\x1bf");
assert_eq!(Keymap::normalize_keys("\\C-a"), "\x01");
assert_eq!(Keymap::normalize_keys("\\M-x"), "\x1bx");
}
#[test]
fn test_keymap_bind_lookup() {
let mut km = Keymap::new();
km.bind("^A", "beginning-of-line");
assert_eq!(km.lookup("^A"), Some("beginning-of-line"));
assert_eq!(km.lookup("\x01"), Some("beginning-of-line"));
}
#[test]
fn test_has_prefix() {
let mut km = Keymap::new();
km.bind("^X^U", "undo");
assert!(km.has_prefix("^X"));
assert!(!km.has_prefix("^X^U"));
assert!(!km.has_prefix("^A"));
}
}