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        if let Ok(s) = std::str::from_utf8(&bytes) {
479            if let Some(c) = s.chars().next() {
480                self.lastchar_wide = c as ZleInt;
481                self.lastchar_wide_valid = true;
482                return Some(c);
483            }
484        }
485
486        self.lastchar_wide_valid = false;
487        None
488    }
489
490    /// Redraw hook
491    pub fn redrawhook(&mut self) {
492        // Call redraw hook functions
493        // TODO: implement hook system
494    }
495
496    /// Core ZLE loop
497    pub fn zlecore(&mut self) {
498        self.done = false;
499
500        while !self.done {
501            // Reset prefix flag
502            if !self.prefixflag {
503                self.zmod = Modifier::default();
504            }
505            self.prefixflag = false;
506
507            // Get next key
508            let c = match self.getfullchar(false) {
509                Some(c) => c,
510                None => {
511                    self.done = true;
512                    continue;
513                }
514            };
515
516            // Look up binding
517            let key = c;
518
519            if let Some(thingy) = self.keymaps.lookup_key(key) {
520                self.lbindk = self.bindk.take();
521                self.bindk = Some(thingy.clone());
522
523                // Execute the widget
524                if let Some(widget) = &thingy.widget {
525                    self.execute_widget(widget);
526                }
527            } else {
528                // Self-insert
529                self.do_self_insert(key);
530            }
531
532            // Refresh display if needed
533            if self.resetneeded {
534                self.zrefresh();
535                self.resetneeded = false;
536            }
537        }
538    }
539
540    /// Execute a widget
541    fn execute_widget(&mut self, widget: &Widget) {
542        self.lastcmd = widget.flags;
543
544        match &widget.func {
545            super::widget::WidgetFunc::Internal(f) => {
546                f(self);
547            }
548            super::widget::WidgetFunc::User(name) => {
549                // Call user-defined widget (shell function)
550                // TODO: implement user widget execution
551                let _ = name;
552            }
553        }
554    }
555
556    /// Self-insert character (internal, used by zlecore)
557    fn do_self_insert(&mut self, c: char) {
558        if self.insmode {
559            // Insert mode
560            self.zleline.insert(self.zlecs, c);
561            self.zlecs += 1;
562            self.zlell += 1;
563        } else {
564            // Overwrite mode
565            if self.zlecs < self.zlell {
566                self.zleline[self.zlecs] = c;
567            } else {
568                self.zleline.push(c);
569                self.zlell += 1;
570            }
571            self.zlecs += 1;
572        }
573        self.resetneeded = true;
574    }
575
576    /// Main entry point for line reading
577    pub fn zleread(
578        &mut self,
579        lprompt: &str,
580        rprompt: &str,
581        flags: ZleReadFlags,
582        context: ZleContext,
583    ) -> io::Result<String> {
584        self.lprompt = lprompt.to_string();
585        self.rprompt = rprompt.to_string();
586        self.zlereadflags = flags;
587        self.zlecontext = context;
588
589        // Initialize line
590        self.zleline.clear();
591        self.zlecs = 0;
592        self.zlell = 0;
593        self.mark = 0;
594        self.done = false;
595
596        // Set up terminal
597        self.zsetterm()?;
598
599        // Display prompt
600        print!("{}", lprompt);
601        io::stdout().flush()?;
602
603        // Enter core loop
604        self.zlecore();
605
606        // Return the line
607        Ok(self.zleline.iter().collect())
608    }
609
610    /// Initialize ZLE modifiers
611    pub fn initmodifier(&mut self) {
612        self.zmod = Modifier {
613            flags: ModifierFlags::empty(),
614            mult: 1,
615            tmult: 0,
616            vibuf: -1,
617            base: 10,
618        };
619    }
620
621    /// Handle prefix commands
622    pub fn handleprefixes(&mut self) {
623        if self.zmod.flags.contains(ModifierFlags::TMULT) {
624            self.zmod.flags.remove(ModifierFlags::TMULT);
625            self.zmod.flags.insert(ModifierFlags::MULT);
626            self.zmod.mult = self.zmod.tmult;
627        }
628    }
629
630    /// Trash the ZLE display
631    pub fn trashzle(&mut self) {
632        print!("\r\x1b[K");
633        io::stdout().flush().ok();
634    }
635
636    /// Reset prompt
637    pub fn resetprompt(&mut self) {
638        self.resetneeded = true;
639    }
640
641    /// Re-expand prompt
642    pub fn reexpandprompt(&mut self) {
643        // TODO: implement prompt expansion
644        self.resetneeded = true;
645    }
646
647    /// Recursive edit
648    pub fn recursive_edit(&mut self) -> i32 {
649        self.zle_recursive += 1;
650
651        let old_done = self.done;
652        self.done = false;
653
654        self.zlecore();
655
656        self.done = old_done;
657        self.zle_recursive -= 1;
658
659        0
660    }
661
662    /// Mark line as done (accept)
663    pub fn finish_line(&mut self) {
664        self.done = true;
665    }
666
667    /// Abort input
668    pub fn abort_line(&mut self) {
669        self.zleline.clear();
670        self.zlecs = 0;
671        self.zlell = 0;
672        self.done = true;
673    }
674}
675
676impl Zle {
677    /// Save current keymap state
678    /// Port of savekeymap() from zle_main.c
679    pub fn save_keymap(&mut self) -> SavedKeymap {
680        SavedKeymap {
681            name: self.keymaps.current_name.clone(),
682            local: self.keymaps.local.clone(),
683        }
684    }
685
686    /// Restore keymap state
687    /// Port of restorekeymap() from zle_main.c
688    pub fn restore_keymap(&mut self, saved: SavedKeymap) {
689        self.keymaps.select(&saved.name);
690        self.keymaps.local = saved.local;
691    }
692
693    /// Describe key briefly
694    /// Port of describekeybriefly() from zle_main.c
695    pub fn describe_key_briefly(&mut self) {
696        if let Some(c) = self.getfullchar(false) {
697            if let Some(thingy) = self.keymaps.lookup_key(c) {
698                self.display_msg(&format!("{} is bound to {}", c, thingy.name));
699            } else {
700                self.display_msg(&format!("{} is not bound", c));
701            }
702        }
703    }
704
705    /// Where is command
706    /// Port of whereis() from zle_main.c
707    pub fn whereis(&self, widget_name: &str) -> Vec<String> {
708        let mut bindings = Vec::new();
709
710        for (name, km) in &self.keymaps.keymaps {
711            // Check single char bindings
712            for (i, opt) in km.first.iter().enumerate() {
713                if let Some(t) = opt {
714                    if t.name == widget_name {
715                        bindings.push(format!("{}:{}", name, super::utils::print_bind(&[i as u8])));
716                    }
717                }
718            }
719
720            // Check multi-char bindings
721            for (seq, kb) in &km.multi {
722                if let Some(ref t) = kb.bind {
723                    if t.name == widget_name {
724                        bindings.push(format!("{}:{}", name, super::utils::print_bind(seq)));
725                    }
726                }
727            }
728        }
729
730        bindings
731    }
732
733    /// Execute an immortal (built-in) function
734    /// Port of execimmortal() from zle_main.c
735    pub fn exec_immortal(&mut self, name: &str) -> bool {
736        if let Some(widget) = get_builtin_widget(name) {
737            self.execute_widget(&widget);
738            true
739        } else {
740            false
741        }
742    }
743
744    /// Execute a ZLE function by name
745    /// Port of execzlefunc() from zle_main.c
746    pub fn exec_zle_func(&mut self, name: &str, _args: &[String]) -> i32 {
747        if let Some(widget) = get_builtin_widget(name) {
748            self.execute_widget(&widget);
749            0
750        } else {
751            // Try user-defined widget
752            1
753        }
754    }
755
756    /// Break read (for signals)
757    /// Port of breakread() from zle_main.c
758    pub fn break_read(&mut self) {
759        self.done = true;
760    }
761
762    /// Handle before trap
763    /// Port of zlebeforetrap() from zle_main.c
764    pub fn before_trap(&mut self) {
765        // Save state before running trap
766    }
767
768    /// Handle after trap
769    /// Port of zleaftertrap() from zle_main.c
770    pub fn after_trap(&mut self) {
771        // Restore state after running trap
772        self.resetneeded = true;
773    }
774
775    /// ZLE reset prompt
776    /// Port of zle_resetprompt() from zle_main.c  
777    pub fn zle_reset_prompt(&mut self) {
778        self.resetneeded = true;
779    }
780
781    /// Display message to user (internal)
782    fn display_msg(&self, msg: &str) {
783        eprintln!("{}", msg);
784    }
785
786    /// The prompt string
787    pub fn prompt(&self) -> &str {
788        &self.lprompt
789    }
790
791    /// Set prompt
792    pub fn set_prompt(&mut self, prompt: &str) {
793        self.lprompt = prompt.to_string();
794        self.resetneeded = true;
795    }
796
797    /// Get repeat count
798    pub fn get_mult(&self) -> i32 {
799        if self.zmod.flags.contains(ModifierFlags::MULT) {
800            self.zmod.mult
801        } else {
802            1
803        }
804    }
805
806    /// Toggle negative argument flag
807    pub fn toggle_neg_arg(&mut self) {
808        self.zmod.flags.toggle(ModifierFlags::NEG);
809    }
810
811    /// Check if negative argument
812    pub fn is_neg(&self) -> bool {
813        self.zmod.flags.contains(ModifierFlags::NEG)
814    }
815
816    /// Vi command mode flag
817    pub fn is_vicmd(&self) -> bool {
818        self.keymaps.is_vi_cmd()
819    }
820
821    /// Vi insert mode flag
822    pub fn is_viins(&self) -> bool {
823        self.keymaps.is_vi_insert()
824    }
825
826    /// Emacs mode flag
827    pub fn is_emacs(&self) -> bool {
828        self.keymaps.is_emacs()
829    }
830
831    /// Check if last command was yank
832    pub fn was_yank(&self) -> bool {
833        self.lastcmd.contains(WidgetFlags::YANK)
834    }
835}
836
837/// Saved keymap state
838#[derive(Debug, Clone)]
839pub struct SavedKeymap {
840    pub name: String,
841    pub local: Option<std::sync::Arc<Keymap>>,
842}
843
844/// Get a builtin widget by name
845fn get_builtin_widget(name: &str) -> Option<Widget> {
846    Some(Widget::builtin(name))
847}
848
849/// Vared builtin implementation
850/// Port of bin_vared() from zle_main.c
851pub fn bin_vared(zle: &mut Zle, varname: &str, opts: VaredOpts) -> io::Result<String> {
852    // Get variable value
853    let initial = std::env::var(varname).unwrap_or_default();
854
855    // Set up ZLE
856    zle.zleline = initial.chars().collect();
857    zle.zlell = zle.zleline.len();
858    zle.zlecs = if opts.cursor_at_end { zle.zlell } else { 0 };
859
860    // Read with prompts
861    let prompt = opts.prompt.as_deref().unwrap_or("");
862    let rprompt = opts.rprompt.as_deref().unwrap_or("");
863
864    let result = zle.zleread(
865        prompt,
866        rprompt,
867        ZleReadFlags {
868            vared: true,
869            ..Default::default()
870        },
871        ZleContext::Vared,
872    )?;
873
874    Ok(result)
875}
876
877/// Vared options
878#[derive(Debug, Default)]
879pub struct VaredOpts {
880    pub prompt: Option<String>,
881    pub rprompt: Option<String>,
882    pub cursor_at_end: bool,
883    pub history: bool,
884}
885
886/// ZLE main entry point for module
887/// Port of zle_main_entry() from zle_main.c
888pub fn zle_main_entry(op: ZleOperation, data: ZleData) -> i32 {
889    match op {
890        ZleOperation::Read => {
891            // Would call zleread
892            0
893        }
894        ZleOperation::Refresh => {
895            // Would call refresh
896            0
897        }
898        ZleOperation::Invalidate => {
899            // Would invalidate display
900            0
901        }
902        ZleOperation::Reset => {
903            // Would reset ZLE
904            0
905        }
906        _ => 1,
907    }
908}
909
910/// ZLE operation types
911#[derive(Debug, Clone, Copy)]
912pub enum ZleOperation {
913    Read,
914    Refresh,
915    Invalidate,
916    Reset,
917    SetKeymap,
918}
919
920/// ZLE operation data
921#[derive(Debug, Default)]
922pub struct ZleData {
923    pub prompt: Option<String>,
924    pub keymap: Option<String>,
925}
926
927/// Module for termios operations
928mod termios {
929    pub use libc::{ECHO, ICANON, TCSANOW, VMIN, VTIME};
930    use std::io;
931    use std::os::unix::io::RawFd;
932
933    #[derive(Clone)]
934    pub struct Termios {
935        inner: libc::termios,
936    }
937
938    impl Termios {
939        pub fn from_fd(fd: RawFd) -> io::Result<Self> {
940            let mut termios = std::mem::MaybeUninit::uninit();
941            let ret = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
942            if ret != 0 {
943                return Err(io::Error::last_os_error());
944            }
945            Ok(Termios {
946                inner: unsafe { termios.assume_init() },
947            })
948        }
949    }
950
951    impl std::ops::Deref for Termios {
952        type Target = libc::termios;
953        fn deref(&self) -> &Self::Target {
954            &self.inner
955        }
956    }
957
958    impl std::ops::DerefMut for Termios {
959        fn deref_mut(&mut self) -> &mut Self::Target {
960            &mut self.inner
961        }
962    }
963
964    pub fn tcsetattr(fd: RawFd, action: i32, termios: &Termios) -> io::Result<()> {
965        let ret = unsafe { libc::tcsetattr(fd, action, &termios.inner) };
966        if ret != 0 {
967            return Err(io::Error::last_os_error());
968        }
969        Ok(())
970    }
971}