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  }