Skip to main content

strat9_kernel/shell/
output.rs

1//! Shell output formatting and capture.
2//!
3//! When capture mode is active, `shell_print!` / `shell_println!` write to an
4//! internal buffer instead of serial + VGA.  This powers pipe (`|`) and
5//! redirection (`>`, `>>`) in the Chevron shell.
6
7use crate::sync::SpinLock;
8use alloc::vec::Vec;
9
10static CAPTURE_BUF: SpinLock<Option<Vec<u8>>> = SpinLock::new(None);
11static PIPE_INPUT: SpinLock<Option<Vec<u8>>> = SpinLock::new(None);
12
13/// Begin capturing shell output into an internal buffer.
14pub fn start_capture() {
15    *CAPTURE_BUF.lock() = Some(Vec::new());
16}
17
18/// Stop capturing and return the accumulated bytes.
19pub fn take_capture() -> Vec<u8> {
20    CAPTURE_BUF.lock().take().unwrap_or_default()
21}
22
23/// Returns `true` when capture mode is active.
24pub fn is_capturing() -> bool {
25    CAPTURE_BUF.lock().is_some()
26}
27
28/// Append raw bytes to the capture buffer (called by the macros).
29pub fn capture_write_bytes(data: &[u8]) {
30    if let Some(buf) = CAPTURE_BUF.lock().as_mut() {
31        buf.extend_from_slice(data);
32    }
33}
34
35/// Set pipe input data for the next command in a pipeline.
36pub fn set_pipe_input(data: Vec<u8>) {
37    *PIPE_INPUT.lock() = Some(data);
38}
39
40/// Take and return the current pipe input, if any.
41///
42/// Commands call this to consume piped data. Returns `None` when
43/// the command was not invoked as the right-hand side of a pipe.
44pub fn take_pipe_input() -> Option<Vec<u8>> {
45    PIPE_INPUT.lock().take()
46}
47
48/// Returns `true` when pipe input data is available.
49pub fn has_pipe_input() -> bool {
50    PIPE_INPUT.lock().is_some()
51}
52
53/// Clear any pending pipe input.
54pub fn clear_pipe_input() {
55    PIPE_INPUT.lock().take();
56}
57
58/// Print to both serial and VGA.
59#[macro_export]
60macro_rules! shell_print {
61    ($($arg:tt)*) => {{
62        if $crate::shell::output::is_capturing() {
63            use core::fmt::Write;
64            let mut __tmp = alloc::string::String::new();
65            let _ = write!(__tmp, $($arg)*);
66            $crate::shell::output::capture_write_bytes(__tmp.as_bytes());
67        } else {
68            $crate::serial_print!($($arg)*);
69            if $crate::arch::x86_64::vga::is_available() {
70                use core::fmt::Write;
71                let _ = write!($crate::arch::x86_64::vga::VGA_WRITER.lock(), $($arg)*);
72            }
73        }
74    }};
75}
76
77/// Print to both serial and VGA with newline.
78#[macro_export]
79macro_rules! shell_println {
80    () => ($crate::shell_print!("\n"));
81    ($($arg:tt)*) => {{
82        if $crate::shell::output::is_capturing() {
83            use core::fmt::Write;
84            let mut __tmp = alloc::string::String::new();
85            let _ = writeln!(__tmp, $($arg)*);
86            $crate::shell::output::capture_write_bytes(__tmp.as_bytes());
87        } else {
88            $crate::serial_println!($($arg)*);
89            if $crate::arch::x86_64::vga::is_available() {
90                use core::fmt::Write;
91                let _ = writeln!($crate::arch::x86_64::vga::VGA_WRITER.lock(), $($arg)*);
92            }
93        }
94    }};
95}
96
97/// Clear the VGA screen.
98pub fn clear_screen() {
99    if crate::arch::x86_64::vga::is_available() {
100        crate::arch::x86_64::vga::VGA_WRITER.lock().clear();
101    }
102}
103
104/// Print the shell prompt.
105pub fn print_prompt() {
106    shell_print!(">>> ");
107}
108
109/// Print a character (no newline).
110pub fn print_char(ch: char) {
111    if crate::arch::x86_64::vga::is_available() {
112        use core::fmt::Write;
113        let _ = write!(crate::arch::x86_64::vga::VGA_WRITER.lock(), "{}", ch);
114    } else {
115        crate::serial_print!("{}", ch);
116    }
117}
118
119/// Format bytes as human-readable size.
120pub fn format_bytes(bytes: usize) -> (usize, &'static str) {
121    const KB: usize = 1024;
122    const MB: usize = KB * 1024;
123    const GB: usize = MB * 1024;
124
125    if bytes >= GB {
126        (bytes / GB, "GB")
127    } else if bytes >= MB {
128        (bytes / MB, "MB")
129    } else if bytes >= KB {
130        (bytes / KB, "KB")
131    } else {
132        (bytes, "B")
133    }
134}