grid.rs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. use std::io::{self, Write};
  2. use term_grid as tg;
  3. use crate::fs::filter::FileFilter;
  4. use crate::fs::File;
  5. use crate::output::file_name::{Classify, Options as FileStyle};
  6. use crate::output::file_name::{EmbedHyperlinks, ShowIcons};
  7. use crate::theme::Theme;
  8. use super::file_name::QuoteStyle;
  9. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  10. pub struct Options {
  11. pub across: bool,
  12. }
  13. impl Options {
  14. pub fn direction(self) -> tg::Direction {
  15. if self.across {
  16. tg::Direction::LeftToRight
  17. } else {
  18. tg::Direction::TopToBottom
  19. }
  20. }
  21. }
  22. pub struct Render<'a> {
  23. pub files: Vec<File<'a>>,
  24. pub theme: &'a Theme,
  25. pub file_style: &'a FileStyle,
  26. pub opts: &'a Options,
  27. pub console_width: usize,
  28. pub filter: &'a FileFilter,
  29. }
  30. impl<'a> Render<'a> {
  31. pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
  32. let mut grid = tg::Grid::new(tg::GridOptions {
  33. direction: self.opts.direction(),
  34. filling: tg::Filling::Spaces(2),
  35. });
  36. grid.reserve(self.files.len());
  37. self.filter.sort_files(&mut self.files);
  38. for file in &self.files {
  39. let filename = self.file_style.for_file(file, self.theme);
  40. // Calculate classification width
  41. let classification_width = if matches!(
  42. filename.options.classify,
  43. Classify::AddFileIndicators | Classify::AutomaticAddFileIndicators
  44. ) {
  45. match filename.classify_char(file) {
  46. Some(s) => s.len(),
  47. None => 0,
  48. }
  49. } else {
  50. 0
  51. };
  52. let space_filename_offset = match self.file_style.quote_style {
  53. QuoteStyle::QuoteSpaces if file.name.contains(' ') => 2,
  54. QuoteStyle::NoQuotes => 0,
  55. QuoteStyle::QuoteSpaces => 0, // Default case
  56. };
  57. let contents = filename.paint();
  58. let width = match (
  59. filename.options.embed_hyperlinks,
  60. filename.options.show_icons,
  61. ) {
  62. (
  63. EmbedHyperlinks::On,
  64. ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing),
  65. ) => {
  66. filename.bare_utf8_width()
  67. + classification_width
  68. + 1
  69. + (spacing as usize)
  70. + space_filename_offset
  71. }
  72. (EmbedHyperlinks::On, ShowIcons::Never) => {
  73. filename.bare_utf8_width() + classification_width + space_filename_offset
  74. }
  75. (
  76. EmbedHyperlinks::Off,
  77. ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing),
  78. ) => {
  79. filename.bare_utf8_width()
  80. + classification_width
  81. + 1
  82. + (spacing as usize)
  83. + space_filename_offset
  84. }
  85. (EmbedHyperlinks::Off, _) => *contents.width(),
  86. };
  87. grid.add(tg::Cell {
  88. contents: contents.strings().to_string(),
  89. // with hyperlink escape sequences,
  90. // the actual *contents.width() is larger than actually needed, so we take only the filename
  91. width,
  92. });
  93. }
  94. if let Some(display) = grid.fit_into_width(self.console_width) {
  95. write!(w, "{display}")
  96. } else {
  97. // File names too long for a grid - drop down to just listing them!
  98. // This isn’t *quite* the same as the lines view, which also
  99. // displays full link paths.
  100. for file in &self.files {
  101. let name_cell = self.file_style.for_file(file, self.theme).paint();
  102. writeln!(w, "{}", name_cell.strings())?;
  103. }
  104. Ok(())
  105. }
  106. }
  107. }