use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use std::time::{Duration, Instant};
use super::keymap::{Keymap, KeymapManager};
use super::thingy::Thingy;
use super::widget::{Widget, WidgetFlags};
pub type ZleChar = char;
pub type ZleString = Vec<ZleChar>;
pub type ZleInt = i32;
pub const ZLEEOF: ZleInt = -1;
#[derive(Debug, Clone, Copy, Default)]
pub struct ZleReadFlags {
pub no_history: bool,
pub completion: bool,
pub vared: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ZleContext {
#[default]
Line,
Cont,
Select,
Vared,
}
#[derive(Debug, Clone, Default)]
pub struct Modifier {
pub flags: ModifierFlags,
pub mult: i32,
pub tmult: i32,
pub vibuf: i32,
pub base: i32,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct ModifierFlags: u32 {
const MULT = 1 << 0;
const TMULT = 1 << 1;
const VIBUF = 1 << 2;
const VIAPP = 1 << 3;
const NEG = 1 << 4;
const NULL = 1 << 5;
const CHAR = 1 << 6;
const LINE = 1 << 7;
const PRI = 1 << 8;
const CLIP = 1 << 9;
}
}
#[derive(Debug, Clone)]
pub struct Change {
pub flags: ChangeFlags,
pub hist: i32,
pub off: usize,
pub del: ZleString,
pub ins: ZleString,
pub old_cs: usize,
pub new_cs: usize,
pub changeno: u64,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct ChangeFlags: u32 {
const NEXT = 1 << 0;
const PREV = 1 << 1;
}
}
#[derive(Debug, Clone)]
pub struct WatchFd {
pub fd: RawFd,
pub func: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeoutType {
None,
Key,
Func,
Max,
}
#[derive(Debug, Clone)]
pub struct Timeout {
pub tp: TimeoutType,
pub exp100ths: u64,
}
pub const ZMAXTIMEOUT: u64 = 1 << 21;
pub struct Zle {
pub zleline: ZleString,
pub zlecs: usize,
pub zlell: usize,
pub mark: usize,
pub insmode: bool,
pub done: bool,
pub lastchar: ZleInt,
pub lastchar_wide: ZleInt,
pub lastchar_wide_valid: bool,
pub lbindk: Option<Thingy>,
pub bindk: Option<Thingy>,
pub lastcmd: WidgetFlags,
pub zmod: Modifier,
pub prefixflag: bool,
pub zle_recursive: i32,
pub zlereadflags: ZleReadFlags,
pub zlecontext: ZleContext,
pub statusline: Option<String>,
pub stackhist: i32,
pub stackcs: usize,
pub vistartchange: u64,
pub undo_stack: Vec<Change>,
pub changeno: u64,
unget_buf: VecDeque<u8>,
eofchar: u8,
eofsent: bool,
pub keytimeout: u64,
baud: u32,
pub watch_fds: Vec<WatchFd>,
pub keymaps: KeymapManager,
pub compwidget: Option<Widget>,
pub incompctlfunc: bool,
pub hascompmod: bool,
ttyfd: RawFd,
lprompt: String,
rprompt: String,
pre_zle_status: i32,
pub resetneeded: bool,
pub vibuf: [ZleString; 36],
pub killring: VecDeque<ZleString>,
pub killringmax: usize,
pub yanklast: bool,
pub neg_arg: bool,
pub mult: i32,
}
impl Default for Zle {
fn default() -> Self {
Self::new()
}
}
impl Zle {
pub fn new() -> Self {
Zle {
zleline: Vec::new(),
zlecs: 0,
zlell: 0,
mark: 0,
insmode: true,
done: false,
lastchar: 0,
lastchar_wide: 0,
lastchar_wide_valid: false,
lbindk: None,
bindk: None,
lastcmd: WidgetFlags::empty(),
zmod: Modifier::default(),
prefixflag: false,
zle_recursive: 0,
zlereadflags: ZleReadFlags::default(),
zlecontext: ZleContext::default(),
statusline: None,
stackhist: 0,
stackcs: 0,
vistartchange: 0,
undo_stack: Vec::new(),
changeno: 0,
unget_buf: VecDeque::new(),
eofchar: 4, eofsent: false,
keytimeout: 40, baud: 38400,
watch_fds: Vec::new(),
keymaps: KeymapManager::new(),
compwidget: None,
incompctlfunc: false,
hascompmod: false,
ttyfd: 0, lprompt: String::new(),
rprompt: String::new(),
pre_zle_status: 0,
resetneeded: false,
vibuf: std::array::from_fn(|_| Vec::new()),
killring: VecDeque::new(),
killringmax: 8,
yanklast: false,
neg_arg: false,
mult: 1,
}
}
pub fn zsetterm(&mut self) -> io::Result<()> {
use std::os::unix::io::FromRawFd;
let mut termios = termios::Termios::from_fd(self.ttyfd)?;
termios.c_lflag &= !(termios::ICANON | termios::ECHO);
termios.c_cc[termios::VMIN] = 1;
termios.c_cc[termios::VTIME] = 0;
termios::tcsetattr(self.ttyfd, termios::TCSANOW, &termios)?;
Ok(())
}
pub fn ungetbyte(&mut self, ch: u8) {
self.unget_buf.push_front(ch);
}
pub fn ungetbytes(&mut self, s: &[u8]) {
for &b in s.iter().rev() {
self.unget_buf.push_front(b);
}
}
fn calc_timeout(&self, do_keytmout: bool) -> Timeout {
if do_keytmout && self.keytimeout > 0 {
let exp = if self.keytimeout > ZMAXTIMEOUT * 100 {
ZMAXTIMEOUT * 100
} else {
self.keytimeout
};
Timeout {
tp: TimeoutType::Key,
exp100ths: exp,
}
} else {
Timeout {
tp: TimeoutType::None,
exp100ths: 0,
}
}
}
pub fn raw_getbyte(&mut self, do_keytmout: bool) -> Option<u8> {
if let Some(b) = self.unget_buf.pop_front() {
return Some(b);
}
let timeout = self.calc_timeout(do_keytmout);
let timeout_duration = if timeout.tp != TimeoutType::None {
Some(Duration::from_millis(timeout.exp100ths * 10))
} else {
None
};
let mut buf = [0u8; 1];
if let Some(dur) = timeout_duration {
let start = Instant::now();
loop {
if start.elapsed() >= dur {
return None; }
match self.try_read_byte(&mut buf) {
Ok(true) => return Some(buf[0]),
Ok(false) => {
std::thread::sleep(Duration::from_millis(10));
}
Err(_) => return None,
}
}
} else {
match io::stdin().read(&mut buf) {
Ok(1) => Some(buf[0]),
_ => None,
}
}
}
fn try_read_byte(&self, buf: &mut [u8]) -> io::Result<bool> {
use std::os::unix::io::AsRawFd;
let mut fds = [libc::pollfd {
fd: io::stdin().as_raw_fd(),
events: libc::POLLIN,
revents: 0,
}];
let ret = unsafe { libc::poll(fds.as_mut_ptr(), 1, 0) };
if ret > 0 && (fds[0].revents & libc::POLLIN) != 0 {
match io::stdin().read(buf) {
Ok(1) => Ok(true),
Ok(_) => Ok(false),
Err(e) => Err(e),
}
} else {
Ok(false)
}
}
pub fn getbyte(&mut self, do_keytmout: bool) -> Option<u8> {
let b = self.raw_getbyte(do_keytmout)?;
let b = if b == b'\n' {
b'\r'
} else if b == b'\r' {
b'\n'
} else {
b
};
self.lastchar = b as ZleInt;
Some(b)
}
pub fn getfullchar(&mut self, do_keytmout: bool) -> Option<char> {
let b = self.getbyte(do_keytmout)?;
if b < 0x80 {
let c = b as char;
self.lastchar_wide = c as ZleInt;
self.lastchar_wide_valid = true;
return Some(c);
}
let mut bytes = vec![b];
let expected_len = if b < 0xE0 {
2
} else if b < 0xF0 {
3
} else {
4
};
while bytes.len() < expected_len {
if let Some(next) = self.getbyte(true) {
if (next & 0xC0) != 0x80 {
self.ungetbyte(next);
break;
}
bytes.push(next);
} else {
break;
}
}
match std::str::from_utf8(&bytes) {
Ok(s) => {
if let Some(c) = s.chars().next() {
self.lastchar_wide = c as ZleInt;
self.lastchar_wide_valid = true;
return Some(c);
}
}
Err(_) => {}
}
self.lastchar_wide_valid = false;
None
}
pub fn redrawhook(&mut self) {
}
pub fn zlecore(&mut self) {
self.done = false;
while !self.done {
if !self.prefixflag {
self.zmod = Modifier::default();
}
self.prefixflag = false;
let c = match self.getfullchar(false) {
Some(c) => c,
None => {
self.done = true;
continue;
}
};
let key = c;
if let Some(thingy) = self.keymaps.lookup_key(key) {
self.lbindk = self.bindk.take();
self.bindk = Some(thingy.clone());
if let Some(widget) = &thingy.widget {
self.execute_widget(widget);
}
} else {
self.do_self_insert(key);
}
if self.resetneeded {
self.zrefresh();
self.resetneeded = false;
}
}
}
fn execute_widget(&mut self, widget: &Widget) {
self.lastcmd = widget.flags;
match &widget.func {
super::widget::WidgetFunc::Internal(f) => {
f(self);
}
super::widget::WidgetFunc::User(name) => {
let _ = name;
}
}
}
fn do_self_insert(&mut self, c: char) {
if self.insmode {
self.zleline.insert(self.zlecs, c);
self.zlecs += 1;
self.zlell += 1;
} else {
if self.zlecs < self.zlell {
self.zleline[self.zlecs] = c;
} else {
self.zleline.push(c);
self.zlell += 1;
}
self.zlecs += 1;
}
self.resetneeded = true;
}
pub fn zleread(
&mut self,
lprompt: &str,
rprompt: &str,
flags: ZleReadFlags,
context: ZleContext,
) -> io::Result<String> {
self.lprompt = lprompt.to_string();
self.rprompt = rprompt.to_string();
self.zlereadflags = flags;
self.zlecontext = context;
self.zleline.clear();
self.zlecs = 0;
self.zlell = 0;
self.mark = 0;
self.done = false;
self.zsetterm()?;
print!("{}", lprompt);
io::stdout().flush()?;
self.zlecore();
Ok(self.zleline.iter().collect())
}
pub fn initmodifier(&mut self) {
self.zmod = Modifier {
flags: ModifierFlags::empty(),
mult: 1,
tmult: 0,
vibuf: -1,
base: 10,
};
}
pub fn handleprefixes(&mut self) {
if self.zmod.flags.contains(ModifierFlags::TMULT) {
self.zmod.flags.remove(ModifierFlags::TMULT);
self.zmod.flags.insert(ModifierFlags::MULT);
self.zmod.mult = self.zmod.tmult;
}
}
pub fn trashzle(&mut self) {
print!("\r\x1b[K");
io::stdout().flush().ok();
}
pub fn resetprompt(&mut self) {
self.resetneeded = true;
}
pub fn reexpandprompt(&mut self) {
self.resetneeded = true;
}
pub fn recursive_edit(&mut self) -> i32 {
self.zle_recursive += 1;
let old_done = self.done;
self.done = false;
self.zlecore();
self.done = old_done;
self.zle_recursive -= 1;
0
}
pub fn finish_line(&mut self) {
self.done = true;
}
pub fn abort_line(&mut self) {
self.zleline.clear();
self.zlecs = 0;
self.zlell = 0;
self.done = true;
}
}
impl Zle {
pub fn save_keymap(&mut self) -> SavedKeymap {
SavedKeymap {
name: self.keymaps.current_name.clone(),
local: self.keymaps.local.clone(),
}
}
pub fn restore_keymap(&mut self, saved: SavedKeymap) {
self.keymaps.select(&saved.name);
self.keymaps.local = saved.local;
}
pub fn describe_key_briefly(&mut self) {
if let Some(c) = self.getfullchar(false) {
if let Some(thingy) = self.keymaps.lookup_key(c) {
self.display_msg(&format!("{} is bound to {}", c, thingy.name));
} else {
self.display_msg(&format!("{} is not bound", c));
}
}
}
pub fn whereis(&self, widget_name: &str) -> Vec<String> {
let mut bindings = Vec::new();
for (name, km) in &self.keymaps.keymaps {
for (i, opt) in km.first.iter().enumerate() {
if let Some(t) = opt {
if t.name == widget_name {
bindings.push(format!("{}:{}", name, super::utils::print_bind(&[i as u8])));
}
}
}
for (seq, kb) in &km.multi {
if let Some(ref t) = kb.bind {
if t.name == widget_name {
bindings.push(format!("{}:{}", name, super::utils::print_bind(seq)));
}
}
}
}
bindings
}
pub fn exec_immortal(&mut self, name: &str) -> bool {
if let Some(widget) = get_builtin_widget(name) {
self.execute_widget(&widget);
true
} else {
false
}
}
pub fn exec_zle_func(&mut self, name: &str, _args: &[String]) -> i32 {
if let Some(widget) = get_builtin_widget(name) {
self.execute_widget(&widget);
0
} else {
1
}
}
pub fn break_read(&mut self) {
self.done = true;
}
pub fn before_trap(&mut self) {
}
pub fn after_trap(&mut self) {
self.resetneeded = true;
}
pub fn zle_reset_prompt(&mut self) {
self.resetneeded = true;
}
fn display_msg(&self, msg: &str) {
eprintln!("{}", msg);
}
pub fn prompt(&self) -> &str {
&self.lprompt
}
pub fn set_prompt(&mut self, prompt: &str) {
self.lprompt = prompt.to_string();
self.resetneeded = true;
}
pub fn get_mult(&self) -> i32 {
if self.zmod.flags.contains(ModifierFlags::MULT) {
self.zmod.mult
} else {
1
}
}
pub fn toggle_neg_arg(&mut self) {
self.zmod.flags.toggle(ModifierFlags::NEG);
}
pub fn is_neg(&self) -> bool {
self.zmod.flags.contains(ModifierFlags::NEG)
}
pub fn is_vicmd(&self) -> bool {
self.keymaps.is_vi_cmd()
}
pub fn is_viins(&self) -> bool {
self.keymaps.is_vi_insert()
}
pub fn is_emacs(&self) -> bool {
self.keymaps.is_emacs()
}
pub fn was_yank(&self) -> bool {
self.lastcmd.contains(WidgetFlags::YANK)
}
}
#[derive(Debug, Clone)]
pub struct SavedKeymap {
pub name: String,
pub local: Option<std::sync::Arc<Keymap>>,
}
fn get_builtin_widget(name: &str) -> Option<Widget> {
Some(Widget::builtin(name))
}
pub fn bin_vared(zle: &mut Zle, varname: &str, opts: VaredOpts) -> io::Result<String> {
let initial = std::env::var(varname).unwrap_or_default();
zle.zleline = initial.chars().collect();
zle.zlell = zle.zleline.len();
zle.zlecs = if opts.cursor_at_end { zle.zlell } else { 0 };
let prompt = opts.prompt.as_deref().unwrap_or("");
let rprompt = opts.rprompt.as_deref().unwrap_or("");
let result = zle.zleread(
prompt,
rprompt,
ZleReadFlags {
vared: true,
..Default::default()
},
ZleContext::Vared,
)?;
Ok(result)
}
#[derive(Debug, Default)]
pub struct VaredOpts {
pub prompt: Option<String>,
pub rprompt: Option<String>,
pub cursor_at_end: bool,
pub history: bool,
}
pub fn zle_main_entry(op: ZleOperation, data: ZleData) -> i32 {
match op {
ZleOperation::Read => {
0
}
ZleOperation::Refresh => {
0
}
ZleOperation::Invalidate => {
0
}
ZleOperation::Reset => {
0
}
_ => 1,
}
}
#[derive(Debug, Clone, Copy)]
pub enum ZleOperation {
Read,
Refresh,
Invalidate,
Reset,
SetKeymap,
}
#[derive(Debug, Default)]
pub struct ZleData {
pub prompt: Option<String>,
pub keymap: Option<String>,
}
mod termios {
pub use libc::{ECHO, ICANON, TCSANOW, VMIN, VTIME};
use std::io;
use std::os::unix::io::RawFd;
#[derive(Clone)]
pub struct Termios {
inner: libc::termios,
}
impl Termios {
pub fn from_fd(fd: RawFd) -> io::Result<Self> {
let mut termios = std::mem::MaybeUninit::uninit();
let ret = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
Ok(Termios {
inner: unsafe { termios.assume_init() },
})
}
}
impl std::ops::Deref for Termios {
type Target = libc::termios;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for Termios {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub fn tcsetattr(fd: RawFd, action: i32, termios: &Termios) -> io::Result<()> {
let ret = unsafe { libc::tcsetattr(fd, action, &termios.inner) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}