2021-07-06 06:21:22 +00:00
|
|
|
|
use crate::color::{Colors, Elem};
|
2022-03-27 05:11:02 +00:00
|
|
|
|
use crate::flags::{Block, Display, Flags, HyperlinkOption, Layout};
|
2019-02-09 10:41:42 +00:00
|
|
|
|
use crate::icon::Icons;
|
2020-02-02 14:32:40 +00:00
|
|
|
|
use crate::meta::name::DisplayOption;
|
2020-02-04 04:32:36 +00:00
|
|
|
|
use crate::meta::{FileType, Meta};
|
2019-10-23 15:17:38 +00:00
|
|
|
|
use std::collections::HashMap;
|
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}"; // "├──"
|
2021-02-15 16:38:03 +00:00
|
|
|
|
const LINE: &str = "\u{2502} "; // "│ "
|
2018-12-13 15:50:47 +00:00
|
|
|
|
const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──"
|
2019-01-23 17:02:36 +00:00
|
|
|
|
const BLANK: &str = " ";
|
2018-12-04 13:54:56 +00:00
|
|
|
|
|
2019-10-23 15:26:39 +00:00
|
|
|
|
pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let term_width = terminal_size().map(|(w, _)| w.0 as usize);
|
2019-01-20 10:22:14 +00:00
|
|
|
|
|
2020-02-04 04:32:36 +00:00
|
|
|
|
inner_display_grid(
|
2020-02-04 12:17:02 +00:00
|
|
|
|
&DisplayOption::None,
|
2020-02-04 04:32:36 +00:00
|
|
|
|
metas,
|
2021-08-17 13:17:21 +00:00
|
|
|
|
flags,
|
2020-02-04 04:32:36 +00:00
|
|
|
|
colors,
|
|
|
|
|
icons,
|
|
|
|
|
0,
|
|
|
|
|
term_width,
|
|
|
|
|
)
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 15:26:39 +00:00
|
|
|
|
pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
|
2021-02-18 08:37:52 +00:00
|
|
|
|
let mut grid = Grid::new(GridOptions {
|
|
|
|
|
filling: Filling::Spaces(1),
|
|
|
|
|
direction: Direction::LeftToRight,
|
|
|
|
|
});
|
|
|
|
|
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let padding_rules = get_padding_rules(metas, flags);
|
2021-02-19 15:04:53 +00:00
|
|
|
|
let mut index = 0;
|
|
|
|
|
for (i, block) in flags.blocks.0.iter().enumerate() {
|
|
|
|
|
if let Block::Name = block {
|
|
|
|
|
index = i;
|
2021-03-12 10:43:30 +00:00
|
|
|
|
break;
|
2021-02-19 15:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-17 13:17:21 +00:00
|
|
|
|
for cell in inner_display_tree(metas, flags, colors, icons, (0, ""), &padding_rules, index) {
|
2021-02-18 08:37:52 +00:00
|
|
|
|
grid.add(cell);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grid.fit_into_columns(flags.blocks.0.len()).to_string()
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 15:18:42 +00:00
|
|
|
|
fn inner_display_grid(
|
2020-02-04 04:32:36 +00:00
|
|
|
|
display_option: &DisplayOption,
|
2019-10-23 15:26:39 +00:00
|
|
|
|
metas: &[Meta],
|
2019-03-30 10:58:36 +00:00
|
|
|
|
flags: &Flags,
|
2019-01-20 10:22:14 +00:00
|
|
|
|
colors: &Colors,
|
|
|
|
|
icons: &Icons,
|
|
|
|
|
depth: usize,
|
2019-10-24 15:18:42 +00:00
|
|
|
|
term_width: Option<usize>,
|
2019-01-20 10:22:14 +00:00
|
|
|
|
) -> String {
|
|
|
|
|
let mut output = String::new();
|
|
|
|
|
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let padding_rules = get_padding_rules(metas, flags);
|
2019-12-05 13:10:42 +00:00
|
|
|
|
let mut grid = match flags.layout {
|
|
|
|
|
Layout::OneLine => Grid::new(GridOptions {
|
|
|
|
|
filling: Filling::Spaces(1),
|
|
|
|
|
direction: Direction::LeftToRight,
|
|
|
|
|
}),
|
|
|
|
|
_ => Grid::new(GridOptions {
|
|
|
|
|
filling: Filling::Spaces(2),
|
2022-03-27 13:11:44 +00:00
|
|
|
|
direction: Direction::TopToBottom,
|
2019-12-05 13:10:42 +00:00
|
|
|
|
}),
|
|
|
|
|
};
|
2018-12-02 16:22:51 +00:00
|
|
|
|
|
2019-05-24 13:30:36 +00:00
|
|
|
|
// The first iteration (depth == 0) corresponds to the inputs given by the
|
|
|
|
|
// user. We defer displaying directories given by the user unless we've been
|
|
|
|
|
// asked to display the directory itself (rather than its contents).
|
2020-11-21 15:40:24 +00:00
|
|
|
|
let skip_dirs = (depth == 0) && (flags.display != Display::DirectoryOnly);
|
2019-05-24 13:30:36 +00:00
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
// print the files first.
|
2020-02-04 04:49:04 +00:00
|
|
|
|
for meta in metas {
|
2019-05-24 13:30:36 +00:00
|
|
|
|
// Maybe skip showing the directory meta now; show its contents later.
|
2020-08-21 18:43:03 +00:00
|
|
|
|
if skip_dirs
|
2021-03-13 03:35:42 +00:00
|
|
|
|
&& (matches!(meta.file_type, FileType::Directory { .. })
|
2020-08-21 19:16:59 +00:00
|
|
|
|
|| (matches!(meta.file_type, FileType::SymLink { is_dir: true })
|
|
|
|
|
&& flags.layout != Layout::OneLine))
|
2020-08-21 18:43:03 +00:00
|
|
|
|
{
|
2019-05-24 13:30:36 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-04 04:32:36 +00:00
|
|
|
|
let blocks = get_output(
|
2021-08-17 13:17:21 +00:00
|
|
|
|
meta,
|
|
|
|
|
colors,
|
|
|
|
|
icons,
|
|
|
|
|
flags,
|
|
|
|
|
display_option,
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&padding_rules,
|
2021-02-19 15:04:53 +00:00
|
|
|
|
(0, ""),
|
2020-02-04 04:32:36 +00:00
|
|
|
|
);
|
2019-10-24 13:31:14 +00:00
|
|
|
|
|
|
|
|
|
for block in blocks {
|
|
|
|
|
let block_str = block.to_string();
|
|
|
|
|
|
|
|
|
|
grid.add(Cell {
|
2022-03-27 05:11:02 +00:00
|
|
|
|
width: get_visible_width(
|
|
|
|
|
&block_str,
|
|
|
|
|
matches!(flags.hyperlink, HyperlinkOption::Always),
|
|
|
|
|
),
|
2019-10-24 13:31:14 +00:00
|
|
|
|
contents: block_str,
|
|
|
|
|
});
|
|
|
|
|
}
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 15:18:42 +00:00
|
|
|
|
if flags.layout == Layout::Grid {
|
|
|
|
|
if let Some(tw) = term_width {
|
|
|
|
|
if let Some(gridded_output) = grid.fit_into_width(tw) {
|
|
|
|
|
output += &gridded_output.to_string();
|
|
|
|
|
} 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!
|
|
|
|
|
output += &grid.fit_into_columns(1).to_string();
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
2019-02-12 12:24:21 +00:00
|
|
|
|
} else {
|
|
|
|
|
output += &grid.fit_into_columns(1).to_string();
|
|
|
|
|
}
|
2019-01-20 10:22:14 +00:00
|
|
|
|
} else {
|
2020-09-24 18:09:26 +00:00
|
|
|
|
output += &grid.fit_into_columns(flags.blocks.0.len()).to_string();
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let should_display_folder_path = should_display_folder_path(depth, metas, flags);
|
2019-01-12 15:17:40 +00:00
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
// print the folder content
|
|
|
|
|
for meta in metas {
|
|
|
|
|
if meta.content.is_some() {
|
|
|
|
|
if should_display_folder_path {
|
2021-08-17 13:17:21 +00:00
|
|
|
|
output += &display_folder_path(meta);
|
2019-01-12 15:17:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-04 13:20:19 +00:00
|
|
|
|
let display_option = DisplayOption::Relative {
|
|
|
|
|
base_path: &meta.path,
|
2020-02-04 04:32:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
output += &inner_display_grid(
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&display_option,
|
2019-10-24 15:18:42 +00:00
|
|
|
|
meta.content.as_ref().unwrap(),
|
2021-08-17 13:17:21 +00:00
|
|
|
|
flags,
|
2019-01-20 10:22:14 +00:00
|
|
|
|
colors,
|
|
|
|
|
icons,
|
|
|
|
|
depth + 1,
|
|
|
|
|
term_width,
|
|
|
|
|
);
|
2019-01-12 15:17:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn inner_display_tree(
|
2019-10-23 15:26:39 +00:00
|
|
|
|
metas: &[Meta],
|
2019-03-30 10:58:36 +00:00
|
|
|
|
flags: &Flags,
|
2019-01-20 10:22:14 +00:00
|
|
|
|
colors: &Colors,
|
|
|
|
|
icons: &Icons,
|
2021-02-19 15:04:53 +00:00
|
|
|
|
tree_depth_prefix: (usize, &str),
|
2021-02-18 10:15:12 +00:00
|
|
|
|
padding_rules: &HashMap<Block, usize>,
|
2021-03-12 10:43:30 +00:00
|
|
|
|
tree_index: usize,
|
2021-02-18 08:37:52 +00:00
|
|
|
|
) -> Vec<Cell> {
|
|
|
|
|
let mut cells = Vec::new();
|
2019-01-20 10:22:14 +00:00
|
|
|
|
let last_idx = metas.len();
|
|
|
|
|
|
2021-02-15 16:38:03 +00:00
|
|
|
|
for (idx, meta) in metas.iter().enumerate() {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
let current_prefix = if tree_depth_prefix.0 > 0 {
|
2021-02-15 16:38:03 +00:00
|
|
|
|
if idx + 1 != last_idx {
|
|
|
|
|
// is last folder elem
|
2021-02-19 15:04:53 +00:00
|
|
|
|
format!("{}{} ", tree_depth_prefix.1, EDGE)
|
2021-02-15 16:38:03 +00:00
|
|
|
|
} else {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
format!("{}{} ", tree_depth_prefix.1, CORNER)
|
2021-02-15 16:38:03 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
tree_depth_prefix.1.to_string()
|
2021-02-15 16:38:03 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-02-04 04:32:36 +00:00
|
|
|
|
for block in get_output(
|
2021-08-17 13:17:21 +00:00
|
|
|
|
meta,
|
|
|
|
|
colors,
|
|
|
|
|
icons,
|
|
|
|
|
flags,
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&DisplayOption::FileName,
|
2021-08-17 13:17:21 +00:00
|
|
|
|
padding_rules,
|
2021-03-12 10:43:30 +00:00
|
|
|
|
(tree_index, ¤t_prefix),
|
2020-02-04 04:32:36 +00:00
|
|
|
|
) {
|
2019-10-24 15:55:05 +00:00
|
|
|
|
let block_str = block.to_string();
|
|
|
|
|
|
2021-02-18 08:37:52 +00:00
|
|
|
|
cells.push(Cell {
|
2022-03-27 05:11:02 +00:00
|
|
|
|
width: get_visible_width(
|
|
|
|
|
&block_str,
|
|
|
|
|
matches!(flags.hyperlink, HyperlinkOption::Always),
|
|
|
|
|
),
|
2019-10-24 15:55:05 +00:00
|
|
|
|
contents: block_str,
|
|
|
|
|
});
|
|
|
|
|
}
|
2018-12-04 13:54:56 +00:00
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
if meta.content.is_some() {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
let new_prefix = if tree_depth_prefix.0 > 0 {
|
2021-02-15 16:38:03 +00:00
|
|
|
|
if idx + 1 != last_idx {
|
|
|
|
|
// is last folder elem
|
2021-03-25 07:50:02 +00:00
|
|
|
|
format!("{}{} ", tree_depth_prefix.1, LINE)
|
2019-01-23 17:02:36 +00:00
|
|
|
|
} else {
|
2021-03-25 07:50:02 +00:00
|
|
|
|
format!("{}{} ", tree_depth_prefix.1, BLANK)
|
2019-01-23 17:02:36 +00:00
|
|
|
|
}
|
2021-02-15 16:38:03 +00:00
|
|
|
|
} else {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
tree_depth_prefix.1.to_string()
|
2021-02-15 16:38:03 +00:00
|
|
|
|
};
|
2019-01-23 17:02:36 +00:00
|
|
|
|
|
2021-02-18 08:37:52 +00:00
|
|
|
|
cells.extend(inner_display_tree(
|
2021-08-17 13:17:21 +00:00
|
|
|
|
meta.content.as_ref().unwrap(),
|
|
|
|
|
flags,
|
2019-01-23 17:02:36 +00:00
|
|
|
|
colors,
|
|
|
|
|
icons,
|
2021-02-19 15:04:53 +00:00
|
|
|
|
(tree_depth_prefix.0 + 1, &new_prefix),
|
2021-02-18 10:15:12 +00:00
|
|
|
|
padding_rules,
|
2021-03-12 10:43:30 +00:00
|
|
|
|
tree_index,
|
2021-02-18 08:37:52 +00:00
|
|
|
|
));
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
2018-12-04 13:54:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-18 08:37:52 +00:00
|
|
|
|
cells
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 19:16:59 +00:00
|
|
|
|
fn should_display_folder_path(depth: usize, metas: &[Meta], flags: &Flags) -> bool {
|
2019-01-20 10:22:14 +00:00
|
|
|
|
if depth > 0 {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
let folder_number = metas
|
|
|
|
|
.iter()
|
2020-08-21 18:43:03 +00:00
|
|
|
|
.filter(|x| {
|
|
|
|
|
matches!(x.file_type, FileType::Directory { .. })
|
2020-08-21 19:16:59 +00:00
|
|
|
|
|| (matches!(x.file_type, FileType::SymLink { is_dir: true })
|
|
|
|
|
&& flags.layout != Layout::OneLine)
|
2020-08-21 18:43:03 +00:00
|
|
|
|
})
|
2019-01-20 10:22:14 +00:00
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
folder_number > 1 || folder_number < metas.len()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn display_folder_path(meta: &Meta) -> String {
|
|
|
|
|
let mut output = String::new();
|
|
|
|
|
output.push('\n');
|
|
|
|
|
output += &meta.path.to_string_lossy();
|
|
|
|
|
output += ":\n";
|
|
|
|
|
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 13:31:14 +00:00
|
|
|
|
fn get_output<'a>(
|
|
|
|
|
meta: &'a Meta,
|
|
|
|
|
colors: &'a Colors,
|
|
|
|
|
icons: &'a Icons,
|
|
|
|
|
flags: &'a Flags,
|
2020-02-02 17:41:43 +00:00
|
|
|
|
display_option: &DisplayOption,
|
2019-10-23 15:17:38 +00:00
|
|
|
|
padding_rules: &HashMap<Block, usize>,
|
2021-02-19 15:04:53 +00:00
|
|
|
|
tree: (usize, &'a str),
|
2021-07-06 06:21:22 +00:00
|
|
|
|
) -> Vec<String> {
|
|
|
|
|
let mut strings: Vec<String> = Vec::new();
|
2021-02-19 15:04:53 +00:00
|
|
|
|
for (i, block) in flags.blocks.0.iter().enumerate() {
|
|
|
|
|
let mut block_vec = if Layout::Tree == flags.layout && tree.0 == i {
|
2021-07-06 06:21:22 +00:00
|
|
|
|
vec![colors.colorize(tree.1.to_string(), &Elem::TreeEdge)]
|
2021-02-19 15:04:53 +00:00
|
|
|
|
} else {
|
|
|
|
|
Vec::new()
|
|
|
|
|
};
|
|
|
|
|
|
2019-03-30 10:58:36 +00:00
|
|
|
|
match block {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
Block::INode => block_vec.push(meta.inode.render(colors)),
|
|
|
|
|
Block::Links => block_vec.push(meta.links.render(colors)),
|
2019-05-04 10:59:11 +00:00
|
|
|
|
Block::Permission => {
|
2021-02-19 15:04:53 +00:00
|
|
|
|
block_vec.extend(vec![
|
2019-10-24 13:31:14 +00:00
|
|
|
|
meta.file_type.render(colors),
|
2022-03-01 03:28:49 +00:00
|
|
|
|
meta.permissions.render(colors, flags),
|
2022-03-15 04:13:52 +00:00
|
|
|
|
meta.access_control.render_method(colors),
|
2021-02-19 15:04:53 +00:00
|
|
|
|
]);
|
2019-05-04 10:59:11 +00:00
|
|
|
|
}
|
2021-02-19 15:04:53 +00:00
|
|
|
|
Block::User => block_vec.push(meta.owner.render_user(colors)),
|
|
|
|
|
Block::Group => block_vec.push(meta.owner.render_group(colors)),
|
2022-03-25 20:33:37 +00:00
|
|
|
|
Block::Context => block_vec.push(meta.access_control.render_context(colors)),
|
2021-02-19 15:04:53 +00:00
|
|
|
|
Block::Size => {
|
|
|
|
|
let pad = if Layout::Tree == flags.layout && 0 == tree.0 && 0 == i {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(padding_rules[&Block::SizeValue])
|
|
|
|
|
};
|
2021-08-17 13:17:21 +00:00
|
|
|
|
block_vec.push(meta.size.render(colors, flags, pad))
|
2021-02-19 15:04:53 +00:00
|
|
|
|
}
|
|
|
|
|
Block::SizeValue => block_vec.push(meta.size.render_value(colors, flags)),
|
2021-08-17 13:17:21 +00:00
|
|
|
|
Block::Date => block_vec.push(meta.date.render(colors, flags)),
|
2019-05-18 13:49:29 +00:00
|
|
|
|
Block::Name => {
|
2021-03-12 10:43:30 +00:00
|
|
|
|
block_vec.extend(vec![
|
2020-04-18 04:14:42 +00:00
|
|
|
|
meta.name
|
|
|
|
|
.render(colors, icons, display_option, flags.hyperlink),
|
2021-08-17 13:17:21 +00:00
|
|
|
|
meta.indicator.render(flags),
|
2021-03-12 10:43:30 +00:00
|
|
|
|
]);
|
|
|
|
|
if !(flags.no_symlink.0 || flags.dereference.0 || flags.layout == Layout::Grid) {
|
2021-08-17 13:17:21 +00:00
|
|
|
|
block_vec.push(meta.symlink.render(colors, flags))
|
2021-03-12 10:43:30 +00:00
|
|
|
|
}
|
2019-05-18 13:49:29 +00:00
|
|
|
|
}
|
2019-03-30 10:58:36 +00:00
|
|
|
|
};
|
2021-07-06 06:21:22 +00:00
|
|
|
|
strings.push(
|
|
|
|
|
block_vec
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
|
.collect::<Vec<String>>()
|
2021-07-07 06:38:02 +00:00
|
|
|
|
.join(""),
|
2021-07-06 06:21:22 +00:00
|
|
|
|
);
|
2019-03-30 10:58:36 +00:00
|
|
|
|
}
|
2019-10-24 13:31:14 +00:00
|
|
|
|
strings
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
fn get_visible_width(input: &str, hyperlink: bool) -> usize {
|
2019-01-20 10:22:14 +00:00
|
|
|
|
let mut nb_invisible_char = 0;
|
|
|
|
|
|
2019-03-12 06:20:57 +00:00
|
|
|
|
// If the input has color, do not compute the length contributed by the color to the actual length
|
2019-10-24 13:31:14 +00:00
|
|
|
|
for (idx, _) in input.match_indices("\u{1b}[") {
|
|
|
|
|
let (_, s) = input.split_at(idx);
|
|
|
|
|
|
|
|
|
|
let m_pos = s.find('m');
|
2019-03-12 06:20:57 +00:00
|
|
|
|
if let Some(len) = m_pos {
|
2019-10-30 09:17:02 +00:00
|
|
|
|
nb_invisible_char += len
|
2019-03-11 13:45:39 +00:00
|
|
|
|
}
|
2019-01-20 10:22:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
if hyperlink {
|
|
|
|
|
for (idx, _) in input.match_indices("\x1B]8;;") {
|
|
|
|
|
let (_, s) = input.split_at(idx);
|
|
|
|
|
|
|
|
|
|
let m_pos = s.find("\x1B\x5C");
|
|
|
|
|
if let Some(len) = m_pos {
|
|
|
|
|
nb_invisible_char += len
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
UnicodeWidthStr::width(input) - nb_invisible_char
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-04 10:54:01 +00:00
|
|
|
|
fn detect_size_lengths(metas: &[Meta], flags: &Flags) -> usize {
|
2019-01-20 10:22:14 +00:00
|
|
|
|
let mut max_value_length: usize = 0;
|
2018-12-02 16:22:51 +00:00
|
|
|
|
|
2019-01-20 10:22:14 +00:00
|
|
|
|
for meta in metas {
|
2019-10-23 15:34:57 +00:00
|
|
|
|
let value_len = meta.size.value_string(flags).len();
|
2019-11-21 01:18:59 +00:00
|
|
|
|
|
2019-06-16 11:46:47 +00:00
|
|
|
|
if value_len > max_value_length {
|
|
|
|
|
max_value_length = value_len;
|
2018-12-20 13:09:01 +00:00
|
|
|
|
}
|
2021-02-18 10:15:12 +00:00
|
|
|
|
|
|
|
|
|
if Layout::Tree == flags.layout {
|
|
|
|
|
if let Some(subs) = &meta.content {
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let sub_length = detect_size_lengths(subs, flags);
|
2021-02-18 10:15:12 +00:00
|
|
|
|
if sub_length > max_value_length {
|
|
|
|
|
max_value_length = sub_length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
2019-01-20 10:22:14 +00:00
|
|
|
|
|
2019-11-04 10:54:01 +00:00
|
|
|
|
max_value_length
|
2018-12-02 16:22:51 +00:00
|
|
|
|
}
|
2019-05-04 10:56:27 +00:00
|
|
|
|
|
2019-10-24 13:42:26 +00:00
|
|
|
|
fn get_padding_rules(metas: &[Meta], flags: &Flags) -> HashMap<Block, usize> {
|
2019-10-23 15:17:38 +00:00
|
|
|
|
let mut padding_rules: HashMap<Block, usize> = HashMap::new();
|
|
|
|
|
|
2020-09-24 18:09:26 +00:00
|
|
|
|
if flags.blocks.0.contains(&Block::Size) {
|
2021-08-17 13:17:21 +00:00
|
|
|
|
let size_val = detect_size_lengths(metas, flags);
|
2019-10-23 15:17:38 +00:00
|
|
|
|
|
2019-10-24 13:42:26 +00:00
|
|
|
|
padding_rules.insert(Block::SizeValue, size_val);
|
2019-10-23 15:17:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
padding_rules
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-20 02:03:58 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2019-02-09 10:41:42 +00:00
|
|
|
|
use crate::color;
|
|
|
|
|
use crate::color::Colors;
|
2022-03-27 05:11:02 +00:00
|
|
|
|
use crate::flags::HyperlinkOption;
|
2019-02-09 10:41:42 +00:00
|
|
|
|
use crate::icon::Icons;
|
|
|
|
|
use crate::meta::{FileType, Name};
|
2021-03-13 03:35:42 +00:00
|
|
|
|
use crate::Config;
|
|
|
|
|
use crate::{app, flags, icon, sort};
|
|
|
|
|
use assert_fs::prelude::*;
|
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
|
|
|
|
for (s, l) in &[
|
|
|
|
|
("Hello,world!", 22),
|
|
|
|
|
("ASCII1234-_", 11),
|
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
2019-01-19 16:27:50 +00:00
|
|
|
|
let name = Name::new(
|
|
|
|
|
&path,
|
|
|
|
|
FileType::File {
|
|
|
|
|
exec: false,
|
|
|
|
|
uid: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
2021-07-07 06:38:02 +00:00
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
|
|
|
|
&DisplayOption::FileName,
|
2022-03-27 05:11:02 +00:00
|
|
|
|
HyperlinkOption::Never,
|
2021-07-07 06:38:02 +00:00
|
|
|
|
)
|
|
|
|
|
.to_string();
|
2019-01-20 10:22:14 +00:00
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
assert_eq!(get_visible_width(&output, false), *l);
|
2018-12-20 02:03:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-20 13:09:01 +00:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_with_icons() {
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
// Add 3 characters for the icons.
|
2019-11-04 16:10:09 +00:00
|
|
|
|
("Hello,world!", 24),
|
|
|
|
|
("ASCII1234-_", 13),
|
|
|
|
|
("File with space", 17),
|
|
|
|
|
("制作样本。", 12),
|
|
|
|
|
("日本語", 8),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 28),
|
|
|
|
|
("👩🐩", 6),
|
|
|
|
|
("🔬", 4),
|
2018-12-20 13:09:01 +00:00
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
2019-01-19 16:27:50 +00:00
|
|
|
|
let name = Name::new(
|
|
|
|
|
&path,
|
|
|
|
|
FileType::File {
|
|
|
|
|
exec: false,
|
|
|
|
|
uid: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
2018-12-20 13:09:01 +00:00
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
2020-11-29 13:14:29 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-01-04 19:25:17 +00:00
|
|
|
|
&Icons::new(icon::Theme::Fancy, " ".to_string()),
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&DisplayOption::FileName,
|
2022-03-27 05:11:02 +00:00
|
|
|
|
HyperlinkOption::Never,
|
2018-12-20 13:09:01 +00:00
|
|
|
|
)
|
|
|
|
|
.to_string();
|
2019-01-20 10:22:14 +00:00
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
assert_eq!(get_visible_width(&output, false), *l);
|
2018-12-20 13:09:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_with_colors() {
|
|
|
|
|
for (s, l) in &[
|
2019-03-11 15:12:16 +00:00
|
|
|
|
("Hello,world!", 22),
|
2018-12-20 13:09:01 +00:00
|
|
|
|
("ASCII1234-_", 11),
|
2019-03-11 15:12:16 +00:00
|
|
|
|
("File with space", 15),
|
2018-12-20 13:09:01 +00:00
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
2019-01-19 16:27:50 +00:00
|
|
|
|
let name = Name::new(
|
|
|
|
|
&path,
|
|
|
|
|
FileType::File {
|
|
|
|
|
exec: false,
|
|
|
|
|
uid: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
2018-12-20 13:09:01 +00:00
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
2020-11-29 13:14:29 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoLscolors),
|
2021-01-04 19:25:17 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&DisplayOption::FileName,
|
2022-03-27 05:11:02 +00:00
|
|
|
|
HyperlinkOption::Never,
|
2018-12-20 13:09:01 +00:00
|
|
|
|
)
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
// check if the color is present.
|
2021-09-09 06:22:02 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
true,
|
|
|
|
|
output.starts_with("\u{1b}[38;5;"),
|
|
|
|
|
"{:?} should start with color",
|
|
|
|
|
output,
|
|
|
|
|
);
|
2021-07-07 06:38:02 +00:00
|
|
|
|
assert_eq!(true, output.ends_with("[39m"), "reset foreground color");
|
2018-12-20 13:09:01 +00:00
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
assert_eq!(get_visible_width(&output, false), *l, "visible match");
|
2019-03-11 15:12:16 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_without_colors() {
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
("Hello,world!", 22),
|
|
|
|
|
("ASCII1234-_", 11),
|
|
|
|
|
("File with space", 15),
|
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
let path = Path::new(s);
|
|
|
|
|
let name = Name::new(
|
|
|
|
|
&path,
|
|
|
|
|
FileType::File {
|
|
|
|
|
exec: false,
|
|
|
|
|
uid: false,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let output = name
|
|
|
|
|
.render(
|
2020-11-29 13:14:29 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-01-04 19:25:17 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
2020-02-04 04:32:36 +00:00
|
|
|
|
&DisplayOption::FileName,
|
2022-03-27 05:11:02 +00:00
|
|
|
|
HyperlinkOption::Never,
|
2019-03-11 15:12:16 +00:00
|
|
|
|
)
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
// check if the color is present.
|
|
|
|
|
assert_eq!(false, output.starts_with("\u{1b}[38;5;"));
|
|
|
|
|
assert_eq!(false, output.ends_with("[0m"));
|
|
|
|
|
|
2022-03-27 05:11:02 +00:00
|
|
|
|
assert_eq!(get_visible_width(&output, false), *l);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_get_visible_width_hypelink_simple() {
|
|
|
|
|
for (s, l) in &[
|
|
|
|
|
("Hello,world!", 22),
|
|
|
|
|
("ASCII1234-_", 11),
|
|
|
|
|
("File with space", 15),
|
|
|
|
|
("制作样本。", 10),
|
|
|
|
|
("日本語", 6),
|
|
|
|
|
("샘플은 무료로 드리겠습니다", 26),
|
|
|
|
|
("👩🐩", 4),
|
|
|
|
|
("🔬", 2),
|
|
|
|
|
] {
|
|
|
|
|
// rending name require actual file, so we are mocking that
|
|
|
|
|
let output = format!("\x1B]8;;{}\x1B\x5C{}\x1B]8;;\x1B\x5C", "url://fake-url", s);
|
|
|
|
|
assert_eq!(get_visible_width(&output, true), *l);
|
2018-12-20 13:09:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-15 16:49:52 +00:00
|
|
|
|
|
2021-03-13 03:35:42 +00:00
|
|
|
|
fn sort(metas: &mut Vec<Meta>, sorters: &Vec<(flags::SortOrder, sort::SortFn)>) {
|
|
|
|
|
metas.sort_unstable_by(|a, b| sort::by_meta(sorters, a, b));
|
|
|
|
|
|
|
|
|
|
for meta in metas {
|
|
|
|
|
if let Some(ref mut content) = meta.content {
|
|
|
|
|
sort(content, sorters);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-15 16:49:52 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_display_tree_with_all() {
|
|
|
|
|
let argv = vec!["lsd", "--tree", "--all"];
|
|
|
|
|
let matches = app::build().get_matches_from_safe(argv).unwrap();
|
|
|
|
|
let flags = Flags::configure_from(&matches, &Config::with_none()).unwrap();
|
|
|
|
|
|
|
|
|
|
let dir = assert_fs::TempDir::new().unwrap();
|
|
|
|
|
dir.child("one.d").create_dir_all().unwrap();
|
|
|
|
|
dir.child("one.d/two").touch().unwrap();
|
|
|
|
|
dir.child("one.d/.hidden").touch().unwrap();
|
2021-03-13 03:35:42 +00:00
|
|
|
|
let mut metas = Meta::from_path(Path::new(dir.path()), false)
|
2021-02-15 16:49:52 +00:00
|
|
|
|
.unwrap()
|
|
|
|
|
.recurse_into(42, &flags)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.unwrap();
|
2021-03-13 03:35:42 +00:00
|
|
|
|
sort(&mut metas, &sort::assemble_sorters(&flags));
|
2021-02-18 10:15:12 +00:00
|
|
|
|
let output = tree(
|
2021-02-15 16:49:52 +00:00
|
|
|
|
&metas,
|
|
|
|
|
&flags,
|
2021-07-07 06:38:02 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-02-15 16:49:52 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!("one.d\n├── .hidden\n└── two\n", output);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-18 10:15:12 +00:00
|
|
|
|
/// Different level of folder may form a different width
|
|
|
|
|
/// we must make sure it is aligned in all level
|
|
|
|
|
///
|
|
|
|
|
/// dir has a bytes size
|
|
|
|
|
/// empty file has an empty size
|
|
|
|
|
/// `---blocks size,name` can help us for this case
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_tree_align_subfolder() {
|
|
|
|
|
let argv = vec!["lsd", "--tree", "--blocks", "size,name"];
|
|
|
|
|
let matches = app::build().get_matches_from_safe(argv).unwrap();
|
|
|
|
|
let flags = Flags::configure_from(&matches, &Config::with_none()).unwrap();
|
|
|
|
|
|
|
|
|
|
let dir = assert_fs::TempDir::new().unwrap();
|
|
|
|
|
dir.child("dir").create_dir_all().unwrap();
|
|
|
|
|
dir.child("dir/file").touch().unwrap();
|
|
|
|
|
let metas = Meta::from_path(Path::new(dir.path()), false)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.recurse_into(42, &flags)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let output = tree(
|
|
|
|
|
&metas,
|
|
|
|
|
&flags,
|
2021-07-07 06:38:02 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-02-18 10:15:12 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let length_before_b = |i| -> usize {
|
|
|
|
|
output
|
|
|
|
|
.lines()
|
|
|
|
|
.nth(i)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.split(|c| c == 'K' || c == 'B')
|
|
|
|
|
.nth(0)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.len()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(length_before_b(0), length_before_b(1));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
output.lines().nth(0).unwrap().find("d"),
|
|
|
|
|
output.lines().nth(1).unwrap().find("└")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-19 15:04:53 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
|
fn test_tree_size_first_without_name() {
|
|
|
|
|
let argv = vec!["lsd", "--tree", "--blocks", "size,permission"];
|
|
|
|
|
let matches = app::build().get_matches_from_safe(argv).unwrap();
|
|
|
|
|
let flags = Flags::configure_from(&matches, &Config::with_none()).unwrap();
|
|
|
|
|
|
|
|
|
|
let dir = assert_fs::TempDir::new().unwrap();
|
|
|
|
|
dir.child("dir").create_dir_all().unwrap();
|
|
|
|
|
dir.child("dir/file").touch().unwrap();
|
|
|
|
|
let metas = Meta::from_path(Path::new(dir.path()), false)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.recurse_into(42, &flags)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let output = tree(
|
|
|
|
|
&metas,
|
|
|
|
|
&flags,
|
2021-07-07 06:38:02 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-02-19 15:04:53 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(output.lines().nth(1).unwrap().chars().nth(0).unwrap(), '└');
|
|
|
|
|
assert_eq!(
|
|
|
|
|
output
|
|
|
|
|
.lines()
|
|
|
|
|
.nth(0)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.chars()
|
|
|
|
|
.position(|x| x == 'd'),
|
|
|
|
|
output
|
|
|
|
|
.lines()
|
|
|
|
|
.nth(1)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.chars()
|
|
|
|
|
.position(|x| x == '.'),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-15 16:49:52 +00:00
|
|
|
|
#[test]
|
2021-02-18 10:15:12 +00:00
|
|
|
|
fn test_tree_edge_before_name() {
|
2021-02-15 16:49:52 +00:00
|
|
|
|
let argv = vec!["lsd", "--tree", "--long"];
|
|
|
|
|
let matches = app::build().get_matches_from_safe(argv).unwrap();
|
|
|
|
|
let flags = Flags::configure_from(&matches, &Config::with_none()).unwrap();
|
|
|
|
|
|
|
|
|
|
let dir = assert_fs::TempDir::new().unwrap();
|
|
|
|
|
dir.child("one.d").create_dir_all().unwrap();
|
|
|
|
|
dir.child("one.d/two").touch().unwrap();
|
|
|
|
|
let metas = Meta::from_path(Path::new(dir.path()), false)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.recurse_into(42, &flags)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.unwrap();
|
2021-02-18 10:15:12 +00:00
|
|
|
|
let output = tree(
|
2021-02-15 16:49:52 +00:00
|
|
|
|
&metas,
|
|
|
|
|
&flags,
|
2021-07-07 06:38:02 +00:00
|
|
|
|
&Colors::new(color::ThemeOption::NoColor),
|
2021-02-15 16:49:52 +00:00
|
|
|
|
&Icons::new(icon::Theme::NoIcon, " ".to_string()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!(output.ends_with("└── two\n"));
|
|
|
|
|
}
|
2018-12-20 02:03:58 +00:00
|
|
|
|
}
|