output.rs 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
use std::cmp::max;
use std::iter::{AdditiveIterator, repeat};

use column::Column;
use column::Alignment::Left;
use file::File;
use users::OSUsers;

use ansi_term::Style::Plain;
use ansi_term::strip_formatting;

12
#[derive(PartialEq, Show)]
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
pub enum View {
    Details(Vec<Column>, bool),
    Lines,
    Grid(bool, usize),
}

impl View {
    pub fn view(&self, files: Vec<File>) {
        match *self {
            View::Grid(across, width)       => grid_view(across, width, files),
            View::Details(ref cols, header) => details_view(cols, files, header),
            View::Lines                     => lines_view(files),
        }
    }
}

fn lines_view(files: Vec<File>) {
    for file in files.iter() {
        println!("{}", file.file_name());
    }
}

fn fit_into_grid(across: bool, console_width: usize, files: &Vec<File>) -> Option<(usize, Vec<usize>)> {
    // TODO: this function could almost certainly be optimised...
    // surely not *all* of the numbers of lines are worth searching through!

    // Instead of numbers of columns, try to find the fewest number of *lines*
    // that the output will fit in.
    for num_lines in range(1, files.len()) {

        // The number of columns is the number of files divided by the number
        // of lines, *rounded up*.
        let mut num_columns = files.len() / num_lines;
        if files.len() % num_lines != 0 {
            num_columns += 1;
        }

        // Early abort: if there are so many columns that the width of the
        // *column separators* is bigger than the width of the screen, then
        // don't even try to tabulate it.
        // This is actually a necessary check, because the width is stored as
        // a usize, and making it go negative makes it huge instead, but it
        // also serves as a speed-up.
        let separator_width = (num_columns - 1) * 2;
        if console_width < separator_width {
            continue;
        }

        // Remove the separator width from the available space.
        let adjusted_width = console_width - separator_width;

        // Find the width of each column by adding the lengths of the file
        // names in that column up.
        let mut column_widths: Vec<usize> = repeat(0).take(num_columns).collect();
        for (index, file) in files.iter().enumerate() {
            let index = if across {
                index % num_columns
            }
            else {
                index / num_lines
            };
            column_widths[index] = max(column_widths[index], file.name.len());
        }

        // If they all fit in the terminal, combined, then success!
        if column_widths.iter().map(|&x| x).sum() < adjusted_width {
            return Some((num_lines, column_widths));
        }
    }

    // If you get here you have really long file names.
    return None;
}

fn grid_view(across: bool, console_width: usize, files: Vec<File>) {
    if let Some((num_lines, widths)) = fit_into_grid(across, console_width, &files) {
        for y in range(0, num_lines) {
            for x in range(0, widths.len()) {
                let num = if across {
                    y * widths.len() + x
                }
                else {
                    y + num_lines * x
                };

                // Show whitespace in the place of trailing files
                if num >= files.len() {
                    continue;
                }

                let ref file = files[num];
                let styled_name = file.file_colour().paint(file.name.as_slice()).to_string();
                if x == widths.len() - 1 {
                    // The final column doesn't need to have trailing spaces
                    print!("{}", styled_name);
                }
                else {
                    assert!(widths[x] >= file.name.len());
                    print!("{}", Left.pad_string(&styled_name, widths[x] - file.name.len() + 2));
                }
            }
            print!("\n");
        }
    }
    else {
        // Drop down to lines view if the file names are too big for a grid
        lines_view(files);
    }
}

fn details_view(columns: &Vec<Column>, files: Vec<File>, header: bool) {
    // The output gets formatted into columns, which looks nicer. To
    // do this, we have to write the results into a table, instead of
    // displaying each file immediately, then calculating the maximum
    // width of each column based on the length of the results and
    // padding the fields during output.

    let mut cache = OSUsers::empty_cache();

    let mut table: Vec<Vec<String>> = files.iter()
        .map(|f| columns.iter().map(|c| f.display(c, &mut cache)).collect())
        .collect();

    if header {
        table.insert(0, columns.iter().map(|c| Plain.underline().paint(c.header()).to_string()).collect());
    }

    // Each column needs to have its invisible colour-formatting
    // characters stripped before it has its width calculated, or the
    // width will be incorrect and the columns won't line up properly.
    // This is fairly expensive to do (it uses a regex), so the
    // results are cached.

    let lengths: Vec<Vec<usize>> = table.iter()
        .map(|row| row.iter().map(|col| strip_formatting(col.as_slice()).len()).collect())
        .collect();

    let column_widths: Vec<usize> = range(0, columns.len())
        .map(|n| lengths.iter().map(|row| row[n]).max().unwrap_or(0))
        .collect();

    for (field_widths, row) in lengths.iter().zip(table.iter()) {
        for (num, column) in columns.iter().enumerate() {
            if num != 0 {
                print!(" ");  // Separator
            }

            if num == columns.len() - 1 {
                // The final column doesn't need to have trailing spaces
                print!("{}", row[num]);
            }
            else {
                let padding = column_widths[num] - field_widths[num];
                print!("{}", column.alignment().pad_string(&row[num], padding));
            }
        }
        print!("\n");
    }
}