use crate::extensions::zsh_ast::{ZshCommand, ZshPipe, ZshProgram, ZshSublist};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::sync::Mutex;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[archive(check_bytes)]
pub struct PipeLine {
pub newlines_before: u32,
pub field: u32,
}
thread_local! {
static RECORD: RefCell<Option<Vec<PipeLine>>> = const { RefCell::new(None) };
static SCAN: Cell<(usize, u32)> = const { Cell::new((0, 0)) };
static ARMED: RefCell<Option<Vec<PipeLine>>> = const { RefCell::new(None) };
}
static AUTOLOAD_LINES: Mutex<Option<HashMap<String, Vec<PipeLine>>>> = Mutex::new(None);
pub fn deparse(prog: crate::ported::zsh_h::eprog) -> (String, Vec<PipeLine>) {
RECORD.with(|r| *r.borrow_mut() = Some(Vec::new()));
SCAN.with(|s| s.set((0, 0)));
let text = crate::ported::text::getpermtext(Box::new(prog), None, 0);
let lines = RECORD.with(|r| r.borrow_mut().take()).unwrap_or_default();
(text, lines)
}
pub fn recording() -> bool {
RECORD.with(|r| r.borrow().is_some())
}
pub fn note(buf: &[u8], field: u32) {
let (scanned, mut count) = SCAN.with(|s| s.get());
let from = if buf.len() < scanned {
count = 0;
0
} else {
scanned
};
count += buf[from..].iter().filter(|&&b| b == b'\n').count() as u32;
SCAN.with(|s| s.set((buf.len(), count)));
RECORD.with(|r| {
if let Some(v) = r.borrow_mut().as_mut() {
v.push(PipeLine {
newlines_before: count,
field,
});
}
});
}
pub fn note_autoload(name: &str, lines: Option<Vec<PipeLine>>) {
let mut slot = AUTOLOAD_LINES.lock().unwrap_or_else(|e| e.into_inner());
match lines {
Some(l) => {
slot.get_or_insert_with(HashMap::new).insert(name.to_string(), l);
}
None => {
if let Some(map) = slot.as_mut() {
map.remove(name);
}
}
}
}
pub fn autoload_lines(name: &str) -> Option<Vec<PipeLine>> {
let slot = AUTOLOAD_LINES.lock().unwrap_or_else(|e| e.into_inner());
slot.as_ref().and_then(|m| m.get(name).cloned())
}
pub struct Armed;
impl Armed {
pub fn new(lines: Option<Vec<PipeLine>>) -> Self {
ARMED.with(|a| *a.borrow_mut() = lines);
Armed
}
}
impl Drop for Armed {
fn drop(&mut self) {
ARMED.with(|a| *a.borrow_mut() = None);
}
}
pub fn apply_armed(program: &mut ZshProgram) {
if let Some(lines) = ARMED.with(|a| a.borrow_mut().take()) {
apply(program, &lines);
}
}
struct Slot<'a> {
lineno: &'a mut u64,
frame: Option<usize>,
}
pub fn apply(program: &mut ZshProgram, lines: &[PipeLine]) {
let total = {
let mut all = Vec::new();
walk_program(program, None, &mut all);
all.len()
};
let target = match &mut program.lists[..] {
[only] if total == lines.len() + 1 && is_wrapper(&only.sublist) => {
match &mut only.sublist.pipe.cmd {
ZshCommand::FuncDef(fd) => &mut *fd.body,
_ => return,
}
}
_ => program,
};
let mut slots = Vec::new();
walk_program(target, None, &mut slots);
if slots.len() != lines.len() {
tracing::debug!(
ast = slots.len(),
wordcode = lines.len(),
"zwc lineno: pipeline counts differ, lines left as deparsed"
);
return;
}
let mut new_lines: Vec<i64> = Vec::with_capacity(slots.len());
for (slot, line) in slots.into_iter().zip(lines) {
let ast = *slot.lineno as i64;
let field = line.field as i64;
let new = match (line.field, slot.frame) {
(0, _) => ast,
(_, Some(f)) => new_lines[f] + field - 1,
(_, None) => ast - line.newlines_before as i64 + field - 2,
}
.max(0);
*slot.lineno = new as u64;
new_lines.push(new);
}
}
fn is_wrapper(s: &ZshSublist) -> bool {
s.next.is_none() && s.pipe.next.is_none() && matches!(s.pipe.cmd, ZshCommand::FuncDef(_))
}
fn walk_program<'a>(p: &'a mut ZshProgram, frame: Option<usize>, out: &mut Vec<Slot<'a>>) {
for list in &mut p.lists {
walk_sublist(&mut list.sublist, frame, out);
}
}
fn walk_sublist<'a>(s: &'a mut ZshSublist, frame: Option<usize>, out: &mut Vec<Slot<'a>>) {
walk_pipe(&mut s.pipe, frame, out);
if let Some((_, next)) = &mut s.next {
walk_sublist(next, frame, out);
}
}
fn walk_pipe<'a>(p: &'a mut ZshPipe, frame: Option<usize>, out: &mut Vec<Slot<'a>>) {
let here = out.len();
out.push(Slot {
lineno: &mut p.lineno,
frame,
});
walk_command(&mut p.cmd, frame, here, out);
if let Some(next) = &mut p.next {
walk_pipe(next, frame, out);
}
}
fn walk_command<'a>(
c: &'a mut ZshCommand,
frame: Option<usize>,
here: usize,
out: &mut Vec<Slot<'a>>,
) {
match c {
ZshCommand::Simple(_) | ZshCommand::Cond(_) | ZshCommand::Arith(_) => {}
ZshCommand::Subsh(p) | ZshCommand::Cursh(p) => walk_program(p, frame, out),
ZshCommand::For(f) => walk_program(&mut f.body, frame, out),
ZshCommand::Case(cs) => {
for arm in &mut cs.arms {
walk_program(&mut arm.body, frame, out);
}
}
ZshCommand::If(i) => {
walk_program(&mut i.cond, frame, out);
walk_program(&mut i.then, frame, out);
for (c, b) in &mut i.elif {
walk_program(c, frame, out);
walk_program(b, frame, out);
}
if let Some(e) = &mut i.else_ {
walk_program(e, frame, out);
}
}
ZshCommand::While(w) | ZshCommand::Until(w) => {
walk_program(&mut w.cond, frame, out);
walk_program(&mut w.body, frame, out);
}
ZshCommand::Repeat(r) => walk_program(&mut r.body, frame, out),
ZshCommand::FuncDef(fd) => walk_program(&mut fd.body, Some(here), out),
ZshCommand::Time(s) => {
if let Some(s) = s {
walk_sublist(s, frame, out);
}
}
ZshCommand::Try(t) => {
walk_program(&mut t.try_block, frame, out);
walk_program(&mut t.always, frame, out);
}
ZshCommand::Redirected(inner, _) => walk_command(inner, frame, here, out),
}
}