Skip to main content

zsh/zle/
main.rs

1//! ZLE main routines - Direct port from zsh/Src/Zle/zle_main.c
2//!
3//! Core event loop, initialization, and main entry points for the line editor.
4//!
5//! Implements:
6//! - zleread() - main entry point for line reading
7//! - zlecore() - core editing loop
8//! - zsetterm() - terminal setup
9//! - getbyte(), getfullchar() - input reading with UTF-8 support
10//! - ungetbyte(), ungetbytes() - input pushback
11//! - calc_timeout() - key timeout handling
12//! - trashzle(), resetprompt() - display management
13//! - recursive_edit() - nested editing
14//! - bin_vared() - vared builtin
15//! - zle_main_entry() - module entry point
16
17use std::collections::VecDeque;
18use std::io::{self, Read, Write};
19use std::os::unix::io::{AsRawFd, RawFd};
20use std::time::{Duration, Instant};
21
22use super::keymap::{Keymap, KeymapManager};
23use super::thingy::Thingy;
24use super::widget::{Widget, WidgetFlags};
25
26/// ZLE character type - always char in Rust (Unicode native)
27pub type ZleChar = char;
28
29/// ZLE string type
30pub type ZleString = Vec<ZleChar>;
31
32/// ZLE integer type for character values
33pub type ZleInt = i32;
34
35/// EOF marker
36pub const ZLEEOF: ZleInt = -1;
37
38/// Flags for zleread()
39#[derive(Debug, Clone, Copy, Default)]
40pub struct ZleReadFlags {
41    /// Don't add to history
42    pub no_history: bool,
43    /// Completion context
44    pub completion: bool,
45    /// We're in a vared context
46    pub vared: bool,
47}
48
49/// Context for zleread()
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum ZleContext {
52    #[default]
53    Line,
54    Cont,
55    Select,
56    Vared,
57}
58
59/// Modifier state for commands
60#[derive(Debug, Clone, Default)]
61pub struct Modifier {
62    pub flags: ModifierFlags,
63    /// Repeat count
64    pub mult: i32,
65    /// Repeat count being edited
66    pub tmult: i32,
67    /// Vi cut buffer
68    pub vibuf: i32,
69    /// Numeric base for digit arguments (usually 10)
70    pub base: i32,
71}
72
73bitflags::bitflags! {
74    #[derive(Debug, Clone, Copy, Default)]
75    pub struct ModifierFlags: u32 {
76        /// A repeat count has been selected
77        const MULT = 1 << 0;
78        /// A repeat count is being entered
79        const TMULT = 1 << 1;
80        /// A vi cut buffer has been selected
81        const VIBUF = 1 << 2;
82        /// Appending to the vi cut buffer
83        const VIAPP = 1 << 3;
84        /// Last command was negate argument
85        const NEG = 1 << 4;
86        /// Throw away text for the vi cut buffer
87        const NULL = 1 << 5;
88        /// Force character-wise movement
89        const CHAR = 1 << 6;
90        /// Force line-wise movement
91        const LINE = 1 << 7;
92        /// OS primary selection for the vi cut buffer
93        const PRI = 1 << 8;
94        /// OS clipboard for the vi cut buffer
95        const CLIP = 1 << 9;
96    }
97}
98
99/// Undo change record
100#[derive(Debug, Clone)]
101pub struct Change {
102    /// Flags (CH_NEXT, CH_PREV)
103    pub flags: ChangeFlags,
104    /// History line being changed
105    pub hist: i32,
106    /// Offset of the text changes
107    pub off: usize,
108    /// Characters to delete
109    pub del: ZleString,
110    /// Characters to insert
111    pub ins: ZleString,
112    /// Old cursor position
113    pub old_cs: usize,
114    /// New cursor position
115    pub new_cs: usize,
116    /// Unique change number
117    pub changeno: u64,
118}
119
120bitflags::bitflags! {
121    #[derive(Debug, Clone, Copy, Default)]
122    pub struct ChangeFlags: u32 {
123        /// Next structure is also part of this change
124        const NEXT = 1 << 0;
125        /// Previous structure is also part of this change
126        const PREV = 1 << 1;
127    }
128}
129
130/// Watch file descriptor entry
131#[derive(Debug, Clone)]
132pub struct WatchFd {
133    pub fd: RawFd,
134    pub func: String,
135}
136
137/// Timeout type
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum TimeoutType {
140    None,
141    Key,
142    Func,
143    Max,
144}
145
146/// Timeout state
147#[derive(Debug, Clone)]
148pub struct Timeout {
149    pub tp: TimeoutType,
150    /// Value in 100ths of a second
151    pub exp100ths: u64,
152}
153
154/// Maximum timeout value (about 24 days in 100ths of a second)
155pub const ZMAXTIMEOUT: u64 = 1 << 21;
156
157/// The main ZLE state
158pub struct Zle {
159    /// The input line assembled so far
160    pub zleline: ZleString,
161    /// Cursor position
162    pub zlecs: usize,
163    /// Line length
164    pub zlell: usize,
165    /// Mark position
166    pub mark: usize,
167    /// Insert mode (true) or overwrite mode (false)
168    pub insmode: bool,
169    /// Done editing flag
170    pub done: bool,
171    /// Last character pressed
172    pub lastchar: ZleInt,
173    /// Last character as wide char (always used in Rust)
174    pub lastchar_wide: ZleInt,
175    /// Whether lastchar_wide is valid
176    pub lastchar_wide_valid: bool,
177    /// Binding for the previous key
178    pub lbindk: Option<Thingy>,
179    /// Binding for this key
180    pub bindk: Option<Thingy>,
181    /// Flags associated with last command
182    pub lastcmd: WidgetFlags,
183    /// Current modifier status
184    pub zmod: Modifier,
185    /// Prefix command flag
186    pub prefixflag: bool,
187    /// Recursive edit depth
188    pub zle_recursive: i32,
189    /// Read flags
190    pub zlereadflags: ZleReadFlags,
191    /// Context
192    pub zlecontext: ZleContext,
193    /// Status line
194    pub statusline: Option<String>,
195    /// History position for buffer stack
196    pub stackhist: i32,
197    /// Cursor position for buffer stack
198    pub stackcs: usize,
199    /// Vi start change position in undo stack
200    pub vistartchange: u64,
201    /// Undo stack
202    pub undo_stack: Vec<Change>,
203    /// Current change number
204    pub changeno: u64,
205    /// Unget buffer for bytes
206    unget_buf: VecDeque<u8>,
207    /// EOF character
208    eofchar: u8,
209    /// EOF sent flag
210    eofsent: bool,
211    /// Key timeout in 100ths of a second
212    pub keytimeout: u64,
213    /// Terminal baud rate
214    baud: u32,
215    /// Watch file descriptors
216    pub watch_fds: Vec<WatchFd>,
217    /// Keymap manager
218    pub keymaps: KeymapManager,
219    /// Completion widget
220    pub compwidget: Option<Widget>,
221    /// In completion function flag
222    pub incompctlfunc: bool,
223    /// Completion module loaded flag
224    pub hascompmod: bool,
225    /// Terminal file descriptor
226    ttyfd: RawFd,
227    /// Left prompt
228    lprompt: String,
229    /// Right prompt
230    rprompt: String,
231    /// Pre-ZLE status
232    pre_zle_status: i32,
233    /// Needs refresh
234    pub resetneeded: bool,
235    /// Vi cut buffers (0-35: 0-9, a-z)
236    pub vibuf: [ZleString; 36],
237    /// Kill ring
238    pub killring: VecDeque<ZleString>,
239    /// Kill ring max size
240    pub killringmax: usize,
241    /// Last command was a yank (for yank-pop)
242    pub yanklast: bool,
243    /// Negative argument flag
244    pub neg_arg: bool,
245    /// Multiplier for commands
246    pub mult: i32,
247}
248
249impl Default for Zle {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255impl Zle {
256    pub fn new() -> Self {
257        Zle {
258            zleline: Vec::new(),
259            zlecs: 0,
260            zlell: 0,
261            mark: 0,
262            insmode: true,
263            done: false,
264            lastchar: 0,
265            lastchar_wide: 0,
266            lastchar_wide_valid: false,
267            lbindk: None,
268            bindk: None,
269            lastcmd: WidgetFlags::empty(),
270            zmod: Modifier::default(),
271            prefixflag: false,
272            zle_recursive: 0,
273            zlereadflags: ZleReadFlags::default(),
274            zlecontext: ZleContext::default(),
275            statusline: None,
276            stackhist: 0,
277            stackcs: 0,
278            vistartchange: 0,
279            undo_stack: Vec::new(),
280            changeno: 0,
281            unget_buf: VecDeque::new(),
282            eofchar: 4, // Ctrl-D
283            eofsent: false,
284            keytimeout: 40, // 0.4 seconds default
285            baud: 38400,
286            watch_fds: Vec::new(),
287            keymaps: KeymapManager::new(),
288            compwidget: None,
289            incompctlfunc: false,
290            hascompmod: false,
291            ttyfd: 0, // stdin
292            lprompt: String::new(),
293            rprompt: String::new(),
294            pre_zle_status: 0,
295            resetneeded: false,
296            vibuf: std::array::from_fn(|_| Vec::new()),
297            killring: VecDeque::new(),
298            killringmax: 8,
299            yanklast: false,
300            neg_arg: false,
301            mult: 1,
302        }
303    }
304
305    /// Set up terminal for ZLE
306    pub fn zsetterm(&mut self) -> io::Result<()> {
307        use std::os::unix::io::FromRawFd;
308
309        // Get current terminal settings
310        let mut termios = termios::Termios::from_fd(self.ttyfd)?;
311
312        // Save original settings (would need to store for restore)
313
314        // Set raw mode
315        termios.c_lflag &= !(termios::ICANON | termios::ECHO);
316        termios.c_cc[termios::VMIN] = 1;
317        termios.c_cc[termios::VTIME] = 0;
318
319        // Apply settings
320        termios::tcsetattr(self.ttyfd, termios::TCSANOW, &termios)?;
321
322        Ok(())
323    }
324
325    /// Unget a byte back to the input buffer
326    pub fn ungetbyte(&mut self, ch: u8) {
327        self.unget_buf.push_front(ch);
328    }
329
330    /// Unget multiple bytes
331    pub fn ungetbytes(&mut self, s: &[u8]) {
332        for &b in s.iter().rev() {
333            self.unget_buf.push_front(b);
334        }
335    }
336
337    /// Calculate timeout for input
338    fn calc_timeout(&self, do_keytmout: bool) -> Timeout {
339        if do_keytmout && self.keytimeout > 0 {
340            let exp = if self.keytimeout > ZMAXTIMEOUT * 100 {
341                ZMAXTIMEOUT * 100
342            } else {
343                self.keytimeout
344            };
345            Timeout {
346                tp: TimeoutType::Key,
347                exp100ths: exp,
348            }
349        } else {
350            Timeout {
351                tp: TimeoutType::None,
352                exp100ths: 0,
353            }
354        }
355    }
356
357    /// Read a raw byte from input with optional timeout
358    pub fn raw_getbyte(&mut self, do_keytmout: bool) -> Option<u8> {
359        // Check unget buffer first
360        if let Some(b) = self.unget_buf.pop_front() {
361            return Some(b);
362        }
363
364        let timeout = self.calc_timeout(do_keytmout);
365
366        let timeout_duration = if timeout.tp != TimeoutType::None {
367            Some(Duration::from_millis(timeout.exp100ths * 10))
368        } else {
369            None
370        };
371
372        // Use poll/select to wait for input with timeout
373        let mut buf = [0u8; 1];
374
375        if let Some(dur) = timeout_duration {
376            // Set up poll
377            let start = Instant::now();
378            loop {
379                if start.elapsed() >= dur {
380                    return None; // Timeout
381                }
382
383                // Try non-blocking read
384                match self.try_read_byte(&mut buf) {
385                    Ok(true) => return Some(buf[0]),
386                    Ok(false) => {
387                        // No data, sleep a bit and retry
388                        std::thread::sleep(Duration::from_millis(10));
389                    }
390                    Err(_) => return None,
391                }
392            }
393        } else {
394            // Blocking read
395            match io::stdin().read(&mut buf) {
396                Ok(1) => Some(buf[0]),
397                _ => None,
398            }
399        }
400    }
401
402    /// Try to read a byte non-blocking
403    fn try_read_byte(&self, buf: &mut [u8]) -> io::Result<bool> {
404        use std::os::unix::io::AsRawFd;
405
406        let mut fds = [libc::pollfd {
407            fd: io::stdin().as_raw_fd(),
408            events: libc::POLLIN,
409            revents: 0,
410        }];
411
412        let ret = unsafe { libc::poll(fds.as_mut_ptr(), 1, 0) };
413
414        if ret > 0 && (fds[0].revents & libc::POLLIN) != 0 {
415            match io::stdin().read(buf) {
416                Ok(1) => Ok(true),
417                Ok(_) => Ok(false),
418                Err(e) => Err(e),
419            }
420        } else {
421            Ok(false)
422        }
423    }
424
425    /// Get a byte from input, handling timeout
426    pub fn getbyte(&mut self, do_keytmout: bool) -> Option<u8> {
427        let b = self.raw_getbyte(do_keytmout)?;
428
429        // Handle newline/carriage return translation
430        // (The C code swaps \n and \r for typeahead handling)
431        let b = if b == b'\n' {
432            b'\r'
433        } else if b == b'\r' {
434            b'\n'
435        } else {
436            b
437        };
438
439        self.lastchar = b as ZleInt;
440        Some(b)
441    }
442
443    /// Get a full (possibly wide) character - always returns char in Rust
444    pub fn getfullchar(&mut self, do_keytmout: bool) -> Option<char> {
445        let b = self.getbyte(do_keytmout)?;
446
447        // UTF-8 decoding
448        if b < 0x80 {
449            let c = b as char;
450            self.lastchar_wide = c as ZleInt;
451            self.lastchar_wide_valid = true;
452            return Some(c);
453        }
454
455        // Multi-byte UTF-8
456        let mut bytes = vec![b];
457        let expected_len = if b < 0xE0 {
458            2
459        } else if b < 0xF0 {
460            3
461        } else {
462            4
463        };
464
465        while bytes.len() < expected_len {
466            if let Some(next) = self.getbyte(true) {
467                if (next & 0xC0) != 0x80 {
468                    // Invalid continuation byte, unget and return error
469                    self.ungetbyte(next);
470                    break;
471                }
472                bytes.push(next);
473            } else {
474                break;
475            }
476        }
477
478        match std::str::from_utf8(&bytes) {
479            Ok(s) => {
480                if let Some(c) = s.chars().next() {
481                    self.lastchar_wide = c as ZleInt;
482                    self.lastchar_wide_valid = true;
483                    return Some(c);
484                }
485            }
486            Err(_) => {}
487        }
488
489        self.lastchar_wide_valid = false;
490        None
491    }
492
493    /// Redraw hook
494    pub fn redrawhook(&mut self) {
495        // Call redraw hook functions
496        // TODO: implement hook system
497    }
498
499    /// Core ZLE loop
500    pub fn zlecore(&mut self) {
501        self.done = false;
502
503        while !self.done {
504            // Reset prefix flag
505            if !self.prefixflag {
506                self.zmod = Modifier::default();
507            }
508            self.prefixflag = false;
509
510            // Get next key
511            let c = match self.getfullchar(false) {
512                Some(c) => c,
513                None => {
514                    self.done = true;
515                    continue;
516                }
517            };
518
519            // Look up binding
520            let key = c;
521
522            if let Some(thingy) = self.keymaps.lookup_key(key) {
523                self.lbindk = self.bindk.take();
524                self.bindk = Some(thingy.clone());
525
526                // Execute the widget
527                if let Some(widget) = &thingy.widget {
528                    self.execute_widget(widget);
529                }
530            } else {
531                // Self-insert
532                self.do_self_insert(key);
533            }
534
535            // Refresh display if needed
536            if self.resetneeded {
537                self.zrefresh();
538                self.resetneeded = false;
539            }
540        }
541    }
542
543    /// Execute a widget
544    fn execute_widget(&mut self, widget: &Widget) {
545        self.lastcmd = widget.flags;
546
547        match &widget.func {
548            super::widget::WidgetFunc::Internal(f) => {
549                f(self);
550            }
551            super::widget::WidgetFunc::User(name) => {
552                // Call user-defined widget (shell function)
553                // TODO: implement user widget execution
554                let _ = name;
555            }
556        }
557    }
558
559    /// Self-insert character (internal, used by zlecore)
560    fn do_self_insert(&mut self, c: char) {
561        if self.insmode {
562            // Insert mode
563            self.zleline.insert(self.zlecs, c);
564            self.zlecs += 1;
565            self.zlell += 1;
566        } else {
567            // Overwrite mode
568            if self.zlecs < self.zlell {
569                self.zleline[self.zlecs] = c;
570            } else {
571                self.zleline.push(c);
572                self.zlell += 1;
573            }
574            self.zlecs += 1;
575        }
576        self.resetneeded = true;
577    }
578
579    /// Main entry point for line reading
580    pub fn zleread(
581        &mut self,
582        lprompt: &str,
583        rprompt: &str,
584        flags: ZleReadFlags,
585        context: ZleContext,
586    ) -> io::Result<String> {
587        self.lprompt = lprompt.to_string();
588        self.rprompt = rprompt.to_string();
589        self.zlereadflags = flags;
590        self.zlecontext = context;
591
592        // Initialize line
593        self.zleline.clear();
594        self.zlecs = 0;
595        self.zlell = 0;
596        self.mark = 0;
597        self.done = false;
598
599        // Set up terminal
600        self.zsetterm()?;
601
602        // Display prompt
603        print!("{}", lprompt);
604        io::stdout().flush()?;
605
606        // Enter core loop
607        self.zlecore();
608
609        // Return the line
610        Ok(self.zleline.iter().collect())
611    }
612
613    /// Initialize ZLE modifiers
614    pub fn initmodifier(&mut self) {
615        self.zmod = Modifier {
616            flags: ModifierFlags::empty(),
617            mult: 1,
618            tmult: 0,
619            vibuf: -1,
620            base: 10,
621        };
622    }
623
624    /// Handle prefix commands
625    pub fn handleprefixes(&mut self) {
626        if self.zmod.flags.contains(ModifierFlags::TMULT) {
627            self.zmod.flags.remove(ModifierFlags::TMULT);
628            self.zmod.flags.insert(ModifierFlags::MULT);
629            self.zmod.mult = self.zmod.tmult;
630        }
631    }
632
633    /// Trash the ZLE display
634    pub fn trashzle(&mut self) {
635        print!("\r\x1b[K");
636        io::stdout().flush().ok();
637    }
638
639    /// Reset prompt
640    pub fn resetprompt(&mut self) {
641        self.resetneeded = true;
642    }
643
644    /// Re-expand prompt
645    pub fn reexpandprompt(&mut self) {
646        // TODO: implement prompt expansion
647        self.resetneeded = true;
648    }
649
650    /// Recursive edit
651    pub fn recursive_edit(&mut self) -> i32 {
652        self.zle_recursive += 1;
653
654        let old_done = self.done;
655        self.done = false;
656
657        self.zlecore();
658
659        self.done = old_done;
660        self.zle_recursive -= 1;
661
662        0
663    }
664
665    /// Mark line as done (accept)
666    pub fn finish_line(&mut self) {
667        self.done = true;
668    }
669
670    /// Abort input
671    pub fn abort_line(&mut self) {
672        self.zleline.clear();
673        self.zlecs = 0;
674        self.zlell = 0;
675        self.done = true;
676    }
677}
678
679impl Zle {
680    /// Save current keymap state
681    /// Port of savekeymap() from zle_main.c
682    pub fn save_keymap(&mut self) -> SavedKeymap {
683        SavedKeymap {
684            name: self.keymaps.current_name.clone(),
685            local: self.keymaps.local.clone(),
686        }
687    }
688
689    /// Restore keymap state
690    /// Port of restorekeymap() from zle_main.c
691    pub fn restore_keymap(&mut self, saved: SavedKeymap) {
692        self.keymaps.select(&saved.name);
693        self.keymaps.local = saved.local;
694    }
695
696    /// Describe key briefly
697    /// Port of describekeybriefly() from zle_main.c
698    pub fn describe_key_briefly(&mut self) {
699        if let Some(c) = self.getfullchar(false) {
700            if let Some(thingy) = self.keymaps.lookup_key(c) {
701                self.display_msg(&format!("{} is bound to {}", c, thingy.name));
702            } else {
703                self.display_msg(&format!("{} is not bound", c));
704            }
705        }
706    }
707
708    /// Where is command
709    /// Port of whereis() from zle_main.c
710    pub fn whereis(&self, widget_name: &str) -> Vec<String> {
711        let mut bindings = Vec::new();
712
713        for (name, km) in &self.keymaps.keymaps {
714            // Check single char bindings
715            for (i, opt) in km.first.iter().enumerate() {
716                if let Some(t) = opt {
717                    if t.name == widget_name {
718                        bindings.push(format!("{}:{}", name, super::utils::print_bind(&[i as u8])));
719                    }
720                }
721            }
722
723            // Check multi-char bindings
724            for (seq, kb) in &km.multi {
725                if let Some(ref t) = kb.bind {
726                    if t.name == widget_name {
727                        bindings.push(format!("{}:{}", name, super::utils::print_bind(seq)));
728                    }
729                }
730            }
731        }
732
733        bindings
734    }
735
736    /// Execute an immortal (built-in) function
737    /// Port of execimmortal() from zle_main.c
738    pub fn exec_immortal(&mut self, name: &str) -> bool {
739        if let Some(widget) = get_builtin_widget(name) {
740            self.execute_widget(&widget);
741            true
742        } else {
743            false
744        }
745    }
746
747    /// Execute a ZLE function by name
748    /// Port of execzlefunc() from zle_main.c
749    pub fn exec_zle_func(&mut self, name: &str, _args: &[String]) -> i32 {
750        if let Some(widget) = get_builtin_widget(name) {
751            self.execute_widget(&widget);
752            0
753        } else {
754            // Try user-defined widget
755            1
756        }
757    }
758
759    /// Break read (for signals)
760    /// Port of breakread() from zle_main.c
761    pub fn break_read(&mut self) {
762        self.done = true;
763    }
764
765    /// Handle before trap
766    /// Port of zlebeforetrap() from zle_main.c
767    pub fn before_trap(&mut self) {
768        // Save state before running trap
769    }
770
771    /// Handle after trap
772    /// Port of zleaftertrap() from zle_main.c
773    pub fn after_trap(&mut self) {
774        // Restore state after running trap
775        self.resetneeded = true;
776    }
777
778    /// ZLE reset prompt
779    /// Port of zle_resetprompt() from zle_main.c  
780    pub fn zle_reset_prompt(&mut self) {
781        self.resetneeded = true;
782    }
783
784    /// Display message to user (internal)
785    fn display_msg(&self, msg: &str) {
786        eprintln!("{}", msg);
787    }
788
789    /// The prompt string
790    pub fn prompt(&self) -> &str {
791        &self.lprompt
792    }
793
794    /// Set prompt
795    pub fn set_prompt(&mut self, prompt: &str) {
796        self.lprompt = prompt.to_string();
797        self.resetneeded = true;
798    }
799
800    /// Get repeat count
801    pub fn get_mult(&self) -> i32 {
802        if self.zmod.flags.contains(ModifierFlags::MULT) {
803            self.zmod.mult
804        } else {
805            1
806        }
807    }
808
809    /// Toggle negative argument flag
810    pub fn toggle_neg_arg(&mut self) {
811        self.zmod.flags.toggle(ModifierFlags::NEG);
812    }
813
814    /// Check if negative argument
815    pub fn is_neg(&self) -> bool {
816        self.zmod.flags.contains(ModifierFlags::NEG)
817    }
818
819    /// Vi command mode flag
820    pub fn is_vicmd(&self) -> bool {
821        self.keymaps.is_vi_cmd()
822    }
823
824    /// Vi insert mode flag
825    pub fn is_viins(&self) -> bool {
826        self.keymaps.is_vi_insert()
827    }
828
829    /// Emacs mode flag
830    pub fn is_emacs(&self) -> bool {
831        self.keymaps.is_emacs()
832    }
833
834    /// Check if last command was yank
835    pub fn was_yank(&self) -> bool {
836        self.lastcmd.contains(WidgetFlags::YANK)
837    }
838}
839
840/// Saved keymap state
841#[derive(Debug, Clone)]
842pub struct SavedKeymap {
843    pub name: String,
844    pub local: Option<std::sync::Arc<Keymap>>,
845}
846
847/// Get a builtin widget by name
848fn get_builtin_widget(name: &str) -> Option<Widget> {
849    Some(Widget::builtin(name))
850}
851
852/// Vared builtin implementation
853/// Port of bin_vared() from zle_main.c
854pub fn bin_vared(zle: &mut Zle, varname: &str, opts: VaredOpts) -> io::Result<String> {
855    // Get variable value
856    let initial = std::env::var(varname).unwrap_or_default();
857
858    // Set up ZLE
859    zle.zleline = initial.chars().collect();
860    zle.zlell = zle.zleline.len();
861    zle.zlecs = if opts.cursor_at_end { zle.zlell } else { 0 };
862
863    // Read with prompts
864    let prompt = opts.prompt.as_deref().unwrap_or("");
865    let rprompt = opts.rprompt.as_deref().unwrap_or("");
866
867    let result = zle.zleread(
868        prompt,
869        rprompt,
870        ZleReadFlags {
871            vared: true,
872            ..Default::default()
873        },
874        ZleContext::Vared,
875    )?;
876
877    Ok(result)
878}
879
880/// Vared options
881#[derive(Debug, Default)]
882pub struct VaredOpts {
883    pub prompt: Option<String>,
884    pub rprompt: Option<String>,
885    pub cursor_at_end: bool,
886    pub history: bool,
887}
888
889/// ZLE main entry point for module
890/// Port of zle_main_entry() from zle_main.c
891pub fn zle_main_entry(op: ZleOperation, data: ZleData) -> i32 {
892    match op {
893        ZleOperation::Read => {
894            // Would call zleread
895            0
896        }
897        ZleOperation::Refresh => {
898            // Would call refresh
899            0
900        }
901        ZleOperation::Invalidate => {
902            // Would invalidate display
903            0
904        }
905        ZleOperation::Reset => {
906            // Would reset ZLE
907            0
908        }
909        _ => 1,
910    }
911}
912
913/// ZLE operation types
914#[derive(Debug, Clone, Copy)]
915pub enum ZleOperation {
916    Read,
917    Refresh,
918    Invalidate,
919    Reset,
920    SetKeymap,
921}
922
923/// ZLE operation data
924#[derive(Debug, Default)]
925pub struct ZleData {
926    pub prompt: Option<String>,
927    pub keymap: Option<String>,
928}
929
930/// Module for termios operations
931mod termios {
932    pub use libc::{ECHO, ICANON, TCSANOW, VMIN, VTIME};
933    use std::io;
934    use std::os::unix::io::RawFd;
935
936    #[derive(Clone)]
937    pub struct Termios {
938        inner: libc::termios,
939    }
940
941    impl Termios {
942        pub fn from_fd(fd: RawFd) -> io::Result<Self> {
943            let mut termios = std::mem::MaybeUninit::uninit();
944            let ret = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
945            if ret != 0 {
946                return Err(io::Error::last_os_error());
947            }
948            Ok(Termios {
949                inner: unsafe { termios.assume_init() },
950            })
951        }
952    }
953
954    impl std::ops::Deref for Termios {
955        type Target = libc::termios;
956        fn deref(&self) -> &Self::Target {
957            &self.inner
958        }
959    }
960
961    impl std::ops::DerefMut for Termios {
962        fn deref_mut(&mut self) -> &mut Self::Target {
963            &mut self.inner
964        }
965    }
966
967    pub fn tcsetattr(fd: RawFd, action: i32, termios: &Termios) -> io::Result<()> {
968        let ret = unsafe { libc::tcsetattr(fd, action, &termios.inner) };
969        if ret != 0 {
970            return Err(io::Error::last_os_error());
971        }
972        Ok(())
973    }
974}