Skip to main content

zsh/
context.rs

1//! Context save and restore for zshrs
2//!
3//! Direct port from zsh/Src/context.c
4//!
5//! This module provides a stack of saved contexts for history, lexer, and parser state.
6
7use std::cell::RefCell;
8
9/// Parts of context that can be saved/restored
10pub const ZCONTEXT_HIST: u32 = 1;
11pub const ZCONTEXT_LEX: u32 = 2;
12pub const ZCONTEXT_PARSE: u32 = 4;
13
14/// History state that gets pushed onto context stack
15#[derive(Clone, Default)]
16pub struct HistStack {
17    pub curhist: usize,
18    pub histsiz: usize,
19    pub savehistsiz: usize,
20}
21
22/// Lexer state that gets pushed onto context stack
23#[derive(Clone, Default)]
24pub struct LexStack {
25    pub tok: i32,
26    pub tokstr: Option<String>,
27    pub zsession: Option<String>,
28}
29
30/// Parser state that gets pushed onto context stack
31#[derive(Clone, Default)]
32pub struct ParseStack {
33    pub ecused: usize,
34    pub ecnpats: usize,
35}
36
37/// A saved context entry
38#[derive(Clone, Default)]
39pub struct ContextStack {
40    pub hist_stack: HistStack,
41    pub lex_stack: LexStack,
42    pub parse_stack: ParseStack,
43}
44
45/// Context stack manager
46pub struct ContextManager {
47    stack: Vec<ContextStack>,
48}
49
50impl Default for ContextManager {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl ContextManager {
57    pub fn new() -> Self {
58        ContextManager { stack: Vec::new() }
59    }
60
61    /// Check if context stack is empty (at top level)
62    pub fn is_empty(&self) -> bool {
63        self.stack.is_empty()
64    }
65
66    /// Save some or all of current context
67    pub fn save_partial(
68        &mut self,
69        parts: u32,
70        hist: &HistStack,
71        lex: &LexStack,
72        parse: &ParseStack,
73    ) {
74        let mut ctx = ContextStack::default();
75
76        if (parts & ZCONTEXT_HIST) != 0 {
77            ctx.hist_stack = hist.clone();
78        }
79        if (parts & ZCONTEXT_LEX) != 0 {
80            ctx.lex_stack = lex.clone();
81        }
82        if (parts & ZCONTEXT_PARSE) != 0 {
83            ctx.parse_stack = parse.clone();
84        }
85
86        self.stack.push(ctx);
87    }
88
89    /// Save full context
90    pub fn save(&mut self, hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
91        self.save_partial(
92            ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE,
93            hist,
94            lex,
95            parse,
96        );
97    }
98
99    /// Restore some or all of context
100    pub fn restore_partial(&mut self, parts: u32) -> Option<ContextStack> {
101        let ctx = self.stack.pop()?;
102
103        let mut result = ContextStack::default();
104        if (parts & ZCONTEXT_HIST) != 0 {
105            result.hist_stack = ctx.hist_stack;
106        }
107        if (parts & ZCONTEXT_LEX) != 0 {
108            result.lex_stack = ctx.lex_stack;
109        }
110        if (parts & ZCONTEXT_PARSE) != 0 {
111            result.parse_stack = ctx.parse_stack;
112        }
113
114        Some(result)
115    }
116
117    /// Restore full context
118    pub fn restore(&mut self) -> Option<ContextStack> {
119        self.restore_partial(ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE)
120    }
121
122    /// Get current stack depth
123    pub fn depth(&self) -> usize {
124        self.stack.len()
125    }
126}
127
128thread_local! {
129    static CONTEXT_STACK: RefCell<ContextManager> = RefCell::new(ContextManager::new());
130}
131
132/// Save context in full (global function)
133pub fn zcontext_save(hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
134    CONTEXT_STACK.with(|cs| {
135        cs.borrow_mut().save(hist, lex, parse);
136    });
137}
138
139/// Save partial context (global function)
140pub fn zcontext_save_partial(parts: u32, hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
141    CONTEXT_STACK.with(|cs| {
142        cs.borrow_mut().save_partial(parts, hist, lex, parse);
143    });
144}
145
146/// Restore full context (global function)
147pub fn zcontext_restore() -> Option<ContextStack> {
148    CONTEXT_STACK.with(|cs| cs.borrow_mut().restore())
149}
150
151/// Restore partial context (global function)
152pub fn zcontext_restore_partial(parts: u32) -> Option<ContextStack> {
153    CONTEXT_STACK.with(|cs| cs.borrow_mut().restore_partial(parts))
154}
155
156/// Check if we're at top level (no contexts saved)
157pub fn zcontext_is_toplevel() -> bool {
158    CONTEXT_STACK.with(|cs| cs.borrow().is_empty())
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_context_save_restore() {
167        let mut mgr = ContextManager::new();
168
169        let hist = HistStack {
170            curhist: 100,
171            histsiz: 1000,
172            savehistsiz: 500,
173        };
174        let lex = LexStack {
175            tok: 42,
176            tokstr: Some("test".to_string()),
177            zsession: None,
178        };
179        let parse = ParseStack {
180            ecused: 10,
181            ecnpats: 5,
182        };
183
184        mgr.save(&hist, &lex, &parse);
185        assert_eq!(mgr.depth(), 1);
186
187        let restored = mgr.restore().unwrap();
188        assert_eq!(restored.hist_stack.curhist, 100);
189        assert_eq!(restored.lex_stack.tok, 42);
190        assert_eq!(restored.parse_stack.ecused, 10);
191        assert_eq!(mgr.depth(), 0);
192    }
193
194    #[test]
195    fn test_context_partial_save() {
196        let mut mgr = ContextManager::new();
197
198        let hist = HistStack {
199            curhist: 50,
200            histsiz: 500,
201            savehistsiz: 250,
202        };
203        let lex = LexStack::default();
204        let parse = ParseStack::default();
205
206        mgr.save_partial(ZCONTEXT_HIST, &hist, &lex, &parse);
207
208        let restored = mgr.restore_partial(ZCONTEXT_HIST).unwrap();
209        assert_eq!(restored.hist_stack.curhist, 50);
210    }
211
212    #[test]
213    fn test_nested_contexts() {
214        let mut mgr = ContextManager::new();
215
216        let hist1 = HistStack {
217            curhist: 1,
218            histsiz: 100,
219            savehistsiz: 50,
220        };
221        let hist2 = HistStack {
222            curhist: 2,
223            histsiz: 200,
224            savehistsiz: 100,
225        };
226        let lex = LexStack::default();
227        let parse = ParseStack::default();
228
229        mgr.save(&hist1, &lex, &parse);
230        mgr.save(&hist2, &lex, &parse);
231
232        assert_eq!(mgr.depth(), 2);
233
234        let restored2 = mgr.restore().unwrap();
235        assert_eq!(restored2.hist_stack.curhist, 2);
236
237        let restored1 = mgr.restore().unwrap();
238        assert_eq!(restored1.hist_stack.curhist, 1);
239
240        assert!(mgr.is_empty());
241    }
242}