table.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. use std::cmp::max;
  2. use std::env;
  3. use std::ops::Deref;
  4. #[cfg(unix)]
  5. use std::sync::{Mutex, MutexGuard};
  6. use datetime::TimeZone;
  7. use zoneinfo_compiled::{CompiledData, Result as TZResult};
  8. use lazy_static::lazy_static;
  9. use log::*;
  10. #[cfg(unix)]
  11. use users::UsersCache;
  12. use crate::fs::{File, fields as f};
  13. use crate::fs::feature::git::GitCache;
  14. use crate::output::cell::TextCell;
  15. use crate::output::render::TimeRender;
  16. use crate::output::time::TimeFormat;
  17. use crate::theme::Theme;
  18. /// Options for displaying a table.
  19. #[derive(PartialEq, Debug)]
  20. pub struct Options {
  21. pub size_format: SizeFormat,
  22. pub time_format: TimeFormat,
  23. pub columns: Columns,
  24. }
  25. /// Extra columns to display in the table.
  26. #[derive(PartialEq, Debug, Copy, Clone)]
  27. pub struct Columns {
  28. /// At least one of these timestamps will be shown.
  29. pub time_types: TimeTypes,
  30. // The rest are just on/off
  31. pub inode: bool,
  32. pub links: bool,
  33. pub blocks: bool,
  34. pub group: bool,
  35. pub git: bool,
  36. pub octal: bool,
  37. // Defaults to true:
  38. pub permissions: bool,
  39. pub filesize: bool,
  40. pub user: bool,
  41. }
  42. impl Columns {
  43. pub fn collect(&self, actually_enable_git: bool) -> Vec<Column> {
  44. let mut columns = Vec::with_capacity(4);
  45. if self.inode {
  46. #[cfg(unix)]
  47. columns.push(Column::Inode);
  48. }
  49. if self.octal {
  50. columns.push(Column::Octal);
  51. }
  52. if self.permissions {
  53. #[cfg(unix)]
  54. columns.push(Column::Permissions);
  55. }
  56. if self.links {
  57. #[cfg(unix)]
  58. columns.push(Column::HardLinks);
  59. }
  60. if self.filesize {
  61. columns.push(Column::FileSize);
  62. }
  63. if self.blocks {
  64. #[cfg(unix)]
  65. columns.push(Column::Blocks);
  66. }
  67. if self.user {
  68. #[cfg(unix)]
  69. columns.push(Column::User);
  70. }
  71. if self.group {
  72. #[cfg(unix)]
  73. columns.push(Column::Group);
  74. }
  75. if self.time_types.modified {
  76. #[cfg(unix)]
  77. columns.push(Column::Timestamp(TimeType::Modified));
  78. }
  79. if self.time_types.changed {
  80. #[cfg(unix)]
  81. columns.push(Column::Timestamp(TimeType::Changed));
  82. }
  83. if self.time_types.created {
  84. #[cfg(unix)]
  85. columns.push(Column::Timestamp(TimeType::Created));
  86. }
  87. if self.time_types.accessed {
  88. #[cfg(unix)]
  89. columns.push(Column::Timestamp(TimeType::Accessed));
  90. }
  91. if cfg!(feature = "git") && self.git && actually_enable_git {
  92. columns.push(Column::GitStatus);
  93. }
  94. columns
  95. }
  96. }
  97. /// A table contains these.
  98. #[derive(Debug, Copy, Clone)]
  99. pub enum Column {
  100. #[cfg(unix)]
  101. Permissions,
  102. FileSize,
  103. #[cfg(unix)]
  104. Timestamp(TimeType),
  105. #[cfg(unix)]
  106. Blocks,
  107. #[cfg(unix)]
  108. User,
  109. #[cfg(unix)]
  110. Group,
  111. #[cfg(unix)]
  112. HardLinks,
  113. #[cfg(unix)]
  114. Inode,
  115. GitStatus,
  116. Octal,
  117. }
  118. /// Each column can pick its own **Alignment**. Usually, numbers are
  119. /// right-aligned, and text is left-aligned.
  120. #[derive(Copy, Clone)]
  121. pub enum Alignment {
  122. Left,
  123. Right,
  124. }
  125. impl Column {
  126. /// Get the alignment this column should use.
  127. #[cfg(unix)]
  128. pub fn alignment(self) -> Alignment {
  129. match self {
  130. Self::FileSize |
  131. Self::HardLinks |
  132. Self::Inode |
  133. Self::Blocks |
  134. Self::GitStatus => Alignment::Right,
  135. _ => Alignment::Left,
  136. }
  137. }
  138. #[cfg(windows)]
  139. pub fn alignment(&self) -> Alignment {
  140. match *self {
  141. Column::FileSize | Column::GitStatus => Alignment::Right,
  142. _ => Alignment::Left,
  143. }
  144. }
  145. #[cfg(windows)]
  146. /// Get the text that should be printed at the top, when the user elects
  147. /// to have a header row printed.
  148. pub fn header(self) -> &'static str {
  149. match self {
  150. #[cfg(unix)]
  151. Self::Permissions => "Permissions",
  152. Self::FileSize => "Size",
  153. #[cfg(unix)]
  154. Self::Timestamp(t) => t.header(),
  155. #[cfg(unix)]
  156. Self::Blocks => "Blocks",
  157. #[cfg(unix)]
  158. Self::User => "User",
  159. #[cfg(unix)]
  160. Self::Group => "Group",
  161. #[cfg(unix)]
  162. Self::HardLinks => "Links",
  163. #[cfg(unix)]
  164. Self::Inode => "inode",
  165. Self::GitStatus => "Git",
  166. Self::Octal => "Octal",
  167. }
  168. }
  169. }
  170. /// Formatting options for file sizes.
  171. #[derive(PartialEq, Debug, Copy, Clone)]
  172. pub enum SizeFormat {
  173. /// Format the file size using **decimal** prefixes, such as “kilo”,
  174. /// “mega”, or “giga”.
  175. DecimalBytes,
  176. /// Format the file size using **binary** prefixes, such as “kibi”,
  177. /// “mebi”, or “gibi”.
  178. BinaryBytes,
  179. /// Do no formatting and just display the size as a number of bytes.
  180. JustBytes,
  181. }
  182. impl Default for SizeFormat {
  183. fn default() -> Self {
  184. Self::DecimalBytes
  185. }
  186. }
  187. /// The types of a file’s time fields. These three fields are standard
  188. /// across most (all?) operating systems.
  189. #[derive(PartialEq, Debug, Copy, Clone)]
  190. pub enum TimeType {
  191. /// The file’s modified time (`st_mtime`).
  192. Modified,
  193. /// The file’s changed time (`st_ctime`)
  194. Changed,
  195. /// The file’s accessed time (`st_atime`).
  196. Accessed,
  197. /// The file’s creation time (`btime` or `birthtime`).
  198. Created,
  199. }
  200. impl TimeType {
  201. /// Returns the text to use for a column’s heading in the columns output.
  202. pub fn header(self) -> &'static str {
  203. match self {
  204. Self::Modified => "Date Modified",
  205. Self::Changed => "Date Changed",
  206. Self::Accessed => "Date Accessed",
  207. Self::Created => "Date Created",
  208. }
  209. }
  210. }
  211. /// Fields for which of a file’s time fields should be displayed in the
  212. /// columns output.
  213. ///
  214. /// There should always be at least one of these — there’s no way to disable
  215. /// the time columns entirely (yet).
  216. #[derive(PartialEq, Debug, Copy, Clone)]
  217. #[allow(clippy::struct_excessive_bools)]
  218. pub struct TimeTypes {
  219. pub modified: bool,
  220. pub changed: bool,
  221. pub accessed: bool,
  222. pub created: bool,
  223. }
  224. impl Default for TimeTypes {
  225. /// By default, display just the ‘modified’ time. This is the most
  226. /// common option, which is why it has this shorthand.
  227. fn default() -> Self {
  228. Self {
  229. modified: true,
  230. changed: false,
  231. accessed: false,
  232. created: false,
  233. }
  234. }
  235. }
  236. /// The **environment** struct contains any data that could change between
  237. /// running instances of exa, depending on the user’s computer’s configuration.
  238. ///
  239. /// Any environment field should be able to be mocked up for test runs.
  240. pub struct Environment {
  241. /// Localisation rules for formatting numbers.
  242. numeric: locale::Numeric,
  243. /// The computer’s current time zone. This gets used to determine how to
  244. /// offset files’ timestamps.
  245. tz: Option<TimeZone>,
  246. /// Mapping cache of user IDs to usernames.
  247. #[cfg(unix)]
  248. users: Mutex<UsersCache>,
  249. }
  250. impl Environment {
  251. #[cfg(unix)]
  252. pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {
  253. self.users.lock().unwrap()
  254. }
  255. fn load_all() -> Self {
  256. let tz = match determine_time_zone() {
  257. Ok(t) => {
  258. Some(t)
  259. }
  260. Err(ref e) => {
  261. println!("Unable to determine time zone: {}", e);
  262. None
  263. }
  264. };
  265. let numeric = locale::Numeric::load_user_locale()
  266. .unwrap_or_else(|_| locale::Numeric::english());
  267. #[cfg(unix)]
  268. let users = Mutex::new(UsersCache::new());
  269. Self { tz, numeric, #[cfg(unix)] users }
  270. }
  271. }
  272. fn determine_time_zone() -> TZResult<TimeZone> {
  273. if let Ok(file) = env::var("TZ") {
  274. TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
  275. }
  276. else {
  277. TimeZone::from_file("/etc/localtime")
  278. }
  279. }
  280. lazy_static! {
  281. static ref ENVIRONMENT: Environment = Environment::load_all();
  282. }
  283. pub struct Table<'a> {
  284. columns: Vec<Column>,
  285. theme: &'a Theme,
  286. env: &'a Environment,
  287. widths: TableWidths,
  288. time_format: TimeFormat,
  289. size_format: SizeFormat,
  290. git: Option<&'a GitCache>,
  291. }
  292. #[derive(Clone)]
  293. pub struct Row {
  294. cells: Vec<TextCell>,
  295. }
  296. impl<'a, 'f> Table<'a> {
  297. pub fn new(options: &'a Options, git: Option<&'a GitCache>, theme: &'a Theme) -> Table<'a> {
  298. let columns = options.columns.collect(git.is_some());
  299. let widths = TableWidths::zero(columns.len());
  300. let env = &*ENVIRONMENT;
  301. Table {
  302. theme,
  303. widths,
  304. columns,
  305. git,
  306. env,
  307. time_format: options.time_format,
  308. size_format: options.size_format,
  309. }
  310. }
  311. pub fn widths(&self) -> &TableWidths {
  312. &self.widths
  313. }
  314. pub fn header_row(&self) -> Row {
  315. let cells = self.columns.iter()
  316. .map(|c| TextCell::paint_str(self.theme.ui.header, c.header()))
  317. .collect();
  318. Row { cells }
  319. }
  320. pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row {
  321. let cells = self.columns.iter()
  322. .map(|c| self.display(file, *c, xattrs))
  323. .collect();
  324. Row { cells }
  325. }
  326. pub fn add_widths(&mut self, row: &Row) {
  327. self.widths.add_widths(row)
  328. }
  329. fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus {
  330. f::PermissionsPlus {
  331. file_type: file.type_char(),
  332. #[cfg(unix)]
  333. permissions: file.permissions(),
  334. xattrs,
  335. }
  336. }
  337. fn octal_permissions(&self, file: &File<'_>) -> f::OctalPermissions {
  338. f::OctalPermissions {
  339. permissions: file.permissions(),
  340. }
  341. }
  342. fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
  343. match column {
  344. #[cfg(unix)]
  345. Column::Permissions => {
  346. self.permissions_plus(file, xattrs).render(self.theme)
  347. }
  348. Column::FileSize => {
  349. file.size().render(self.theme, self.size_format, &self.env.numeric)
  350. }
  351. #[cfg(unix)]
  352. Column::HardLinks => {
  353. file.links().render(self.theme, &self.env.numeric)
  354. }
  355. #[cfg(unix)]
  356. Column::Inode => {
  357. file.inode().render(self.theme.ui.inode)
  358. }
  359. #[cfg(unix)]
  360. Column::Blocks => {
  361. file.blocks().render(self.theme)
  362. }
  363. #[cfg(unix)]
  364. Column::User => {
  365. file.user().render(self.theme, &*self.env.lock_users())
  366. }
  367. #[cfg(unix)]
  368. Column::Group => {
  369. file.group().render(self.theme, &*self.env.lock_users())
  370. }
  371. Column::GitStatus => {
  372. self.git_status(file).render(self.theme)
  373. }
  374. Column::Octal => {
  375. self.octal_permissions(file).render(self.theme.ui.octal)
  376. }
  377. Column::Timestamp(TimeType::Modified) => {
  378. file.modified_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
  379. }
  380. Column::Timestamp(TimeType::Changed) => {
  381. file.changed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
  382. }
  383. Column::Timestamp(TimeType::Created) => {
  384. file.created_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
  385. }
  386. Column::Timestamp(TimeType::Accessed) => {
  387. file.accessed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
  388. }
  389. }
  390. }
  391. fn git_status(&self, file: &File<'_>) -> f::Git {
  392. debug!("Getting Git status for file {:?}", file.path);
  393. self.git
  394. .map(|g| g.get(&file.path, file.is_directory()))
  395. .unwrap_or_default()
  396. }
  397. pub fn render(&self, row: Row) -> TextCell {
  398. let mut cell = TextCell::default();
  399. let iter = row.cells.into_iter()
  400. .zip(self.widths.iter())
  401. .enumerate();
  402. for (n, (this_cell, width)) in iter {
  403. let padding = width - *this_cell.width;
  404. match self.columns[n].alignment() {
  405. Alignment::Left => {
  406. cell.append(this_cell);
  407. cell.add_spaces(padding);
  408. }
  409. Alignment::Right => {
  410. cell.add_spaces(padding);
  411. cell.append(this_cell);
  412. }
  413. }
  414. cell.add_spaces(1);
  415. }
  416. cell
  417. }
  418. }
  419. pub struct TableWidths(Vec<usize>);
  420. impl Deref for TableWidths {
  421. type Target = [usize];
  422. fn deref(&self) -> &Self::Target {
  423. &self.0
  424. }
  425. }
  426. impl TableWidths {
  427. pub fn zero(count: usize) -> Self {
  428. Self(vec![0; count])
  429. }
  430. pub fn add_widths(&mut self, row: &Row) {
  431. for (old_width, cell) in self.0.iter_mut().zip(row.cells.iter()) {
  432. *old_width = max(*old_width, *cell.width);
  433. }
  434. }
  435. pub fn total(&self) -> usize {
  436. self.0.len() + self.0.iter().sum::<usize>()
  437. }
  438. }