use super::main::{Zle, ZleChar, ZleString};
impl Zle {
pub fn insert_str(&mut self, s: &str) {
for c in s.chars() {
self.zleline.insert(self.zlecs, c);
self.zlecs += 1;
self.zlell += 1;
}
self.resetneeded = true;
}
pub fn insert_chars(&mut self, chars: &[ZleChar]) {
for &c in chars {
self.zleline.insert(self.zlecs, c);
self.zlecs += 1;
self.zlell += 1;
}
self.resetneeded = true;
}
pub fn delete_chars(&mut self, n: usize) {
let n = n.min(self.zlell - self.zlecs);
for _ in 0..n {
if self.zlecs < self.zlell {
self.zleline.remove(self.zlecs);
self.zlell -= 1;
}
}
self.resetneeded = true;
}
pub fn backspace_chars(&mut self, n: usize) {
let n = n.min(self.zlecs);
for _ in 0..n {
if self.zlecs > 0 {
self.zlecs -= 1;
self.zleline.remove(self.zlecs);
self.zlell -= 1;
}
}
self.resetneeded = true;
}
pub fn get_line(&self) -> String {
self.zleline.iter().collect()
}
pub fn set_line_keep_cursor(&mut self, s: &str) {
self.zleline = s.chars().collect();
self.zlell = self.zleline.len();
self.zlecs = self.zlecs.min(self.zlell);
self.resetneeded = true;
}
pub fn clear_line(&mut self) {
self.zleline.clear();
self.zlell = 0;
self.zlecs = 0;
self.mark = 0;
self.resetneeded = true;
}
pub fn get_region(&self) -> &[ZleChar] {
let (start, end) = if self.zlecs < self.mark {
(self.zlecs, self.mark)
} else {
(self.mark, self.zlecs)
};
&self.zleline[start..end]
}
pub fn cut_to_buffer(&mut self, buf: usize, append: bool) {
if buf < self.vibuf.len() {
let (start, end) = if self.zlecs < self.mark {
(self.zlecs, self.mark)
} else {
(self.mark, self.zlecs)
};
let text: ZleString = self.zleline[start..end].to_vec();
if append {
self.vibuf[buf].extend(text);
} else {
self.vibuf[buf] = text;
}
}
}
pub fn paste_from_buffer(&mut self, buf: usize, after: bool) {
if buf < self.vibuf.len() {
let text = self.vibuf[buf].clone();
if !text.is_empty() {
if after && self.zlecs < self.zlell {
self.zlecs += 1;
}
self.insert_chars(&text);
}
}
}
}
pub fn metafy(s: &str) -> String {
s.to_string()
}
pub fn unmetafy(s: &str) -> String {
s.to_string()
}
pub fn strwidth(s: &str) -> usize {
use unicode_width::UnicodeWidthStr;
s.width()
}
pub fn is_printable(c: char) -> bool {
!c.is_control() && c != '\x7f'
}
pub fn escape_for_display(c: char) -> String {
if c.is_control() {
if c as u32 <= 26 {
format!("^{}", (c as u8 + b'@') as char)
} else {
format!("\\x{:02x}", c as u32)
}
} else if c == '\x7f' {
"^?".to_string()
} else {
c.to_string()
}
}
#[derive(Debug, Clone)]
pub struct UndoEntry {
pub start: usize,
pub end: usize,
pub text: ZleString,
pub cursor: usize,
pub group_start: bool,
}
#[derive(Debug, Default)]
pub struct UndoState {
pub history: Vec<UndoEntry>,
pub current: usize,
pub limit: usize,
pub recording: bool,
pub merge_inserts: bool,
}
impl UndoState {
pub fn new() -> Self {
UndoState {
recording: true,
merge_inserts: true,
..Default::default()
}
}
pub fn init(&mut self) {
self.history.clear();
self.current = 0;
self.limit = 0;
self.recording = true;
}
pub fn free(&mut self) {
self.history.clear();
self.current = 0;
}
pub fn make_entry(&mut self, start: usize, end: usize, text: ZleString, cursor: usize) {
if !self.recording {
return;
}
self.history.truncate(self.current);
let entry = UndoEntry {
start,
end,
text,
cursor,
group_start: false,
};
self.history.push(entry);
self.current = self.history.len();
}
pub fn split(&mut self) {
if let Some(entry) = self.history.last_mut() {
entry.group_start = true;
}
}
pub fn merge(&mut self) {
if self.history.len() >= 2 {
let last = self.history.len() - 1;
let prev = last - 1;
if self.history[prev].end == self.history[last].start
&& self.history[prev].text.is_empty()
&& self.history[last].text.is_empty()
{
self.history[prev].end = self.history[last].end;
self.history.pop();
self.current = self.history.len();
}
}
}
pub fn get_current(&self) -> Option<&UndoEntry> {
if self.current > 0 {
self.history.get(self.current - 1)
} else {
None
}
}
pub fn set_limit(&mut self) {
self.limit = self.current;
}
pub fn get_limit(&self) -> usize {
self.limit
}
}
impl Zle {
#[allow(dead_code)]
fn apply_undo_entry(&mut self, entry: &UndoEntry, reverse: bool) {
if reverse {
let removed: ZleString = self.zleline.drain(entry.start..entry.end).collect();
for (i, &c) in entry.text.iter().enumerate() {
self.zleline.insert(entry.start + i, c);
}
self.zlell = self.zleline.len();
self.zlecs = entry.cursor;
let _ = removed;
} else {
let end = entry.start + entry.text.len();
self.zleline.drain(entry.start..end.min(self.zleline.len()));
for (i, &c) in entry.text.iter().enumerate() {
self.zleline.insert(entry.start + i, c);
}
self.zlell = self.zleline.len();
self.zlecs = entry.cursor;
}
self.resetneeded = true;
}
pub fn find_bol(&self, pos: usize) -> usize {
let mut p = pos;
while p > 0 && self.zleline.get(p - 1) != Some(&'\n') {
p -= 1;
}
p
}
pub fn find_eol(&self, pos: usize) -> usize {
let mut p = pos;
while p < self.zlell && self.zleline.get(p) != Some(&'\n') {
p += 1;
}
p
}
pub fn find_line(&self, pos: usize) -> usize {
self.zleline[..pos].iter().filter(|&&c| c == '\n').count()
}
pub fn size_line(&mut self, needed: usize) {
if self.zleline.capacity() < needed {
self.zleline.reserve(needed - self.zleline.len());
}
}
pub fn space_in_line(&mut self, pos: usize, count: usize) {
for _ in 0..count {
self.zleline.insert(pos, ' ');
}
self.zlell += count;
if self.zlecs >= pos {
self.zlecs += count;
}
}
pub fn shift_chars(&mut self, from: usize, count: i32) {
if count > 0 {
for _ in 0..count {
self.zleline.insert(from, ' ');
}
self.zlell += count as usize;
} else if count < 0 {
let to_remove = (-count) as usize;
for _ in 0..to_remove.min(self.zlell - from) {
self.zleline.remove(from);
}
self.zlell = self.zleline.len();
}
}
pub fn fore_del(&mut self, count: usize, flags: CutFlags) {
let count = count.min(self.zlell - self.zlecs);
if count == 0 {
return;
}
if flags.contains(CutFlags::KILL) {
let text: ZleString = self.zleline[self.zlecs..self.zlecs + count].to_vec();
self.killring.push_front(text);
if self.killring.len() > self.killringmax {
self.killring.pop_back();
}
}
for _ in 0..count {
self.zleline.remove(self.zlecs);
}
self.zlell -= count;
self.resetneeded = true;
}
pub fn back_del(&mut self, count: usize, flags: CutFlags) {
let count = count.min(self.zlecs);
if count == 0 {
return;
}
if flags.contains(CutFlags::KILL) {
let text: ZleString = self.zleline[self.zlecs - count..self.zlecs].to_vec();
self.killring.push_front(text);
if self.killring.len() > self.killringmax {
self.killring.pop_back();
}
}
self.zlecs -= count;
for _ in 0..count {
self.zleline.remove(self.zlecs);
}
self.zlell -= count;
self.resetneeded = true;
}
pub fn fore_kill(&mut self, count: usize, append: bool) {
let count = count.min(self.zlell - self.zlecs);
if count == 0 {
return;
}
let text: ZleString = self.zleline[self.zlecs..self.zlecs + count].to_vec();
if append {
if let Some(front) = self.killring.front_mut() {
front.extend(text);
} else {
self.killring.push_front(text);
}
} else {
self.killring.push_front(text);
}
if self.killring.len() > self.killringmax {
self.killring.pop_back();
}
for _ in 0..count {
self.zleline.remove(self.zlecs);
}
self.zlell -= count;
self.resetneeded = true;
}
pub fn back_kill(&mut self, count: usize, append: bool) {
let count = count.min(self.zlecs);
if count == 0 {
return;
}
let text: ZleString = self.zleline[self.zlecs - count..self.zlecs].to_vec();
if append {
if let Some(front) = self.killring.front_mut() {
let mut new_text = text;
new_text.extend(front.iter());
*front = new_text;
} else {
self.killring.push_front(text);
}
} else {
self.killring.push_front(text);
}
if self.killring.len() > self.killringmax {
self.killring.pop_back();
}
self.zlecs -= count;
for _ in 0..count {
self.zleline.remove(self.zlecs);
}
self.zlell -= count;
self.resetneeded = true;
}
pub fn cut_text(&mut self, start: usize, end: usize, dir: CutDirection) {
if start >= end || end > self.zlell {
return;
}
let text: ZleString = self.zleline[start..end].to_vec();
match dir {
CutDirection::Front => {
self.killring.push_front(text);
}
CutDirection::Back => {
if let Some(front) = self.killring.front_mut() {
front.extend(text);
} else {
self.killring.push_front(text);
}
}
}
if self.killring.len() > self.killringmax {
self.killring.pop_back();
}
}
pub fn set_last_line(&mut self) {
self.setlastline();
}
pub fn show_msg(&self, msg: &str) {
eprintln!("{}", msg);
}
pub fn handle_feep(&self) {
print!("\x07"); }
pub fn add_to_line(&mut self, pos: usize, text: &str) {
for (i, c) in text.chars().enumerate() {
self.zleline.insert(pos + i, c);
}
self.zlell += text.chars().count();
if self.zlecs >= pos {
self.zlecs += text.chars().count();
}
self.resetneeded = true;
}
pub fn line_as_string(&self) -> String {
self.zleline.iter().collect()
}
pub fn string_as_line(&mut self, s: &str) {
self.zleline = s.chars().collect();
self.zlell = self.zleline.len();
if self.zlecs > self.zlell {
self.zlecs = self.zlell;
}
self.resetneeded = true;
}
pub fn get_zle_line(&self) -> &[ZleChar] {
&self.zleline
}
pub fn get_zle_query(&mut self) -> bool {
let c = match self.getfullchar(false) {
Some(c) => c,
None => return false, };
let resolved = if c == '\t' {
'y'
} else if c.is_control() {
'n'
} else {
c.to_ascii_lowercase()
};
if resolved != '\n' {
print!("{}", resolved);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
resolved == 'y'
}
pub fn handle_suffix(&mut self) {
self.call_hook("handle-suffix", None);
}
pub fn set_line(&mut self, s: &str) {
self.zleline = s.chars().collect();
self.zlell = self.zleline.len();
self.zlecs = self.zlell;
self.resetneeded = true;
}
}
#[derive(Debug, Clone)]
pub struct SavedPositions {
pub zlecs: usize,
pub zlell: usize,
pub mark: usize,
}
impl Zle {
pub fn save_positions(&self) -> SavedPositions {
SavedPositions {
zlecs: self.zlecs,
zlell: self.zlell,
mark: self.mark,
}
}
pub fn restore_positions(&mut self, saved: &SavedPositions) {
self.zlecs = saved.zlecs.min(self.zlell);
self.mark = saved.mark.min(self.zlell);
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct CutFlags: u32 {
const KILL = 1 << 0; const COPY = 1 << 1; const APPEND = 1 << 2; }
}
#[derive(Debug, Clone, Copy)]
pub enum CutDirection {
Front,
Back,
}
pub fn bind_ztrdup(seq: &[u8]) -> String {
let mut buf = String::new();
for &b in seq {
let mut c = b;
if c & 0x80 != 0 {
buf.push('\\');
buf.push('M');
buf.push('-');
c &= 0x7f;
}
if c < 32 || c == 0x7f {
buf.push('^');
c ^= 64;
}
if c == b'\\' || c == b'^' {
buf.push('\\');
}
buf.push(c as char);
}
buf
}
pub fn print_bind(seq: &[u8]) -> String {
let mut result = String::new();
for &b in seq {
match b {
0x1b => result.push_str("^["),
0..=31 => {
result.push('^');
result.push((b + 64) as char);
}
127 => result.push_str("^?"),
128..=159 => {
result.push_str("^[^");
result.push((b - 64) as char);
}
_ => result.push(b as char),
}
}
result
}
pub fn zle_call_hook(_name: &str, _args: &[&str]) -> i32 {
0
}
impl Zle {
pub fn call_hook(&mut self, name: &str, arg: Option<&str>) {
self.pending_hooks
.push((name.to_string(), arg.map(|s| s.to_string())));
}
pub fn drain_hooks(&mut self) -> Vec<(String, Option<String>)> {
std::mem::take(&mut self.pending_hooks)
}
}
#[cfg(test)]
mod tests_hooks {
use super::Zle;
#[test]
fn call_hook_queues_for_host_dispatch() {
let mut zle = Zle::new();
zle.call_hook("zle-line-init", None);
zle.call_hook("zle-keymap-select", Some("vicmd"));
let drained = zle.drain_hooks();
assert_eq!(drained.len(), 2);
assert_eq!(drained[0], ("zle-line-init".to_string(), None));
assert_eq!(
drained[1],
("zle-keymap-select".to_string(), Some("vicmd".to_string()))
);
assert!(zle.drain_hooks().is_empty());
}
#[test]
fn redrawhook_queues_pre_redraw_hook() {
let mut zle = Zle::new();
zle.redrawhook();
let drained = zle.drain_hooks();
assert_eq!(drained, vec![("zle-line-pre-redraw".to_string(), None)]);
}
#[test]
fn reexpandprompt_re_runs_expansion_against_raw_templates() {
let mut zle = Zle::new();
zle.lprompt_raw = "%% > ".to_string();
zle.rprompt_raw = "[%%]".to_string();
zle.reexpandprompt();
assert_eq!(zle.prompt(), "% > ");
assert_eq!(zle.rprompt(), "[%]");
}
}
#[cfg(test)]
mod tests_strwidth {
use super::strwidth;
#[test]
fn strwidth_ascii_is_one_per_char() {
assert_eq!(strwidth("hello"), 5);
}
#[test]
fn strwidth_counts_east_asian_wide_as_two() {
assert_eq!(strwidth("漢"), 2);
assert_eq!(strwidth("a漢"), 3);
}
#[test]
fn strwidth_zero_width_combining_mark_does_not_widen() {
assert_eq!(strwidth("e\u{0301}"), 1);
}
#[test]
fn strwidth_emoji_presentation_is_wide() {
assert_eq!(strwidth("🎉"), 2);
}
}
#[cfg(test)]
mod tests_bindkey_format {
use super::bind_ztrdup;
use super::print_bind;
#[test]
fn bind_ztrdup_emits_caret_form_for_control_chars() {
assert_eq!(bind_ztrdup(b"\x01"), "^A");
assert_eq!(bind_ztrdup(b"\x1f"), "^_");
assert_eq!(bind_ztrdup(b"\x7f"), "^?");
}
#[test]
fn bind_ztrdup_escapes_backslash_and_caret() {
assert_eq!(bind_ztrdup(b"\\"), "\\\\");
assert_eq!(bind_ztrdup(b"^"), "\\^");
}
#[test]
fn bind_ztrdup_handles_high_bit_as_meta() {
assert_eq!(bind_ztrdup(b"\xC1"), "\\M-A");
}
#[test]
fn print_bind_caret_form_matches_describe_key_output() {
assert_eq!(print_bind(b"\x01"), "^A");
assert_eq!(print_bind(b"\x1b"), "^[");
}
}
impl Zle {
pub fn setlastline(&mut self) {
self.last_line.clear();
self.last_line.extend_from_slice(&self.zleline);
self.last_ll = self.zlell;
self.last_cs = self.zlecs;
}
pub fn mkundoent(&mut self) {
if self.last_ll == self.zlell && self.last_line[..self.last_ll] == self.zleline[..self.zlell]
{
self.last_cs = self.zlecs;
return;
}
let sh = self.last_ll.min(self.zlell);
let mut pre = 0usize;
while pre < sh && self.zleline[pre] == self.last_line[pre] {
pre += 1;
}
let mut suf = 0usize;
while suf < sh - pre
&& self.zleline[self.zlell - 1 - suf] == self.last_line[self.last_ll - 1 - suf]
{
suf += 1;
}
let del: ZleString = if suf + pre == self.last_ll {
Vec::new()
} else {
self.last_line[pre..self.last_ll - suf].to_vec()
};
let ins: ZleString = if suf + pre == self.zlell {
Vec::new()
} else {
self.zleline[pre..self.zlell - suf].to_vec()
};
self.undo_changeno += 1;
let ch = super::main::Change {
flags: super::main::ChangeFlags::empty(),
hist: self.history.cursor as i32,
off: pre,
del,
ins,
old_cs: self.last_cs,
new_cs: self.zlecs,
changeno: self.undo_changeno,
};
self.undo_stack.truncate(self.cur_change);
self.undo_stack.push(ch);
self.cur_change = self.undo_stack.len();
}
pub fn handleundo(&mut self) {
self.setlastline();
}
pub fn unapply_change(&mut self, idx: usize) -> bool {
if idx >= self.undo_stack.len() {
return false;
}
let (off, dell, insl, old_cs);
let del_vec;
let ins_len;
{
let ch = &self.undo_stack[idx];
off = ch.off;
dell = ch.del.len();
insl = ch.ins.len();
ins_len = ch.ins.len();
old_cs = ch.old_cs;
del_vec = ch.del.clone();
}
let _ = ins_len;
self.zlecs = off;
if insl > 0 {
self.zleline.drain(off..off + insl);
}
if dell > 0 {
for (i, c) in del_vec.into_iter().enumerate() {
self.zleline.insert(off + i, c);
}
}
self.zlell = self.zleline.len();
self.zlecs = old_cs.min(self.zlell);
self.resetneeded = true;
true
}
pub fn apply_change(&mut self, idx: usize) -> bool {
if idx >= self.undo_stack.len() {
return false;
}
let (off, dell, insl, new_cs);
let ins_vec;
{
let ch = &self.undo_stack[idx];
off = ch.off;
dell = ch.del.len();
insl = ch.ins.len();
new_cs = ch.new_cs;
ins_vec = ch.ins.clone();
}
self.zlecs = off;
if dell > 0 {
self.zleline.drain(off..off + dell);
}
if insl > 0 {
for (i, c) in ins_vec.into_iter().enumerate() {
self.zleline.insert(off + i, c);
}
}
self.zlell = self.zleline.len();
self.zlecs = new_cs.min(self.zlell);
self.resetneeded = true;
true
}
pub fn undo_widget(&mut self) -> i32 {
self.mkundoent();
if self.cur_change == 0 {
return 1;
}
let prev_idx = self.cur_change - 1;
if self.undo_stack[prev_idx].changeno <= self.undo_limitno {
return 1;
}
if self.unapply_change(prev_idx) {
self.cur_change = prev_idx;
}
self.setlastline();
0
}
pub fn redo_widget(&mut self) -> i32 {
self.mkundoent();
if self.cur_change >= self.undo_stack.len() {
return 1;
}
if self.apply_change(self.cur_change) {
self.cur_change += 1;
}
self.setlastline();
0
}
}