ls.rs
1 use core::fmt::Write; 2 use alloc::string::String as AllocString; 3 use crate::console::icons; 4 use crate::filesystems::sd::list_directory_at; 5 use crate::programs::shell; 6 7 /// (extension, glyph, ANSI color) 8 const FILE_TYPES: &[(&str, &str, &str)] = &[ 9 ("rs", icons::NF_DEV_RUST, "\x1b[33m"), 10 ("toml", icons::NF_SETI_TOML, "\x1b[32m"), 11 ("json", icons::NF_SETI_JSON, "\x1b[32m"), 12 ("csv", icons::NF_FA_DATABASE, "\x1b[36m"), 13 ("db", icons::NF_FA_DATABASE, "\x1b[36m"), 14 ("txt", icons::NF_FA_FILE_TEXT, "\x1b[0m"), 15 ("log", icons::NF_FA_FILE_TEXT, "\x1b[0m"), 16 ("md", icons::NF_SETI_MARKDOWN, "\x1b[1;33m"), 17 ("org", icons::NF_SETI_ORG, "\x1b[1;33m"), 18 ("html", icons::NF_DEV_HTML5, "\x1b[35m"), 19 ("htm", icons::NF_DEV_HTML5, "\x1b[35m"), 20 ("js", icons::NF_DEV_JAVASCRIPT, "\x1b[33m"), 21 ("css", icons::NF_DEV_CSS3, "\x1b[35m"), 22 ("wasm", icons::NF_SETI_WASM, "\x1b[35m"), 23 ("was", icons::NF_SETI_WASM, "\x1b[35m"), 24 ("svg", icons::NF_FA_FILE_IMAGE, "\x1b[35m"), 25 ("png", icons::NF_FA_FILE_IMAGE, "\x1b[35m"), 26 ("jpg", icons::NF_FA_FILE_IMAGE, "\x1b[35m"), 27 ("bin", icons::NF_MD_BINARY, "\x1b[2m"), 28 ("dat", icons::NF_MD_BINARY, "\x1b[2m"), 29 ("lock", icons::NF_FA_LOCK, "\x1b[2m"), 30 ("nix", icons::NF_LINUX_NIX, "\x1b[34m"), 31 ]; 32 33 /// (directory name, glyph) — matched case-insensitively 34 const DIR_TYPES: &[(&str, &str)] = &[ 35 ("desktop", icons::NF_FA_DESKTOP), 36 ("pictures", icons::NF_MD_PICTURE), 37 ("downloads", icons::NF_FA_DOWNLOAD), 38 ("documents", icons::NF_MD_DOCUMENT), 39 ("home", icons::NF_FA_HOME), 40 (".config", icons::NF_SETI_CONFIG), 41 ("public", icons::NF_MD_PUBLIC), 42 ("tmp", icons::NF_MD_TEMP), 43 (".ssh", icons::NF_MD_SSH), 44 ]; 45 46 const DIR_COLOR: &str = "\x1b[1;34m"; 47 48 fn file_ext(name: &str) -> Option<&str> { 49 name.rsplit('.').next() 50 } 51 52 fn lookup_file(name: &str) -> (&'static str, &'static str) { 53 if let Some(ext) = file_ext(name) { 54 for &(e, glyph, color) in FILE_TYPES { 55 if ext.eq_ignore_ascii_case(e) { 56 return (glyph, color); 57 } 58 } 59 } 60 (icons::NF_FA_FILE, "\x1b[0m") 61 } 62 63 fn lookup_dir(name: &str) -> &'static str { 64 for &(dir_name, glyph) in DIR_TYPES { 65 if name.eq_ignore_ascii_case(dir_name) { 66 return glyph; 67 } 68 } 69 icons::NF_FA_FOLDER 70 } 71 72 pub fn run(cwd: &str) -> AllocString { 73 let mut out = AllocString::new(); 74 75 match list_directory_at(cwd) { 76 Ok(entries) => { 77 if entries.is_empty() { 78 let _ = write!(out, "\x1b[2m(empty)\x1b[0m\r\n"); 79 return out; 80 } 81 82 let mut dirs = heapless::Vec::<usize, 64>::new(); 83 let mut files = heapless::Vec::<usize, 64>::new(); 84 85 for (i, entry) in entries.iter().enumerate() { 86 if entry.is_directory { 87 let _ = dirs.push(i); 88 } else { 89 let _ = files.push(i); 90 } 91 } 92 93 let mut display_entries = heapless::Vec::<AllocString, 64>::new(); 94 let mut max_width: usize = 0; 95 96 for &i in dirs.iter() { 97 let entry = &entries[i]; 98 let name = to_lower(entry.name.as_str()); 99 let glyph = lookup_dir(&name); 100 let mut s = AllocString::new(); 101 let _ = write!(s, "{}{} {}/\x1b[0m", DIR_COLOR, glyph, name); 102 let vis_width = 2 + 1 + name.len() + 1; 103 if vis_width > max_width { max_width = vis_width; } 104 let _ = display_entries.push(s); 105 } 106 107 for &i in files.iter() { 108 let entry = &entries[i]; 109 let name = to_lower(entry.name.as_str()); 110 let (glyph, color) = lookup_file(&name); 111 let mut s = AllocString::new(); 112 let _ = write!(s, "{}{} {}\x1b[0m", color, glyph, name); 113 let vis_width = 2 + 1 + name.len(); 114 if vis_width > max_width { max_width = vis_width; } 115 let _ = display_entries.push(s); 116 } 117 118 let term_width = shell::terminal_width() as usize; 119 let col_width = max_width + 2; 120 let num_cols = if col_width > 0 { (term_width / col_width).max(1) } else { 1 }; 121 let num_rows = (display_entries.len() + num_cols - 1) / num_cols; 122 123 for row in 0..num_rows { 124 for col in 0..num_cols { 125 let idx = col * num_rows + row; 126 if idx < display_entries.len() { 127 let entry_str = &display_entries[idx]; 128 let _ = write!(out, "{}", entry_str); 129 130 let vis_idx = if idx < dirs.len() { 131 let ei = dirs[idx]; 132 2 + 1 + entries[ei].name.len() + 1 133 } else { 134 let fi = idx - dirs.len(); 135 if fi < files.len() { 136 let ei = files[fi]; 137 2 + 1 + entries[ei].name.len() 138 } else { 139 0 140 } 141 }; 142 143 if col < num_cols - 1 { 144 let pad = col_width.saturating_sub(vis_idx); 145 for _ in 0..pad { 146 out.push(' '); 147 } 148 } 149 } 150 } 151 let _ = write!(out, "\r\n"); 152 } 153 } 154 Err(error) => return super::fmt_error(&error), 155 } 156 157 let _ = write!(out, "\r\n"); 158 out 159 } 160 161 fn to_lower(s: &str) -> AllocString { 162 let mut lower = AllocString::new(); 163 for c in s.chars() { 164 lower.push(c.to_ascii_lowercase()); 165 } 166 lower 167 }