/ firmware / src / services / ssh / writer.rs
writer.rs
  1  use core::fmt;
  2  use embedded_io_async::Write as AsyncWrite;
  3  
  4  /// Terminal writer for formatted output with ANSI support
  5  pub struct TerminalWriter<'a, W: AsyncWrite> {
  6      writer: &'a mut W,
  7      ansi_enabled: bool,
  8  }
  9  
 10  impl<'a, W: AsyncWrite> TerminalWriter<'a, W> {
 11      /// Create a new terminal writer
 12      pub fn new(writer: &'a mut W, ansi_enabled: bool) -> Self {
 13          Self {
 14              writer,
 15              ansi_enabled,
 16          }
 17      }
 18  
 19      /// Write a string
 20      pub async fn write_str(&mut self, s: &str) -> Result<(), W::Error> {
 21          self.writer.write_all(s.as_bytes()).await?;
 22          self.writer.flush().await
 23      }
 24  
 25      /// Write a formatted string
 26      pub async fn write_fmt(
 27          &mut self,
 28          args: fmt::Arguments<'_>,
 29      ) -> Result<(), W::Error> {
 30          // For no_std, we need to format to a temporary buffer
 31          use heapless::String;
 32          let mut buffer = String::<256>::new();
 33          let _ = fmt::write(&mut buffer, args);
 34          self.write_str(&buffer).await
 35      }
 36  
 37      /// Write a line (adds \r\n)
 38      pub async fn writeln(&mut self, s: &str) -> Result<(), W::Error> {
 39          self.write_str(s).await?;
 40          self.write_str("\r\n").await
 41      }
 42  
 43      /// Write the prompt
 44      pub async fn write_prompt(&mut self, prompt: &str) -> Result<(), W::Error> {
 45          self.write_str(prompt).await
 46      }
 47  
 48      /// Clear the current line
 49      pub async fn clear_line(&mut self) -> Result<(), W::Error> {
 50          if self.ansi_enabled {
 51              // Move to start of line and clear
 52              self.write_str("\r\x1b[K").await
 53          } else {
 54              // Just carriage return for simple terminals
 55              self.write_str("\r").await
 56          }
 57      }
 58  
 59      /// Clear the screen
 60      pub async fn clear_screen(&mut self) -> Result<(), W::Error> {
 61          if self.ansi_enabled {
 62              self.write_str("\x1b[2J\x1b[H").await
 63          } else {
 64              // Send multiple newlines as fallback
 65              self.write_str("\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n").await
 66          }
 67      }
 68  
 69      /// Move cursor up by n lines
 70      pub async fn cursor_up(&mut self, n: usize) -> Result<(), W::Error> {
 71          if self.ansi_enabled && n > 0 {
 72              use heapless::String;
 73              let mut cmd = String::<16>::new();
 74              use core::fmt::Write;
 75              write!(&mut cmd, "\x1b[{}A", n).ok();
 76              self.write_str(&cmd).await
 77          } else {
 78              Ok(())
 79          }
 80      }
 81  
 82      /// Move cursor down by n lines
 83      pub async fn cursor_down(&mut self, n: usize) -> Result<(), W::Error> {
 84          if self.ansi_enabled && n > 0 {
 85              use heapless::String;
 86              let mut cmd = String::<16>::new();
 87              use core::fmt::Write;
 88              write!(&mut cmd, "\x1b[{}B", n).ok();
 89              self.write_str(&cmd).await
 90          } else {
 91              Ok(())
 92          }
 93      }
 94  
 95      /// Set text color (ANSI colors: 0-7 for basic colors, 8-15 for bright colors)
 96      pub async fn set_color(&mut self, color: u8) -> Result<(), W::Error> {
 97          if self.ansi_enabled {
 98              use heapless::String;
 99              let mut cmd = String::<16>::new();
100              use core::fmt::Write;
101              if color < 8 {
102                  write!(&mut cmd, "\x1b[3{}m", color).ok();
103              } else {
104                  write!(&mut cmd, "\x1b[9{}m", color - 8).ok();
105              }
106              self.write_str(&cmd).await
107          } else {
108              Ok(())
109          }
110      }
111  
112      /// Reset text formatting
113      pub async fn reset_format(&mut self) -> Result<(), W::Error> {
114          if self.ansi_enabled {
115              self.write_str("\x1b[0m").await
116          } else {
117              Ok(())
118          }
119      }
120  
121      /// Set bold text
122      pub async fn set_bold(&mut self, enable: bool) -> Result<(), W::Error> {
123          if self.ansi_enabled {
124              if enable {
125                  self.write_str("\x1b[1m").await
126              } else {
127                  self.write_str("\x1b[22m").await
128              }
129          } else {
130              Ok(())
131          }
132      }
133  
134      /// Write colored text
135      pub async fn write_colored(
136          &mut self,
137          text: &str,
138          color: u8,
139      ) -> Result<(), W::Error> {
140          self.set_color(color).await?;
141          self.write_str(text).await?;
142          self.reset_format().await
143      }
144  
145      /// Write an error message
146      pub async fn write_error(&mut self, msg: &str) -> Result<(), W::Error> {
147          if self.ansi_enabled {
148              self.write_colored(msg, 1).await // Red
149          } else {
150              self.writeln(msg).await
151          }
152      }
153  
154      /// Write a success message
155      pub async fn write_success(&mut self, msg: &str) -> Result<(), W::Error> {
156          if self.ansi_enabled {
157              self.write_colored(msg, 2).await // Green
158          } else {
159              self.writeln(msg).await
160          }
161      }
162  
163      /// Write a warning message
164      pub async fn write_warning(&mut self, msg: &str) -> Result<(), W::Error> {
165          if self.ansi_enabled {
166              self.write_colored(msg, 3).await // Yellow
167          } else {
168              self.writeln(msg).await
169          }
170      }
171  
172      /// Write an info message
173      pub async fn write_info(&mut self, msg: &str) -> Result<(), W::Error> {
174          if self.ansi_enabled {
175              self.write_colored(msg, 6).await // Cyan
176          } else {
177              self.writeln(msg).await
178          }
179      }
180  
181      /// Flush the writer
182      pub async fn flush(&mut self) -> Result<(), W::Error> {
183          self.writer.flush().await
184      }
185  }
186  
187  /// ANSI color codes for convenience
188  pub mod colors {
189      pub const BLACK: u8 = 0;
190      pub const RED: u8 = 1;
191      pub const GREEN: u8 = 2;
192      pub const YELLOW: u8 = 3;
193      pub const BLUE: u8 = 4;
194      pub const MAGENTA: u8 = 5;
195      pub const CYAN: u8 = 6;
196      pub const WHITE: u8 = 7;
197      
198      pub const BRIGHT_BLACK: u8 = 8;
199      pub const BRIGHT_RED: u8 = 9;
200      pub const BRIGHT_GREEN: u8 = 10;
201      pub const BRIGHT_YELLOW: u8 = 11;
202      pub const BRIGHT_BLUE: u8 = 12;
203      pub const BRIGHT_MAGENTA: u8 = 13;
204      pub const BRIGHT_CYAN: u8 = 14;
205      pub const BRIGHT_WHITE: u8 = 15;
206  }