prompt.rs
1 use alloc::string::String as AllocString; 2 use core::fmt::Write; 3 4 use embassy_time::Instant; 5 6 use crate::services::identity; 7 8 use super::{ 9 path::{display_cwd, home_dir}, 10 terminal_width, 11 }; 12 13 unsafe extern "C" { 14 #[link_name = "esp_app_desc"] 15 static ESP_APP_DESC: esp_bootloader_esp_idf::EspAppDesc; 16 } 17 18 pub mod theme { 19 use crate::console::icons; 20 21 pub const OS_ICON: &str = icons::NF_FA_MICROCHIP; 22 pub const OS_FOREGROUND: &str = "\x1b[30m"; 23 pub const OS_BACKGROUND: &str = "\x1b[44m"; 24 pub const OS_BG_AS_FG: &str = "\x1b[34m"; 25 26 pub const HOME_ICON: &str = icons::NF_FA_HOME; 27 pub const ROOT_ICON: &str = icons::NF_FA_LOCK; 28 pub const FOLDER_ICON: &str = icons::NF_FA_FOLDER_OPEN; 29 pub const DIR_FOREGROUND: &str = "\x1b[30m"; 30 pub const DIR_BACKGROUND: &str = "\x1b[45m"; 31 pub const DIR_BG_AS_FG: &str = "\x1b[35m"; 32 33 pub const ARCH_ICON: &str = icons::NF_MD_ARCH; 34 pub const ARCH_LABEL: &str = "xtensa"; 35 pub const ARCH_BACKGROUND: &str = "\x1b[43m"; 36 37 pub const CONTEXT_FOREGROUND: &str = "\x1b[33m"; 38 pub const CONTEXT_BACKGROUND: &str = "\x1b[40m"; 39 pub const CONTEXT_BG_AS_FG: &str = "\x1b[30m"; 40 41 pub const RAM_ICON: &str = icons::NF_MD_RAM; 42 pub const RAM_FOREGROUND: &str = "\x1b[30m"; 43 pub const RAM_BACKGROUND: &str = "\x1b[43m"; 44 45 pub const CLOCK_ICON: &str = icons::NF_FA_CLOCK; 46 pub const CLOCK_FOREGROUND: &str = "\x1b[30m"; 47 pub const CLOCK_BACKGROUND: &str = "\x1b[47m"; 48 pub const CLOCK_BG_AS_FG: &str = "\x1b[37m"; 49 50 pub const LEFT_SEGMENT_SEPARATOR: &str = icons::NF_PLE_LEFT_HARD; 51 pub const RIGHT_SEGMENT_SEPARATOR: &str = icons::NF_PLE_RIGHT_HARD; 52 pub const RIGHT_SUBSEGMENT_SEPARATOR: &str = icons::NF_PLE_RIGHT_SOFT; 53 54 pub const FRAME_TOP_LEFT: &str = "╭─"; 55 pub const FRAME_TOP_RIGHT: &str = "─╮"; 56 pub const FRAME_BOT_LEFT: &str = "╰─"; 57 pub const FRAME_LINE: char = '─'; 58 pub const FRAME_COLOR: &str = "\x1b[2m"; 59 pub const RESET: &str = "\x1b[0m"; 60 } 61 62 fn cwd_glyph(cwd: &str) -> &'static str { 63 let home = home_dir(); 64 if cwd == "/" { 65 theme::ROOT_ICON 66 } else if cwd == home || cwd.starts_with(home.as_str()) { 67 theme::HOME_ICON 68 } else { 69 theme::FOLDER_ICON 70 } 71 } 72 73 pub fn build_prompt(cwd: &str) -> AllocString { 74 use crate::time::{get_current_epoch_secs, is_time_synced}; 75 76 let mut prompt_buffer = AllocString::new(); 77 78 let time_str = if is_time_synced() { 79 let epoch = get_current_epoch_secs(); 80 let local_epoch = (epoch as i64 + crate::config::app::time::UTC_OFFSET_HOURS * 3600) as u64; 81 let secs_of_day = local_epoch % 86400; 82 let hour24 = secs_of_day / 3600; 83 let minute = (secs_of_day % 3600) / 60; 84 let second = secs_of_day % 60; 85 let (hour12, ampm) = if hour24 == 0 { 86 (12, "AM") 87 } else if hour24 < 12 { 88 (hour24, "AM") 89 } else if hour24 == 12 { 90 (12, "PM") 91 } else { 92 (hour24 - 12, "PM") 93 }; 94 let mut time = AllocString::new(); 95 let _ = write!(time, "{:02}:{:02}:{:02} {}", hour12, minute, second, ampm); 96 time 97 } else { 98 let uptime = Instant::now().as_secs(); 99 let mut time = AllocString::new(); 100 let _ = write!(time, "{}m{}s", uptime / 60, uptime % 60); 101 time 102 }; 103 104 let display = display_cwd(cwd); 105 let glyph = cwd_glyph(cwd); 106 107 let mut left = AllocString::new(); 108 let _ = write!(left, "\x1b[2m{}\x1b[0m", theme::FRAME_TOP_LEFT); 109 let _ = write!( 110 left, 111 "{}{} {} ", 112 theme::OS_BACKGROUND, 113 theme::OS_FOREGROUND, 114 theme::OS_ICON 115 ); 116 let _ = write!( 117 left, 118 "{}{}{}", 119 theme::DIR_BACKGROUND, 120 theme::OS_BG_AS_FG, 121 theme::LEFT_SEGMENT_SEPARATOR 122 ); 123 let _ = write!( 124 left, 125 "{}{} {} {} ", 126 theme::DIR_BACKGROUND, 127 theme::DIR_FOREGROUND, 128 glyph, 129 display 130 ); 131 let _ = write!( 132 left, 133 "\x1b[0m{}{}\x1b[0m", 134 theme::DIR_BG_AS_FG, 135 theme::LEFT_SEGMENT_SEPARATOR 136 ); 137 138 let heap_used = esp_alloc::HEAP.used(); 139 let heap_free = esp_alloc::HEAP.free(); 140 let heap_total = heap_used + heap_free; 141 let heap_pct = if heap_total > 0 { 142 (heap_used * 100) / heap_total 143 } else { 144 0 145 }; 146 147 let mut ram_str = AllocString::new(); 148 if heap_free >= 1024 * 1024 { 149 let _ = write!(ram_str, "{:.1}M", heap_free as f32 / (1024.0 * 1024.0)); 150 } else { 151 let _ = write!(ram_str, "{:.1}K", heap_free as f32 / 1024.0); 152 } 153 154 let mut ram_pct_str = AllocString::new(); 155 let _ = write!(ram_pct_str, "{}%", heap_pct); 156 157 let mut context_str = AllocString::new(); 158 let _ = write!( 159 context_str, 160 "{}@{}", 161 identity::ssh_user(), 162 identity::hostname() 163 ); 164 165 let mut right = AllocString::new(); 166 let _ = write!( 167 right, 168 "{}{}\x1b[0m", 169 theme::CONTEXT_BG_AS_FG, 170 theme::RIGHT_SEGMENT_SEPARATOR 171 ); 172 let _ = write!( 173 right, 174 "{}{} {} ", 175 theme::CONTEXT_BACKGROUND, 176 theme::CONTEXT_FOREGROUND, 177 context_str 178 ); 179 let _ = write!( 180 right, 181 "{}{}{} ", 182 theme::RAM_BACKGROUND, 183 theme::RAM_FOREGROUND, 184 theme::RIGHT_SEGMENT_SEPARATOR 185 ); 186 let _ = write!( 187 right, 188 "{}{} {} {} {} {} {} {} ", 189 theme::RAM_BACKGROUND, 190 theme::RAM_FOREGROUND, 191 ram_pct_str, 192 theme::RAM_ICON, 193 ram_str, 194 theme::RIGHT_SUBSEGMENT_SEPARATOR, 195 theme::ARCH_LABEL, 196 theme::ARCH_ICON 197 ); 198 let _ = write!( 199 right, 200 "{}{}{}\x1b[0m", 201 theme::ARCH_BACKGROUND, 202 theme::CLOCK_BG_AS_FG, 203 theme::RIGHT_SEGMENT_SEPARATOR 204 ); 205 let _ = write!( 206 right, 207 "{}{} {} {} \x1b[0m", 208 theme::CLOCK_BACKGROUND, 209 theme::CLOCK_FOREGROUND, 210 time_str, 211 theme::CLOCK_ICON 212 ); 213 let _ = write!(right, "\x1b[2m{}\x1b[0m", theme::FRAME_TOP_RIGHT); 214 215 let nf_icons_left = 3; 216 let nf_icons_right = 6; 217 let left_vis = 2 + 1 + display.len() + nf_icons_left * 2 + 4; 218 let right_vis = context_str.len() 219 + 2 220 + ram_pct_str.len() 221 + 1 222 + ram_str.len() 223 + 1 224 + theme::ARCH_LABEL.len() 225 + time_str.len() 226 + 2 227 + nf_icons_right * 2 228 + 8; 229 let fill = (terminal_width() as usize) 230 .saturating_sub(left_vis + right_vis) 231 .max(1); 232 233 let _ = write!(prompt_buffer, "{}", left); 234 let _ = write!(prompt_buffer, "\x1b[2m"); 235 for _ in 0..fill { 236 prompt_buffer.push(theme::FRAME_LINE); 237 } 238 let _ = write!(prompt_buffer, "\x1b[0m"); 239 let _ = write!(prompt_buffer, "{}\r\n", right); 240 let _ = write!(prompt_buffer, "\x1b[2m{}\x1b[0m ", theme::FRAME_BOT_LEFT); 241 242 prompt_buffer 243 } 244 245 pub fn build_motd(remote: &str) -> AllocString { 246 use crate::time; 247 use esp_hal::efuse; 248 249 let mut out = AllocString::new(); 250 251 let epoch = time::get_current_epoch_secs(); 252 if epoch > 0 { 253 let ts = time::format_iso8601(epoch); 254 let _ = write!(out, "Last login: {} from {}\r\n", ts, remote); 255 } 256 257 let chip_rev = efuse::chip_revision(); 258 unsafe { 259 let desc = &ESP_APP_DESC; 260 let _ = write!( 261 out, 262 "Microvisor {} (ESP32-S3 rev {}.{}) built {}\r\n", 263 desc.version(), 264 chip_rev.major, 265 chip_rev.minor, 266 desc.date(), 267 ); 268 } 269 270 let _ = write!(out, "\r\n"); 271 let _ = write!(out, "Welcome to Microvisor!\r\n"); 272 let _ = write!(out, "\r\n"); 273 let _ = write!(out, "System information: microfetch\r\n"); 274 let _ = write!(out, "Hardware sensors: sensors\r\n"); 275 let _ = write!(out, "Network interfaces: ifconfig\r\n"); 276 let _ = write!(out, "Memory usage: free\r\n"); 277 let _ = write!(out, "Disk usage: df\r\n"); 278 let _ = write!(out, "\r\n"); 279 let _ = write!( 280 out, 281 "Data files are stored on the SD card mounted at /.\r\n" 282 ); 283 let _ = write!(out, "Show the list of available commands: help\r\n"); 284 let _ = write!(out, "\r\n"); 285 286 out 287 }