lsd/src/display.rs

221 lines
6.5 KiB
Rust
Raw Normal View History

2018-12-13 15:50:47 +00:00
use color::ColoredString;
use flags::Flags;
2019-01-12 15:17:40 +00:00
use std::io::{self, Write};
2018-12-02 16:22:51 +00:00
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::terminal_size;
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
pub struct Display {
flags: Flags,
2018-12-02 16:22:51 +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>) {
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) {
2019-01-12 15:17:40 +00:00
self.print_output(&gridded_output.to_string());
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);
2019-01-12 15:17:40 +00:00
self.print_output(&lined_output.to_string());
2018-12-05 03:44:01 +00:00
}
2018-12-02 16:22:51 +00:00
}
2019-01-12 15:17:40 +00:00
pub fn print_output(&self, output: &str) {
let stdout = io::stdout();
let mut handle = stdout.lock();
if let Err(err) = handle.write_all(output.as_bytes()) {
if err.kind() != io::ErrorKind::Interrupted {
io::stderr().write_all(err.to_string().as_bytes()).unwrap();
std::process::exit(1);
}
};
// Do not check th
if let Err(err) = handle.flush() {
match err.kind() {
io::ErrorKind::Interrupted | io::ErrorKind::BrokenPipe => std::process::exit(0),
_ => {
io::stderr().write_all(err.to_string().as_bytes()).unwrap();
std::process::exit(1);
}
};
}
}
pub fn print_tree_row(&self, output: &ColoredString, depth: usize, last: bool) -> String {
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;
2019-01-12 15:17:40 +00:00
res += "\n";
2018-12-04 13:54:56 +00:00
2019-01-12 15:17:40 +00:00
res
2018-12-04 13:54:56 +00:00
}
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
2019-01-12 15:17:40 +00:00
self.print_output(&res);
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;
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();
nb_invisible_char += 6 + code_size; /* "\e[38;5;" + color number + "m" */
2018-12-02 16:22:51 +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
UnicodeWidthStr::width(input) - nb_invisible_char
2018-12-02 16:22:51 +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;
#[test]
fn test_display_get_visible_width_without_icons() {
let display = Display::new(Flags::default());
for (s, l) in &[
(",!", 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(
&Colors::new(color::Theme::NoColor),
&Icons::new(icon::Theme::NoIcon),
2018-12-20 02:51:44 +00:00
);
assert_eq!(display.get_visible_width(&output), *l);
}
}
#[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.
(",!", 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::Fancy),
)
.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 &[
(",!", 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);
}
}
}