details.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. //! The **Details** output view displays each file as a row in a table.
  2. //!
  3. //! It's used in the following situations:
  4. //!
  5. //! - Most commonly, when using the `--long` command-line argument to display the
  6. //! details of each file, which requires using a table view to hold all the data;
  7. //! - When using the `--tree` argument, which uses the same table view to display
  8. //! each file on its own line, with the table providing the tree characters;
  9. //! - When using both the `--long` and `--grid` arguments, which constructs a
  10. //! series of tables to fit all the data on the screen.
  11. //!
  12. //! You will probably recognise it from the `ls --long` command. It looks like
  13. //! this:
  14. //!
  15. //! ```text
  16. //! .rw-r--r-- 9.6k ben 29 Jun 16:16 Cargo.lock
  17. //! .rw-r--r-- 547 ben 23 Jun 10:54 Cargo.toml
  18. //! .rw-r--r-- 1.1k ben 23 Nov 2014 LICENCE
  19. //! .rw-r--r-- 2.5k ben 21 May 14:38 README.md
  20. //! .rw-r--r-- 382k ben 8 Jun 21:00 screenshot.png
  21. //! drwxr-xr-x - ben 29 Jun 14:50 src
  22. //! drwxr-xr-x - ben 28 Jun 19:53 target
  23. //! ```
  24. //!
  25. //! The table is constructed by creating a `Table` value, which produces a `Row`
  26. //! value for each file. These rows can contain a vector of `Cell`s, or they can
  27. //! contain depth information for the tree view, or both. These are described
  28. //! below.
  29. //!
  30. //!
  31. //! ## Constructing Detail Views
  32. //!
  33. //! When using the `--long` command-line argument, the details of each file are
  34. //! displayed next to its name.
  35. //!
  36. //! The table holds a vector of all the column types. For each file and column, a
  37. //! `Cell` value containing the ANSI-coloured text and Unicode width of each cell
  38. //! is generated, with the row and column determined by indexing into both arrays.
  39. //!
  40. //! The column types vector does not actually include the filename. This is
  41. //! because the filename is always the rightmost field, and as such, it does not
  42. //! need to have its width queried or be padded with spaces.
  43. //!
  44. //! To illustrate the above:
  45. //!
  46. //! ```text
  47. //! โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  48. //! โ”‚ columns: [ Permissions, Size, User, Date(Modified) ] โ”‚
  49. //! โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
  50. //! โ”‚ rows: cells: filename: โ”‚
  51. //! โ”‚ row 1: [ ".rw-r--r--", "9.6k", "ben", "29 Jun 16:16" ] Cargo.lock โ”‚
  52. //! โ”‚ row 2: [ ".rw-r--r--", "547", "ben", "23 Jun 10:54" ] Cargo.toml โ”‚
  53. //! โ”‚ row 3: [ "drwxr-xr-x", "-", "ben", "29 Jun 14:50" ] src โ”‚
  54. //! โ”‚ row 4: [ "drwxr-xr-x", "-", "ben", "28 Jun 19:53" ] target โ”‚
  55. //! โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  56. //! ```
  57. //!
  58. //! Each column in the table needs to be resized to fit its widest argument. This
  59. //! means that we must wait until every row has been added to the table before it
  60. //! can be displayed, in order to make sure that every column is wide enough.
  61. use std::io::{Write, Error as IOError, Result as IOResult};
  62. use std::path::PathBuf;
  63. use std::vec::IntoIter as VecIntoIter;
  64. use ansi_term::Style;
  65. use fs::{Dir, File};
  66. use fs::dir_action::RecurseOptions;
  67. use fs::filter::FileFilter;
  68. use fs::feature::git::GitCache;
  69. use fs::feature::xattr::{Attribute, FileAttributes};
  70. use style::Colours;
  71. use output::cell::TextCell;
  72. use output::tree::{TreeTrunk, TreeParams, TreeDepth};
  73. use output::file_name::FileStyle;
  74. use output::table::{Table, Options as TableOptions, Row as TableRow};
  75. /// With the **Details** view, the output gets formatted into columns, with
  76. /// each `Column` object showing some piece of information about the file,
  77. /// such as its size, or its permissions.
  78. ///
  79. /// To do this, the results have to be written to a table, instead of
  80. /// displaying each file immediately. Then, the width of each column can be
  81. /// calculated based on the individual results, and the fields are padded
  82. /// during output.
  83. ///
  84. /// Almost all the heavy lifting is done in a Table object, which handles the
  85. /// columns for each row.
  86. #[derive(Debug)]
  87. pub struct Options {
  88. /// Options specific to drawing a table.
  89. ///
  90. /// Directories themselves can pick which columns are *added* to this
  91. /// list, such as the Git column.
  92. pub table: Option<TableOptions>,
  93. /// Whether to show a header line or not.
  94. pub header: bool,
  95. /// Whether to show each file's extended attributes.
  96. pub xattr: bool,
  97. }
  98. pub struct Render<'a> {
  99. pub dir: Option<&'a Dir>,
  100. pub files: Vec<File<'a>>,
  101. pub colours: &'a Colours,
  102. pub style: &'a FileStyle,
  103. pub opts: &'a Options,
  104. /// Whether to recurse through directories with a tree view, and if so,
  105. /// which options to use. This field is only relevant here if the `tree`
  106. /// field of the RecurseOptions is `true`.
  107. pub recurse: Option<RecurseOptions>,
  108. /// How to sort and filter the files after getting their details.
  109. pub filter: &'a FileFilter,
  110. }
  111. struct Egg<'a> {
  112. table_row: Option<TableRow>,
  113. xattrs: Vec<Attribute>,
  114. errors: Vec<(IOError, Option<PathBuf>)>,
  115. dir: Option<Dir>,
  116. file: &'a File<'a>,
  117. }
  118. impl<'a> AsRef<File<'a>> for Egg<'a> {
  119. fn as_ref(&self) -> &File<'a> {
  120. self.file
  121. }
  122. }
  123. impl<'a> Render<'a> {
  124. pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, w: &mut W) -> IOResult<()> {
  125. let mut rows = Vec::new();
  126. if let Some(ref table) = self.opts.table {
  127. match (git, self.dir) {
  128. (Some(g), Some(d)) => if !g.has_anything_for(&d.path) { git = None },
  129. (Some(g), None) => if !self.files.iter().any(|f| g.has_anything_for(&f.path)) { git = None },
  130. (None, _) => {/* Keep Git how it is */},
  131. }
  132. let mut table = Table::new(&table, git, &self.colours);
  133. if self.opts.header {
  134. let header = table.header_row();
  135. table.add_widths(&header);
  136. rows.push(self.render_header(header));
  137. }
  138. // This is weird, but I canโ€™t find a way around it:
  139. // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6
  140. let mut table = Some(table);
  141. self.add_files_to_table(&mut table, &mut rows, &self.files, TreeDepth::root());
  142. for row in self.iterate_with_table(table.unwrap(), rows) {
  143. writeln!(w, "{}", row.strings())?
  144. }
  145. }
  146. else {
  147. self.add_files_to_table(&mut None, &mut rows, &self.files, TreeDepth::root());
  148. for row in self.iterate(rows) {
  149. writeln!(w, "{}", row.strings())?
  150. }
  151. }
  152. Ok(())
  153. }
  154. /// Adds files to the table, possibly recursively. This is easily
  155. /// parallelisable, and uses a pool of threads.
  156. fn add_files_to_table<'dir>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, depth: TreeDepth) {
  157. use num_cpus;
  158. use scoped_threadpool::Pool;
  159. use std::sync::{Arc, Mutex};
  160. use fs::feature::xattr;
  161. let mut pool = Pool::new(num_cpus::get() as u32);
  162. let mut file_eggs = Vec::new();
  163. pool.scoped(|scoped| {
  164. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  165. let table = table.as_ref();
  166. for file in src {
  167. let file_eggs = file_eggs.clone();
  168. scoped.execute(move || {
  169. let mut errors = Vec::new();
  170. let mut xattrs = Vec::new();
  171. // There are three โ€œlevelsโ€ of extended attribute support:
  172. //
  173. // 1. If weโ€™re compiling without that feature, then
  174. // exa pretends all files have no attributes.
  175. // 2. If the feature is enabled and the --extended flag
  176. // has been specified, then display an @ in the
  177. // permissions column for files with attributes, the
  178. // names of all attributes and their lengths, and any
  179. // errors encountered when getting them.
  180. // 3. If the --extended flag *hasnโ€™t* been specified, then
  181. // display the @, but donโ€™t display anything else.
  182. //
  183. // For a while, exa took a stricter approach to (3):
  184. // if an error occurred while checking a fileโ€™s xattrs to
  185. // see if it should display the @, exa would display that
  186. // error even though the attributes werenโ€™t actually being
  187. // shown! This was confusing, as users were being shown
  188. // errors for something they didnโ€™t explicitly ask for,
  189. // and just cluttered up the output. So now errors arenโ€™t
  190. // printed unless the user passes --extended to signify
  191. // that they want to see them.
  192. if xattr::ENABLED {
  193. match file.path.attributes() {
  194. Ok(xs) => {
  195. xattrs.extend(xs);
  196. }
  197. Err(e) => {
  198. if self.opts.xattr {
  199. errors.push((e, None));
  200. }
  201. else {
  202. error!("Error looking up xattr for {:?}: {:#?}", file.path, e);
  203. }
  204. }
  205. }
  206. }
  207. let table_row = table.as_ref().map(|t| t.row_for_file(&file, !xattrs.is_empty()));
  208. if !self.opts.xattr {
  209. xattrs.clear();
  210. }
  211. let mut dir = None;
  212. if let Some(r) = self.recurse {
  213. if file.is_directory() && r.tree && !r.is_too_deep(depth.0) {
  214. match file.to_dir() {
  215. Ok(d) => { dir = Some(d); },
  216. Err(e) => { errors.push((e, None)) },
  217. }
  218. }
  219. };
  220. let egg = Egg { table_row, xattrs, errors, dir, file };
  221. file_eggs.lock().unwrap().push(egg);
  222. });
  223. }
  224. });
  225. self.filter.sort_files(&mut file_eggs);
  226. for (tree_params, egg) in depth.iterate_over(file_eggs.into_iter()) {
  227. let mut files = Vec::new();
  228. let mut errors = egg.errors;
  229. if let (Some(ref mut t), Some(ref row)) = (table.as_mut(), egg.table_row.as_ref()) {
  230. t.add_widths(row);
  231. }
  232. let row = Row {
  233. tree: tree_params,
  234. cells: egg.table_row,
  235. name: self.style.for_file(&egg.file, self.colours)
  236. .with_link_paths()
  237. .paint().promote(),
  238. };
  239. rows.push(row);
  240. if let Some(ref dir) = egg.dir {
  241. for file_to_add in dir.files(self.filter.dot_filter) {
  242. match file_to_add {
  243. Ok(f) => files.push(f),
  244. Err((path, e)) => errors.push((e, Some(path)))
  245. }
  246. }
  247. self.filter.filter_child_files(&mut files);
  248. if !files.is_empty() {
  249. for xattr in egg.xattrs {
  250. rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false)));
  251. }
  252. for (error, path) in errors {
  253. rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));
  254. }
  255. self.add_files_to_table(table, rows, &files, depth.deeper());
  256. continue;
  257. }
  258. }
  259. let count = egg.xattrs.len();
  260. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  261. rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1)));
  262. }
  263. let count = errors.len();
  264. for (index, (error, path)) in errors.into_iter().enumerate() {
  265. rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), index == count - 1), path));
  266. }
  267. }
  268. }
  269. pub fn render_header(&self, header: TableRow) -> Row {
  270. Row {
  271. tree: TreeParams::new(TreeDepth::root(), false),
  272. cells: Some(header),
  273. name: TextCell::paint_str(self.colours.header, "Name"),
  274. }
  275. }
  276. fn render_error(&self, error: &IOError, tree: TreeParams, path: Option<PathBuf>) -> Row {
  277. let error_message = match path {
  278. Some(path) => format!("<{}: {}>", path.display(), error),
  279. None => format!("<{}>", error),
  280. };
  281. let name = TextCell::paint(self.colours.broken_arrow, error_message);
  282. Row { cells: None, name, tree }
  283. }
  284. fn render_xattr(&self, xattr: Attribute, tree: TreeParams) -> Row {
  285. let name = TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size));
  286. Row { cells: None, name, tree }
  287. }
  288. pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row {
  289. Row { cells: Some(cells), name, tree }
  290. }
  291. pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) -> TableIter<'a> {
  292. TableIter {
  293. tree_trunk: TreeTrunk::default(),
  294. total_width: table.widths().total(),
  295. table: table,
  296. inner: rows.into_iter(),
  297. tree_style: self.colours.punctuation,
  298. }
  299. }
  300. pub fn iterate(&'a self, rows: Vec<Row>) -> Iter {
  301. Iter {
  302. tree_trunk: TreeTrunk::default(),
  303. inner: rows.into_iter(),
  304. tree_style: self.colours.punctuation,
  305. }
  306. }
  307. }
  308. pub struct Row {
  309. /// Vector of cells to display.
  310. ///
  311. /// Most of the rows will be used to display files' metadata, so this will
  312. /// almost always be `Some`, containing a vector of cells. It will only be
  313. /// `None` for a row displaying an attribute or error, neither of which
  314. /// have cells.
  315. pub cells: Option<TableRow>,
  316. /// This file's name, in coloured output. The name is treated separately
  317. /// from the other cells, as it never requires padding.
  318. pub name: TextCell,
  319. /// Information used to determine which symbols to display in a tree.
  320. pub tree: TreeParams,
  321. }
  322. pub struct TableIter<'a> {
  323. inner: VecIntoIter<Row>,
  324. table: Table<'a>,
  325. total_width: usize,
  326. tree_style: Style,
  327. tree_trunk: TreeTrunk,
  328. }
  329. impl<'a> Iterator for TableIter<'a> {
  330. type Item = TextCell;
  331. fn next(&mut self) -> Option<Self::Item> {
  332. self.inner.next().map(|row| {
  333. let mut cell =
  334. if let Some(cells) = row.cells {
  335. self.table.render(cells)
  336. }
  337. else {
  338. let mut cell = TextCell::default();
  339. cell.add_spaces(self.total_width);
  340. cell
  341. };
  342. for tree_part in self.tree_trunk.new_row(row.tree) {
  343. cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
  344. }
  345. // If any tree characters have been printed, then add an extra
  346. // space, which makes the output look much better.
  347. if !row.tree.is_at_root() {
  348. cell.add_spaces(1);
  349. }
  350. cell.append(row.name);
  351. cell
  352. })
  353. }
  354. }
  355. pub struct Iter {
  356. tree_trunk: TreeTrunk,
  357. tree_style: Style,
  358. inner: VecIntoIter<Row>,
  359. }
  360. impl Iterator for Iter {
  361. type Item = TextCell;
  362. fn next(&mut self) -> Option<Self::Item> {
  363. self.inner.next().map(|row| {
  364. let mut cell = TextCell::default();
  365. for tree_part in self.tree_trunk.new_row(row.tree) {
  366. cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
  367. }
  368. // If any tree characters have been printed, then add an extra
  369. // space, which makes the output look much better.
  370. if !row.tree.is_at_root() {
  371. cell.add_spaces(1);
  372. }
  373. cell.append(row.name);
  374. cell
  375. })
  376. }
  377. }