Преглед изворни кода

refactor: port grid and grid-details to new uutils-term-grid

Terts Diepraam пре 2 година
родитељ
комит
dedb17df6e
7 измењених фајлова са 109 додато и 328 уклоњено
  1. 13 3
      Cargo.lock
  2. 2 1
      Cargo.toml
  3. 0 2
      src/main.rs
  4. 0 2
      src/options/view.rs
  5. 0 8
      src/output/details.rs
  6. 27 86
      src/output/grid.rs
  7. 67 226
      src/output/grid_details.rs

+ 13 - 3
Cargo.lock

@@ -32,6 +32,15 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
+[[package]]
+name = "ansi-width"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219e3ce6f2611d83b51ec2098a12702112c29e57203a6b0a0929b2cddb486608"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "ansi_colours"
 version = "1.2.2"
@@ -379,6 +388,7 @@ dependencies = [
 name = "eza"
 version = "0.18.5"
 dependencies = [
+ "ansi-width",
  "ansiterm",
  "chrono",
  "criterion",
@@ -1321,11 +1331,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "uutils_term_grid"
-version = "0.3.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b389452a568698688dda38802068378a16c15c4af9b153cdd99b65391292bbc7"
+checksum = "f89defb4adb4ba5703a57abc879f96ddd6263a444cacc446db90bf2617f141fb"
 dependencies = [
- "unicode-width",
+ "ansi-width",
 ]
 
 [[package]]

+ 2 - 1
Cargo.toml

@@ -71,6 +71,7 @@ name = "eza"
 
 
 [dependencies]
+ansi-width = "0.1.0"
 ansiterm = { version = "0.12.2", features = ["ansi_colours"] }
 chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
 glob = "0.3"
@@ -85,7 +86,7 @@ once_cell = "1.19.0"
 percent-encoding = "2.3.1"
 phf = { version = "0.11.2", features = ["macros"] }
 plist = { version = "1.6.0", default-features = false }
-uutils_term_grid = "0.3"
+uutils_term_grid = "0.6.0"
 terminal_size = "0.3.0"
 timeago = { version = "0.4.2", default-features = false }
 unicode-width = "0.1"

+ 0 - 2
src/main.rs

@@ -449,7 +449,6 @@ impl<'args> Exa<'args> {
             }
 
             (Mode::GridDetails(ref opts), Some(console_width)) => {
-                let grid = &opts.grid;
                 let details = &opts.details;
                 let row_threshold = opts.row_threshold;
 
@@ -463,7 +462,6 @@ impl<'args> Exa<'args> {
                     files,
                     theme,
                     file_style,
-                    grid,
                     details,
                     filter,
                     row_threshold,

+ 0 - 2
src/options/view.rs

@@ -64,10 +64,8 @@ impl Mode {
 
             if flag.is_some() && flag.unwrap().matches(&flags::GRID) {
                 let _ = matches.has(&flags::GRID)?;
-                let grid = grid::Options::deduce(matches)?;
                 let row_threshold = RowThreshold::deduce(vars)?;
                 let grid_details = grid_details::Options {
-                    grid,
                     details,
                     row_threshold,
                 };

+ 0 - 8
src/output/details.rs

@@ -427,14 +427,6 @@ impl<'a> Render<'a> {
         }
     }
 
-    pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row {
-        Row {
-            cells: Some(cells),
-            name,
-            tree,
-        }
-    }
-
     pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) -> TableIter<'a> {
         TableIter {
             tree_trunk: TreeTrunk::default(),

+ 27 - 86
src/output/grid.rs

@@ -1,26 +1,23 @@
 use std::io::{self, Write};
 
-use term_grid as tg;
+use term_grid::{Direction, Filling, Grid, GridOptions};
 
 use crate::fs::filter::FileFilter;
 use crate::fs::File;
-use crate::output::file_name::{Classify, Options as FileStyle};
-use crate::output::file_name::{EmbedHyperlinks, ShowIcons};
+use crate::output::file_name::Options as FileStyle;
 use crate::theme::Theme;
 
-use super::file_name::QuoteStyle;
-
 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
 pub struct Options {
     pub across: bool,
 }
 
 impl Options {
-    pub fn direction(self) -> tg::Direction {
+    pub fn direction(self) -> Direction {
         if self.across {
-            tg::Direction::LeftToRight
+            Direction::LeftToRight
         } else {
-            tg::Direction::TopToBottom
+            Direction::TopToBottom
         }
     }
 }
@@ -36,85 +33,29 @@ pub struct Render<'a> {
 
 impl<'a> Render<'a> {
     pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
-        let mut grid = tg::Grid::new(tg::GridOptions {
-            direction: self.opts.direction(),
-            filling: tg::Filling::Spaces(2),
-        });
-
-        grid.reserve(self.files.len());
-
         self.filter.sort_files(&mut self.files);
-        for file in &self.files {
-            let filename = self.file_style.for_file(file, self.theme);
 
-            // Calculate classification width
-            let classification_width = if matches!(
-                filename.options.classify,
-                Classify::AddFileIndicators | Classify::AutomaticAddFileIndicators
-            ) {
-                match filename.classify_char(file) {
-                    Some(s) => s.len(),
-                    None => 0,
-                }
-            } else {
-                0
-            };
-            let space_filename_offset = match self.file_style.quote_style {
-                QuoteStyle::QuoteSpaces if file.name.contains(' ') => 2,
-                QuoteStyle::NoQuotes => 0,
-                QuoteStyle::QuoteSpaces => 0, // Default case
-            };
-            let contents = filename.paint();
-            let width = match (
-                filename.options.embed_hyperlinks,
-                filename.options.show_icons,
-            ) {
-                (
-                    EmbedHyperlinks::On,
-                    ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing),
-                ) => {
-                    filename.bare_utf8_width()
-                        + classification_width
-                        + 1
-                        + (spacing as usize)
-                        + space_filename_offset
-                }
-                (EmbedHyperlinks::On, ShowIcons::Never) => {
-                    filename.bare_utf8_width() + classification_width + space_filename_offset
-                }
-                (
-                    EmbedHyperlinks::Off,
-                    ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing),
-                ) => {
-                    filename.bare_utf8_width()
-                        + classification_width
-                        + 1
-                        + (spacing as usize)
-                        + space_filename_offset
-                }
-                (EmbedHyperlinks::Off, _) => *contents.width(),
-            };
-
-            grid.add(tg::Cell {
-                contents: contents.strings().to_string(),
-                // with hyperlink escape sequences,
-                // the actual *contents.width() is larger than actually needed, so we take only the filename
-                width,
-            });
-        }
-
-        if let Some(display) = grid.fit_into_width(self.console_width) {
-            write!(w, "{display}")
-        } else {
-            // File names too long for a grid - drop down to just listing them!
-            // This isn’t *quite* the same as the lines view, which also
-            // displays full link paths.
-            for file in &self.files {
-                let name_cell = self.file_style.for_file(file, self.theme).paint();
-                writeln!(w, "{}", name_cell.strings())?;
-            }
-
-            Ok(())
-        }
+        let cells = self
+            .files
+            .iter()
+            .map(|file| {
+                self.file_style
+                    .for_file(file, self.theme)
+                    .paint()
+                    .strings()
+                    .to_string()
+            })
+            .collect();
+
+        let grid = Grid::new(
+            cells,
+            GridOptions {
+                filling: Filling::Spaces(2),
+                direction: self.opts.direction(),
+                width: self.console_width,
+            },
+        );
+
+        write!(w, "{grid}")
     }
 }

+ 67 - 226
src/output/grid_details.rs

@@ -2,29 +2,20 @@
 
 use std::io::{self, Write};
 
-use ansiterm::ANSIStrings;
-use term_grid as grid;
+use term_grid::{Direction, Filling, Grid, GridOptions};
 
 use crate::fs::feature::git::GitCache;
 use crate::fs::filter::FileFilter;
 use crate::fs::{Dir, File};
-use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::cell::TextCell;
 use crate::output::color_scale::ColorScaleInformation;
-use crate::output::details::{
-    Options as DetailsOptions, Render as DetailsRender, Row as DetailsRow,
-};
+use crate::output::details::{Options as DetailsOptions, Render as DetailsRender};
 use crate::output::file_name::Options as FileStyle;
-use crate::output::file_name::{EmbedHyperlinks, ShowIcons};
-use crate::output::grid::Options as GridOptions;
-use crate::output::table::{Options as TableOptions, Row as TableRow, Table};
-use crate::output::tree::{TreeDepth, TreeParams};
+use crate::output::table::{Options as TableOptions, Table};
 use crate::theme::Theme;
 
-use super::file_name::QuoteStyle;
-
 #[derive(PartialEq, Eq, Debug)]
 pub struct Options {
-    pub grid: GridOptions,
     pub details: DetailsOptions,
     pub row_threshold: RowThreshold,
 }
@@ -67,9 +58,6 @@ pub struct Render<'a> {
     /// How to format filenames.
     pub file_style: &'a FileStyle,
 
-    /// The grid part of the grid-details view.
-    pub grid: &'a GridOptions,
-
     /// The details part of the grid-details view.
     pub details: &'a DetailsOptions,
 
@@ -115,38 +103,10 @@ impl<'a> Render<'a> {
         };
     }
 
-    /// Create a Details render for when this grid-details render doesn’t fit
-    /// in the terminal (or something has gone wrong) and we have given up, or
-    /// when the user asked for a grid-details view but the terminal width is
-    /// not available, so we downgrade.
-    pub fn give_up(self) -> DetailsRender<'a> {
-        #[rustfmt::skip]
-        return DetailsRender {
-            dir:           self.dir,
-            files:         self.files,
-            theme:         self.theme,
-            file_style:    self.file_style,
-            opts:          self.details,
-            recurse:       None,
-            filter:        self.filter,
-            git_ignoring:  self.git_ignoring,
-            git:           self.git,
-            git_repos:     self.git_repos,
-        };
-    }
-
     // This doesn’t take an IgnoreCache even though the details one does
     // because grid-details has no tree view.
 
     pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
-        if let Some((grid, width)) = self.find_fitting_grid() {
-            write!(w, "{}", grid.fit_into_columns(width))
-        } else {
-            self.give_up().render(w)
-        }
-    }
-
-    pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> {
         let options = self
             .details
             .table
@@ -164,103 +124,79 @@ impl<'a> Render<'a> {
             None,
         );
 
-        let (first_table, _) = self.make_table(options, &drender);
+        let mut table = self.make_table(options);
 
-        let rows = self
+        // It is important to collect all these rows _before_ turning them into
+        // cells, because the width calculations need to consider all rows
+        // before each row is turned into a string.
+        let rows: Vec<_> = self
             .files
             .iter()
             .map(|file| {
-                first_table.row_for_file(file, drender.show_xattr_hint(file), color_scale_info)
+                let row = table.row_for_file(file, drender.show_xattr_hint(file), color_scale_info);
+                table.add_widths(&row);
+                row
             })
-            .collect::<Vec<_>>();
+            .collect();
 
-        let file_names = self
-            .files
-            .iter()
-            .map(|file| {
-                let filename = self.file_style.for_file(file, self.theme);
-                let contents = filename.paint();
-                let space_filename_offset = match self.file_style.quote_style {
-                    QuoteStyle::QuoteSpaces if file.name.contains(' ') => 2,
-                    QuoteStyle::NoQuotes => 0,
-                    QuoteStyle::QuoteSpaces => 0, // Default case
-                };
-                let width = match (
-                    filename.options.embed_hyperlinks,
-                    filename.options.show_icons,
-                ) {
-                    (EmbedHyperlinks::On, ShowIcons::Automatic(spacing)) => {
-                        filename.bare_utf8_width() + 1 + (spacing as usize) + space_filename_offset
-                    }
-                    (EmbedHyperlinks::On, ShowIcons::Always(spacing)) => {
-                        filename.bare_utf8_width() + 1 + (spacing as usize) + space_filename_offset
-                    }
-                    (EmbedHyperlinks::On, ShowIcons::Never) => {
-                        filename.bare_utf8_width() + space_filename_offset
-                    }
-                    (EmbedHyperlinks::Off, _) => *contents.width(),
-                };
-
-                TextCell {
-                    contents,
-                    // with hyperlink escape sequences,
-                    // the actual *contents.width() is larger than actually needed, so we take only the filename
-                    width: DisplayWidth::from(width),
-                }
-            })
-            .collect::<Vec<_>>();
-
-        let mut last_working_grid = self.make_grid(1, options, &file_names, rows.clone(), &drender);
-
-        if file_names.len() == 1 {
-            return Some((last_working_grid, 1));
-        }
-
-        // If we can’t fit everything in a grid 100 columns wide, then
-        // something has gone seriously awry
-        for column_count in 2..100 {
-            let grid = self.make_grid(column_count, options, &file_names, rows.clone(), &drender);
-
-            let the_grid_fits = {
-                let d = grid.fit_into_columns(column_count);
-                d.width() <= self.console_width
-            };
-
-            if the_grid_fits {
-                last_working_grid = grid;
-            }
-
-            if !the_grid_fits || column_count == file_names.len() {
-                let last_column_count = if the_grid_fits {
-                    column_count
+        let cells = rows
+            .into_iter()
+            .zip(self.files)
+            .map(|(row, file)| {
+                let filename = self
+                    .file_style
+                    .for_file(&file, self.theme)
+                    .paint()
+                    .strings()
+                    .to_string();
+                let details = table.render(row).strings().to_string();
+
+                // This bit fixes a strange corner case. If there is a header,
+                // then "Name" will be added to the header row. That means that
+                // the filename column, should be at least 4 characters wide.
+                // Therefore we pad the filenames with some spaces. We have to
+                // use ansi_width here, because the filename might contain some
+                // styling.
+                let padding = " ".repeat(if self.details.header {
+                    4usize.saturating_sub(ansi_width::ansi_width(&filename))
                 } else {
-                    column_count - 1
-                };
-                // If we’ve figured out how many columns can fit in the user’s terminal,
-                // and it turns out there aren’t enough rows to make it worthwhile
-                // (according to EZA_GRID_ROWS), then just resort to the lines view.
-                if let RowThreshold::MinimumRows(thresh) = self.row_threshold {
-                    if last_working_grid
-                        .fit_into_columns(last_column_count)
-                        .row_count()
-                        < thresh
-                    {
-                        return None;
-                    }
-                }
+                    0
+                });
+
+                format!("{details} {filename}{padding}")
+            })
+            .collect();
+
+        let grid = Grid::new(
+            cells,
+            GridOptions {
+                filling: Filling::Spaces(4),
+                direction: Direction::TopToBottom,
+                width: self.console_width,
+            },
+        );
 
-                return Some((last_working_grid, last_column_count));
+        if self.details.header {
+            let row = table.header_row();
+            let name = TextCell::paint_str(self.theme.ui.header, "Name")
+                .strings()
+                .to_string();
+            let s = table.render(row).strings().to_string();
+            let combined_header = format!("{s} {name}");
+            let header_width = ansi_width::ansi_width(&combined_header);
+            for column_width in grid.column_widths() {
+                let padding = " ".repeat((column_width + 4).saturating_sub(header_width));
+                write!(w, "{combined_header}{padding}")?;
             }
+            writeln!(w)?;
         }
 
-        None
+        write!(w, "{grid}")?;
+
+        Ok(())
     }
 
-    fn make_table(
-        &mut self,
-        options: &'a TableOptions,
-        drender: &DetailsRender<'_>,
-    ) -> (Table<'a>, Vec<DetailsRow>) {
+    fn make_table(&mut self, options: &'a TableOptions) -> Table<'a> {
         match (self.git, self.dir) {
             (Some(g), Some(d)) => {
                 if !g.has_anything_for(&d.path) {
@@ -276,109 +212,14 @@ impl<'a> Render<'a> {
         }
 
         let mut table = Table::new(options, self.git, self.theme, self.git_repos);
-        let mut rows = Vec::new();
 
+        // The header row will be printed separately, but it should be
+        // considered for the width calculations.
         if self.details.header {
             let row = table.header_row();
             table.add_widths(&row);
-            rows.push(drender.render_header(row));
         }
 
-        (table, rows)
+        table
     }
-
-    fn make_grid(
-        &mut self,
-        column_count: usize,
-        options: &'a TableOptions,
-        file_names: &[TextCell],
-        rows: Vec<TableRow>,
-        drender: &DetailsRender<'_>,
-    ) -> grid::Grid {
-        let mut tables = Vec::new();
-        for _ in 0..column_count {
-            tables.push(self.make_table(options, drender));
-        }
-
-        let mut num_cells = rows.len();
-        if self.details.header {
-            num_cells += column_count;
-        }
-
-        let original_height = divide_rounding_up(rows.len(), column_count);
-        let height = divide_rounding_up(num_cells, column_count);
-
-        for (i, (file_name, row)) in file_names.iter().zip(rows).enumerate() {
-            let index = if self.grid.across {
-                i % column_count
-            } else {
-                i / original_height
-            };
-
-            let (ref mut table, ref mut rows) = tables[index];
-            table.add_widths(&row);
-            let details_row = drender.render_file(
-                row,
-                file_name.clone(),
-                TreeParams::new(TreeDepth::root(), false),
-            );
-            rows.push(details_row);
-        }
-
-        let columns = tables
-            .into_iter()
-            .map(|(table, details_rows)| {
-                drender
-                    .iterate_with_table(table, details_rows)
-                    .collect::<Vec<_>>()
-            })
-            .collect::<Vec<_>>();
-
-        let direction = if self.grid.across {
-            grid::Direction::LeftToRight
-        } else {
-            grid::Direction::TopToBottom
-        };
-
-        let filling = grid::Filling::Spaces(4);
-        let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });
-
-        if self.grid.across {
-            for row in 0..height {
-                for column in &columns {
-                    if row < column.len() {
-                        let cell = grid::Cell {
-                            contents: ANSIStrings(&column[row].contents).to_string(),
-                            width: *column[row].width,
-                        };
-
-                        grid.add(cell);
-                    }
-                }
-            }
-        } else {
-            for column in &columns {
-                for cell in column {
-                    let cell = grid::Cell {
-                        contents: ANSIStrings(&cell.contents).to_string(),
-                        width: *cell.width,
-                    };
-
-                    grid.add(cell);
-                }
-            }
-        }
-
-        grid
-    }
-}
-
-fn divide_rounding_up(a: usize, b: usize) -> usize {
-    let mut result = a / b;
-
-    if a % b != 0 {
-        result += 1;
-    }
-
-    result
 }