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