2018-12-13 15:50:47 +00:00
|
|
|
|
use color::ColoredString;
|
2018-12-08 13:00:42 +00:00
|
|
|
|
use flags::Flags;
|
2018-12-13 07:03:49 +00:00
|
|
|
|
use std::io::Write;
|
2018-12-02 16:22:51 +00:00
|
|
|
|
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
|
|
|
|
use terminal_size::terminal_size;
|
2018-12-19 04:50:55 +00:00
|
|
|
|
use unicode_width::UnicodeWidthStr;
|
2018-12-02 16:22:51 +00:00
|
|
|
|
|
2018-12-13 15:50:47 +00:00
|
|
|
|
const EDGE: &str = "\u{251c}\u{2500}\u{2500}"; // "├──"
|
|
|
|
|
const LINE: &str = "\u{2502} "; // "├ "
|
|
|
|
|
const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──"
|
2018-12-04 13:54:56 +00:00
|
|
|
|
|
2018-12-05 19:54:17 +00:00
|
|
|
|
pub struct Display {
|
2018-12-08 13:00:42 +00:00
|
|
|
|
flags: Flags,
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-05 19:54:17 +00:00
|
|
|
|
impl Display {
|
2018-12-13 15:50:47 +00:00
|
|
|
|
pub fn new(flags: Flags) -> Self {
|
|
|
|
|
Self { flags }
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn print_outputs(&self, outputs: Vec<String>) {
|
2018-12-08 13:00:42 +00:00
|
|
|
|
if self.flags.display_long || self.flags.display_online {
|
2018-12-02 16:22:51 +00:00
|
|
|
|
self.print_one_per_line(&outputs);
|
|
|
|
|
} else {
|
|
|
|
|
self.print_grid(outputs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn print_grid(&self, outputs: Vec<String>) {
|
|
|
|
|
let term_width = match terminal_size() {
|
|
|
|
|
Some((w, _)) => w.0 as usize,
|
|
|
|
|
None => panic!("failed to retrieve terminal size"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut grid = Grid::new(GridOptions {
|
|
|
|
|
filling: Filling::Spaces(2),
|
2018-12-07 18:44:26 +00:00
|
|
|
|
direction: Direction::TopToBottom,
|
2018-12-02 16:22:51 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for output in outputs {
|
|
|
|
|
grid.add(Cell {
|
|
|
|
|
width: self.get_visible_width(&output),
|
|
|
|
|
contents: output,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-05 03:44:01 +00:00
|
|
|
|
if let Some(gridded_output) = grid.fit_into_width(term_width) {
|
2018-12-13 04:38:45 +00:00
|
|
|
|
print!("{}", gridded_output);
|
2018-12-13 15:50:47 +00:00
|
|
|
|
std::io::stdout().flush().expect("Could not flush stdout");
|
2018-12-05 03:44:01 +00:00
|
|
|
|
} else {
|
|
|
|
|
//does not fit into grid, usually because (some) filename(s)
|
|
|
|
|
//are longer or almost as long as term_width
|
|
|
|
|
//print line by line instead!
|
|
|
|
|
let lined_output = grid.fit_into_columns(1);
|
2018-12-13 04:38:45 +00:00
|
|
|
|
print!("{}", lined_output);
|
2018-12-13 15:50:47 +00:00
|
|
|
|
std::io::stdout().flush().expect("Could not flush stdout");
|
2018-12-05 03:44:01 +00:00
|
|
|
|
}
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-13 15:50:47 +00:00
|
|
|
|
pub fn print_tree_row(&self, output: &ColoredString, depth: usize, last: bool) {
|
2018-12-04 13:54:56 +00:00
|
|
|
|
let mut res = String::new();
|
|
|
|
|
|
|
|
|
|
for _ in 0..depth {
|
|
|
|
|
res += LINE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if last {
|
|
|
|
|
res += EDGE;
|
|
|
|
|
} else {
|
|
|
|
|
res += CORNER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res += " ";
|
|
|
|
|
res += &output;
|
|
|
|
|
|
|
|
|
|
println!("{}", res);
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-02 17:12:53 +00:00
|
|
|
|
fn print_one_per_line(&self, outputs: &[String]) {
|
2018-12-02 17:16:02 +00:00
|
|
|
|
let mut res = String::new();
|
2018-12-02 16:22:51 +00:00
|
|
|
|
for output in outputs {
|
2018-12-02 17:16:02 +00:00
|
|
|
|
res += output;
|
|
|
|
|
res += "\n";
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
2018-12-02 17:16:02 +00:00
|
|
|
|
|
2018-12-13 04:38:45 +00:00
|
|
|
|
print!("{}", res);
|
2018-12-13 15:50:47 +00:00
|
|
|
|
std::io::stdout().flush().expect("Could not flush stdout");
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-02 17:12:53 +00:00
|
|
|
|
fn get_visible_width(&self, input: &str) -> usize {
|
2018-12-02 16:22:51 +00:00
|
|
|
|
let mut nb_invisible_char = 0;
|
|
|
|
|
|
2018-12-20 13:09:01 +00:00
|
|
|
|
for (idx, _) in input.match_indices("\u{1b}[38;5;" /* "\e[38;5;" */) {
|
|
|
|
|
let color_code = input.chars().skip(idx + 7);
|
2018-12-02 16:22:51 +00:00
|
|
|
|
let mut code_size = 0;
|
|
|
|
|
color_code
|
|
|
|
|
.skip_while(|x| {
|
|
|
|
|
code_size += 1;
|
|
|
|
|
char::is_numeric(*x)
|
2018-12-12 10:17:32 +00:00
|
|
|
|
})
|
|
|
|
|
.count();
|
2018-12-20 13:09:01 +00:00
|
|
|
|
nb_invisible_char += 6 + code_size; /* "\e[38;5;" + color number + "m" */
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-20 13:09:01 +00:00
|
|
|
|
if nb_invisible_char > 0 {
|
|
|
|
|
// If no color have been set, the is no reset character.
|
|
|
|
|
nb_invisible_char += 3; /* "[0m" */
|
|
|
|
|
}
|
2018-12-02 16:22:51 +00:00
|
|
|
|
|
2018-12-19 04:50:55 +00:00
|
|
|
|
UnicodeWidthStr::width(input) - nb_invisible_char
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-20 02:03:58 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use color;
|
|
|
|
|
use color::Colors;
|
|
|
|
|
use icon;
|
|
|
|
|
use icon::Icons;
|
|
|
|
|
use meta::{FileType, Name};
|
2018-12-20 02:51:44 +00:00
|
|
|
|
use std::path::Path;
|
2018-12-20 02:03:58 +00:00
|
|
|
|
|
|
|
|
|
#[test]
|
2018-12-20 13:09:01 +00:00
|
|
|
|
fn test_display_get_visible_width_without_icons() {
|
2018-12-20 02:03:58 +00:00
|
|
|
|
let display = Display::new(Flags::default());
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
("Hello,world!", 22),
|
|
|
|
|
("ASCII1234-_", 11),
|
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
|
|
|
|
let name = Name::new(&path, FileType::File);
|
2018-12-20 02:51:44 +00:00
|
|
|
|
let output = name.render(
|
2018-12-20 13:09:01 +00:00
|
|
|
|
&Colors::new(color::Theme::NoColor),
|
|
|
|
|
&Icons::new(icon::Theme::NoIcon),
|
2018-12-20 02:51:44 +00:00
|
|
|
|
);
|
2018-12-20 02:03:58 +00:00
|
|
|
|
assert_eq!(display.get_visible_width(&output), *l);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-20 13:09:01 +00:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_with_icons() {
|
|
|
|
|
let display = Display::new(Flags::default());
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
// Add 3 characters for the icons.
|
|
|
|
|
("Hello,world!", 25),
|
|
|
|
|
("ASCII1234-_", 14),
|
|
|
|
|
("制作样本。", 13),
|
|
|
|
|
("日本語", 9),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 29),
|
|
|
|
|
("👩🐩", 7),
|
|
|
|
|
("🔬", 5),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
|
|
|
|
let name = Name::new(&path, FileType::File);
|
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
|
|
|
|
&Colors::new(color::Theme::NoColor),
|
|
|
|
|
&Icons::new(icon::Theme::Default),
|
|
|
|
|
)
|
|
|
|
|
.to_string();
|
|
|
|
|
assert_eq!(display.get_visible_width(&output), *l);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_with_colors() {
|
|
|
|
|
let display = Display::new(Flags::default());
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
("Hello,world!", 22),
|
|
|
|
|
("ASCII1234-_", 11),
|
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
|
|
|
|
let name = Name::new(&path, FileType::File);
|
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
|
|
|
|
&Colors::new(color::Theme::Default),
|
|
|
|
|
&Icons::new(icon::Theme::NoIcon),
|
|
|
|
|
)
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
// check if the color is present.
|
|
|
|
|
assert_eq!(true, output.starts_with("\u{1b}[38;5;"));
|
|
|
|
|
assert_eq!(true, output.ends_with("[0m"));
|
|
|
|
|
|
|
|
|
|
assert_eq!(display.get_visible_width(&output), *l);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-20 02:03:58 +00:00
|
|
|
|
}
|