summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPascal H <hpwxf@haveneer.com>2023-04-30 18:26:15 +0200
committerGitHub <noreply@github.com>2023-05-01 00:26:15 +0800
commit2fe3fcdd3564836962eab8ba6b1444996fe24e1e (patch)
treefad504cec71bd00aa6b1a5e6c34fe042e4a1cfec
parent6840c01905a7601b0d7f0d4dc2e08c5a5a581321 (diff)
Git integration (#822)
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock62
-rw-r--r--Cargo.toml8
-rw-r--r--README.md2
-rw-r--r--build.rs11
-rw-r--r--ci/before_deploy.bash2
-rw-r--r--doc/lsd.md7
-rw-r--r--src/app.rs49
-rw-r--r--src/color.rs7
-rw-r--r--src/config_file.rs4
-rw-r--r--src/core.rs32
-rw-r--r--src/display.rs180
-rw-r--r--src/flags.rs1
-rw-r--r--src/flags/blocks.rs59
-rw-r--r--src/flags/sorting.rs29
-rw-r--r--src/git.rs460
-rw-r--r--src/git_theme.rs31
-rw-r--r--src/icon.rs2
-rw-r--r--src/main.rs2
-rw-r--r--src/meta/git_file_status.rs67
-rw-r--r--src/meta/mod.rs14
-rw-r--r--src/meta/name.rs2
-rw-r--r--src/sort.rs5
-rw-r--r--src/theme.rs3
-rw-r--r--src/theme/color.rs19
-rw-r--r--src/theme/git.rs35
26 files changed, 1056 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e2e0b4..de3a46f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+- Add [Git integration](https://github.com/Peltoche/lsd/issues/7) from [hpwxf](https://github.com/hpwxf)
- In keeping with the coreutils change, add quotes and escapes for necessary filenames from [merelymyself](https://github.com/merelymyself)
- Add support for icon theme from [zwpaper](https://github.com/zwpaper)
- Add icon for kt and kts from [LeeWeeder](https://github.com/LeeWeeder)
diff --git a/Cargo.lock b/Cargo.lock
index 4261dda..0e5ed12 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,9 @@ name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+dependencies = [
+ "jobserver",
+]
[[package]]
name = "cfg-if"
@@ -296,6 +299,19 @@ dependencies = [
]
[[package]]
+name = "git2"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "url",
+]
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -432,6 +448,15 @@ dependencies = [
]
[[package]]
+name = "jobserver"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "js-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -453,6 +478,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
+name = "libgit2-sys"
+version = "0.14.2+1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -503,6 +552,7 @@ dependencies = [
"clap_complete",
"crossterm",
"dirs",
+ "git2",
"globset",
"human-sort",
"libc",
@@ -644,6 +694,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
name = "predicates"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1105,6 +1161,12 @@ dependencies = [
]
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index dff0f7a..50e8cbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,7 @@ clap_complete = "4.1"
version_check = "0.9.*"
[dependencies]
-crossterm = { version = "0.24.0", features = ["serde"]}
+crossterm = { version = "0.24.0", features = ["serde"] }
dirs = "3.0.*"
libc = "0.2.*"
human-sort = "0.2.2"
@@ -42,6 +42,10 @@ serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
url = "2.1.*"
+[target."cfg(not(all(windows, target_arch = \"x86\", target_env = \"gnu\")))".dependencies]
+# if ssl feature is enabled compilation will fail on arm-unknown-linux-gnueabihf and i686-pc-windows-gnu
+git2 = { version = "0.16", optional = true, default-features = false }
+
[target.'cfg(unix)'.dependencies]
users = "0.11.*"
xattr = "0.2.*"
@@ -61,7 +65,9 @@ tempfile = "3"
serial_test = "0.5"
[features]
+default = ["git2"]
sudo = []
+no-git = [] # force disabling git even if available by default
[profile.release]
lto = true
diff --git a/README.md b/README.md
index dc14c81..227ee07 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ classic: false
# == Blocks ==
# This specifies the columns and their order when using the long and the tree
# layout.
-# Possible values: permission, user, group, context, size, date, name, inode, links
+# Possible values: permission, user, group, context, size, date, name, inode, links, git
blocks:
- permission
- user
diff --git a/build.rs b/build.rs
index 8e3f05c..68540c9 100644
--- a/build.rs
+++ b/build.rs
@@ -34,4 +34,15 @@ fn main() {
generate_to(Zsh, &mut app, bin_name, &outdir).expect("Failed to generate Zsh completions");
generate_to(PowerShell, &mut app, bin_name, &outdir)
.expect("Failed to generate PowerShell completions");
+
+ // Disable git feature for these target where git2 is not well supported
+ if !std::env::var("CARGO_FEATURE_GIT2")
+ .map(|flag| flag == "1")
+ .unwrap_or(false)
+ || std::env::var("TARGET")
+ .map(|target| target == "i686-pc-windows-gnu")
+ .unwrap_or(false)
+ {
+ println!(r#"cargo:rustc-cfg=feature="no-git""#);
+ }
}
diff --git a/ci/before_deploy.bash b/ci/before_deploy.bash
index e4afd5c..9c8f045 100644
--- a/ci/before_deploy.bash
+++ b/ci/before_deploy.bash
@@ -4,7 +4,7 @@
set -ex
build() {
- cargo build --target "$TARGET" --release --verbose
+ cargo build --target "$TARGET" --features="$FEATURES" --release --verbose
}
pack() {
diff --git a/doc/lsd.md b/doc/lsd.md
index 1a3cc3e..0adec7b 100644
--- a/doc/lsd.md
+++ b/doc/lsd.md
@@ -38,6 +38,9 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
`-X`, `--extensionsort`
: Sort by file extension
+`--git`
+: Display git status. Directory git status is a reduction of included file statuses (recursively).
+
`--help`
: Prints help information
@@ -90,7 +93,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
: Natural sort of (version) numbers within text
`--blocks <blocks>...`
-: Specify the blocks that will be displayed and in what order [possible values: permission, user, group, size, date, name, inode]
+: Specify the blocks that will be displayed and in what order [possible values: permission, user, group, size, date, name, inode, git]
`--color <color>...`
: When to use terminal colours [default: auto] [possible values: always, auto, never]
@@ -126,7 +129,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
: How to display size [default: default] [possible values: default, short, bytes]
`--sort <WORD>...`
-: Sort by WORD instead of name [possible values: size, time, version, extension]
+: Sort by WORD instead of name [possible values: size, time, version, extension, git]
`-U`, `--no-sort`
: Do not sort. List entries in directory order
diff --git a/src/app.rs b/src/app.rs
index 5003866..bd6defa 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -96,6 +96,10 @@ pub struct Cli {
#[arg(short = 'X', long)]
pub extensionsort: bool,
+ /// Sort by git status
+ #[arg(short = 'G', long)]
+ pub gitsort: bool,
+
/// Natural sort of (version) numbers within text
#[arg(short = 'v', long)]
pub versionsort: bool,
@@ -104,13 +108,13 @@ pub struct Cli {
#[arg(
long,
value_name = "TYPE",
- value_parser = ["size", "time", "version", "extension", "none"],
- overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "no_sort"]
+ value_parser = ["size", "time", "version", "extension", "git", "none"],
+ overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "no_sort"]
)]
pub sort: Option<String>,
/// Do not sort. List entries in directory order
- #[arg(short = 'U', long, overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "sort"])]
+ #[arg(short = 'U', long, overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "sort"])]
pub no_sort: bool,
/// Reverse the order of the sort
@@ -127,9 +131,9 @@ pub struct Cli {
/// Specify the blocks that will be displayed and in what order
#[arg(
- long,
- value_delimiter = ',',
- value_parser = ["permission", "user", "group", "context", "size", "date", "name", "inode", "links"],
+ long,
+ value_delimiter = ',',
+ value_parser = ["permission", "user", "group", "context", "size", "date", "name", "inode", "links", "git"],
)]
pub blocks: Vec<String>,
@@ -150,6 +154,11 @@ pub struct Cli {
#[arg(short, long)]
pub inode: bool,
+ /// Show git status on file and directory"
+ /// Only when used with --long option
+ #[arg(short, long)]
+ pub git: bool,
+
/// When showing file information for a symbolic link,
/// show information for the file the link references rather than for the link itself
#[arg(short = 'L', long)]
@@ -196,15 +205,15 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> {
Some('f') => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
- Some(c) => return Err(format!("invalid format specifier: %.{}{}", n, c)),
+ Some(c) => return Err(format!("invalid format specifier: %.{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
- Some(c) => return Err(format!("invalid format specifier: %.{}", c)),
+ Some(c) => return Err(format!("invalid format specifier: %.{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ (':' | '#')) => match chars.next() {
Some('z') => (),
- Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
+ Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ ('-' | '_' | '0')) => match chars.next() {
@@ -212,7 +221,7 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> {
'C' | 'd' | 'e' | 'f' | 'G' | 'g' | 'H' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm'
| 'S' | 's' | 'U' | 'u' | 'V' | 'W' | 'w' | 'Y' | 'y',
) => (),
- Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
+ Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(
@@ -223,10 +232,10 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> {
) => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
- Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
+ Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
- Some(c) => return Err(format!("invalid format specifier: %{}", c)),
+ Some(c) => return Err(format!("invalid format specifier: %{c}")),
None => return Err("missing format specifier".to_owned()),
},
None => break,
@@ -235,3 +244,19 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> {
}
Ok(formatter.to_owned())
}
+
+// Wrapper for value_parser to simply remove non supported option (mainly git flag)
+// required since value_parser requires impl Into<ValueParser> that Vec do not support
+// should be located here, since this file is included by build.rs
+struct LabelFilter<Filter: Fn(&'static str) -> bool, const C: usize>([&'static str; C], Filter);
+
+impl<Filter: Fn(&'static str) -> bool, const C: usize> From<LabelFilter<Filter, C>>
+ for clap::builder::ValueParser
+{
+ fn from(label_filter: LabelFilter<Filter, C>) -> Self {
+ let filter = label_filter.1;
+ let values = label_filter.0.into_iter().filter(|x| filter(x));
+ let inner = clap::builder::PossibleValuesParser::from(values);
+ Self::from(inner)
+ }
+}
diff --git a/src/color.rs b/src/color.rs
index c054d5a..ed69849 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -4,6 +4,7 @@ use lscolors::{Indicator, LsColors};
use std::path::Path;
pub use crate::flags::color::ThemeOption;
+use crate::git::GitStatus;
use crate::theme::{color::ColorTheme, Theme};
#[allow(dead_code)]
@@ -61,6 +62,10 @@ pub enum Elem {
},
TreeEdge,
+
+ GitStatus {
+ status: GitStatus,
+ },
}
impl Elem {
@@ -121,6 +126,7 @@ impl Elem {
Elem::TreeEdge => theme.tree_edge,
Elem::Links { valid: false } => theme.links.invalid,
Elem::Links { valid: true } => theme.links.valid,
+ Elem::GitStatus { .. } => theme.git_status.default,
}
}
}
@@ -389,6 +395,7 @@ mod elem {
invalid: Color::AnsiValue(245), // Grey
},
tree_edge: Color::AnsiValue(245), // Grey
+ git_status: Default::default(),
}
}
diff --git a/src/config_file.rs b/src/config_file.rs
index d2c6d7c..c9392c8 100644
--- a/src/config_file.rs
+++ b/src/config_file.rs
@@ -203,7 +203,7 @@ classic: false
# == Blocks ==
# This specifies the columns and their order when using the long and the tree
# layout.
-# Possible values: permission, user, group, context, size, date, name, inode
+# Possible values: permission, user, group, context, size, date, name, inode, git
blocks:
- permission
- user
@@ -388,7 +388,7 @@ mod tests {
total_size: Some(false),
symlink_arrow: Some("⇒".into()),
hyperlink: Some(HyperlinkOption::Never),
- header: None
+ header: None,
},
c
);
diff --git a/src/core.rs b/src/core.rs
index 9377c07..b0d0efc 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -1,7 +1,9 @@
use crate::color::Colors;
use crate::display;
use crate::flags::{ColorOption, Display, Flags, HyperlinkOption, Layout, SortOrder, ThemeOption};
+use crate::git::GitCache;
use crate::icon::Icons;
+
use crate::meta::Meta;
use crate::{print_error, print_output, sort, ExitCode};
use std::path::PathBuf;
@@ -11,6 +13,8 @@ use std::io;
#[cfg(not(target_os = "windows"))]
use std::os::unix::io::AsRawFd;
+use crate::flags::blocks::Block;
+use crate::git_theme::GitTheme;
#[cfg(target_os = "windows")]
use terminal_size::terminal_size;
@@ -18,6 +22,7 @@ pub struct Core {
flags: Flags,
icons: Icons,
colors: Colors,
+ git_theme: GitTheme,
sorters: Vec<(SortOrder, sort::SortFn)>,
}
@@ -75,6 +80,7 @@ impl Core {
flags,
colors: Colors::new(color_theme),
icons: Icons::new(tty_available, icon_when, icon_theme, icon_separator),
+ git_theme: GitTheme::new(),
sorters,
}
}
@@ -106,12 +112,19 @@ impl Core {
}
};
+ let cache = if self.flags.blocks.0.contains(&Block::GitStatus) {
+ Some(GitCache::new(&path))
+ } else {
+ None
+ };
+
let recurse =
self.flags.layout == Layout::Tree || self.flags.display != Display::DirectoryOnly;
if recurse {
- match meta.recurse_into(depth, &self.flags) {
+ match meta.recurse_into(depth, &self.flags, cache.as_ref()) {
Ok((content, path_exit_code)) => {
meta.content = content;
+ meta.git_status = cache.and_then(|cache| cache.get(&meta.path, true));
meta_list.push(meta);
exit_code.set_if_greater(path_exit_code);
}
@@ -122,6 +135,7 @@ impl Core {
}
};
} else {
+ meta.git_status = cache.and_then(|cache| cache.get(&meta.path, true));
meta_list.push(meta);
};
}
@@ -147,9 +161,21 @@ impl Core {
fn display(&self, metas: &[Meta]) {
let output = if self.flags.layout == Layout::Tree {
- display::tree(metas, &self.flags, &self.colors, &self.icons)
+ display::tree(
+ metas,
+ &self.flags,
+ &self.colors,
+ &self.icons,
+ &self.git_theme,
+ )
} else {
- display::grid(metas, &self.flags, &self.colors, &self.icons)
+ display::grid(
+ metas,
+ &self.flags,
+ &self.colors,
+ &self.icons,
+ &self.git_theme,
+ )
};
print_output!("{}", output);
diff --git a/src/display.rs b/src/display.rs
index 8e7c251..6fea6e9 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -1,5 +1,7 @@
use crate::color::{Colors, Elem};
-use crate::flags::{Block, Display, Flags, HyperlinkOption, Layout};
+use crate::flags::blocks::Block;
+use crate::flags::{Display, Flags, HyperlinkOption, Layout};
+use crate::git_theme::GitTheme;
use crate::icon::Icons;
use crate::meta::name::DisplayOption;
use crate::meta::{FileType, Meta};
@@ -13,7 +15,13 @@ const LINE: &str = "\u{2502} "; // "│ "
const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──"
const BLANK: &str = " ";
-pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
+pub fn grid(
+ metas: &[Meta],
+ flags: &Flags,
+ colors: &Colors,
+ icons: &Icons,
+ git_theme: &GitTheme,
+) -> String {
let term_width = terminal_size().map(|(w, _)| w.0 as usize);
inner_display_grid(
@@ -22,12 +30,19 @@ pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St
flags,
colors,
icons,
+ git_theme,
0,
term_width,
)
}
-pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
+pub fn tree(
+ metas: &[Meta],
+ flags: &Flags,
+ colors: &Colors,
+ icons: &Icons,
+ git_theme: &GitTheme,
+) -> String {
let mut grid = Grid::new(GridOptions {
filling: Filling::Spaces(1),
direction: Direction::LeftToRight,
@@ -42,19 +57,30 @@ pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St
}
}
- for cell in inner_display_tree(metas, flags, colors, icons, (0, ""), &padding_rules, index) {
+ for cell in inner_display_tree(
+ metas,
+ flags,
+ colors,
+ icons,
+ git_theme,
+ (0, ""),
+ &padding_rules,
+ index,
+ ) {
grid.add(cell);
}
grid.fit_into_columns(flags.blocks.0.len()).to_string()
}
+#[allow(clippy::too_many_arguments)] // should wrap flags, colors, icons, git_theme into one struct
fn inner_display_grid(
display_option: &DisplayOption,
metas: &[Meta],
flags: &Flags,
colors: &Colors,
icons: &Icons,
+ git_theme: &GitTheme,
depth: usize,
term_width: Option<usize>,
) -> String {
@@ -93,6 +119,7 @@ fn inner_display_grid(
meta,
colors,
icons,
+ git_theme,
flags,
display_option,
&padding_rules,
@@ -152,6 +179,7 @@ fn inner_display_grid(
flags,
colors,
icons,
+ git_theme,
depth + 1,
term_width,
);
@@ -192,11 +220,13 @@ fn add_header(flags: &Flags, cells: &[Cell], grid: &mut Grid) {
}
}
+#[allow(clippy::too_many_arguments)]
fn inner_display_tree(
metas: &[Meta],
flags: &Flags,
colors: &Colors,
icons: &Icons,
+ git_theme: &GitTheme,
tree_depth_prefix: (usize, &str),
padding_rules: &HashMap<Block, usize>,
tree_index: usize,
@@ -220,6 +250,7 @@ fn inner_display_tree(
meta,
colors,
icons,
+ git_theme,
flags,
&DisplayOption::FileName,
padding_rules,
@@ -248,6 +279,7 @@ fn inner_display_tree(
flags,
colors,
icons,
+ git_theme,
(tree_depth_prefix.0 + 1, &new_prefix),
padding_rules,
tree_index,
@@ -279,10 +311,12 @@ fn display_folder_path(meta: &Meta) -> String {
format!("\n{}:\n", meta.path.to_string_lossy())
}
+#[allow(clippy::too_many_arguments)]
fn get_output(
meta: &Meta,
colors: &Colors,
icons: &Icons,
+ git_theme: &GitTheme,
flags: &Flags,
display_option: &DisplayOption,
padding_rules: &HashMap<Block, usize>,
@@ -366,6 +400,11 @@ fn get_output(
block_vec.push(meta.symlink.render(colors, flags))
}
}
+ Block::GitStatus => {
+ if let Some(_s) = &meta.git_status {
+ block_vec.push(_s.render(colors, git_theme));
+ }
+ }
};
strings.push(
block_vec
@@ -457,6 +496,7 @@ mod tests {
use assert_fs::prelude::*;
use clap::Parser;
use std::path::Path;
+ use tempfile::tempdir;
#[test]
fn test_display_get_visible_width_without_icons() {
@@ -559,8 +599,7 @@ mod tests {
// check if the color is present.
assert!(
output.starts_with("\u{1b}[38;5;"),
- "{:?} should start with color",
- output,
+ "{output:?} should start with color"
);
assert!(output.ends_with("[39m"), "reset foreground color");
@@ -646,7 +685,7 @@ mod tests {
dir.child("one.d/.hidden").touch().unwrap();
let mut metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(42, &flags)
+ .recurse_into(42, &flags, None)
.unwrap()
.0
.unwrap();
@@ -656,6 +695,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
assert_eq!("one.d\n├── .hidden\n└── two\n", output);
@@ -678,7 +718,7 @@ mod tests {
dir.child("dir/file").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(42, &flags)
+ .recurse_into(42, &flags, None)
.unwrap()
.0
.unwrap();
@@ -687,6 +727,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
let length_before_b = |i| -> usize {
@@ -718,7 +759,7 @@ mod tests {
dir.child("dir/file").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(42, &flags)
+ .recurse_into(42, &flags, None)
.unwrap()
.0
.unwrap();
@@ -727,6 +768,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
assert_eq!(output.lines().nth(1).unwrap().chars().next().unwrap(), '└');
@@ -757,7 +799,7 @@ mod tests {
dir.child("one.d/two").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(42, &flags)
+ .recurse_into(42, &flags, None)
.unwrap()
.0
.unwrap();
@@ -766,6 +808,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
assert!(output.ends_with("└── two\n"));
@@ -787,7 +830,7 @@ mod tests {
dir.child("test").touch().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(1, &flags)
+ .recurse_into(1, &flags, None)
.unwrap()
.0
.unwrap();
@@ -796,6 +839,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
dir.close().unwrap();
@@ -820,7 +864,7 @@ mod tests {
dir.child("testdir").create_dir_all().unwrap();
let metas = Meta::from_path(Path::new(dir.path()), false)
.unwrap()
- .recurse_into(1, &flags)
+ .recurse_into(1, &flags, None)
.unwrap()
.0
.unwrap();
@@ -829,6 +873,7 @@ mod tests {
&flags,
&Colors::new(color::ThemeOption::NoColor),
&Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()),
+ &GitTheme::new(),
);
dir.close().unwrap();
@@ -840,4 +885,115 @@ mod tests {
assert!(!output.contains("Date Modified"));
assert!(!output.contains("Name"));
}
+
+ #[test]
+ fn test_folder_path() {
+ let tmp_dir = tempdir().expect("failed to create temp dir");
+
+ let file_path = tmp_dir.path().join("file");
+ std::fs::File::create(&file_path).expect("failed to create the file");
+ let file = Meta::from_path(&file_path, false).unwrap();
+
+ let dir_path = tmp_dir.path().join("dir");
+ std::fs::create_dir(&dir_path).expect("failed to create the dir");
+ let dir = Meta::from_path(&dir_path, false).unwrap();
+
+ assert_eq!(
+ display_folder_path(&dir),
+ format!(
+ "\n{}{}dir:\n",
+ tmp_dir.path().to_string_lossy(),
+ std::path::MAIN_SEPARATOR
+ )
+ );
+
+ const YES: bool = true;
+ const NO: bool = false;
+
+ assert_eq!(
+ should_display_folder_path(0, &[file.clone()], &Flags::default()),
+ YES // doesn't matter since there is no folder
+ );
+ assert_eq!(
+ should_display_folder_path(0, &[dir.clone()], &Flags::default()),
+ NO
+ );
+ assert_eq!(
+ should_display_folder_path(0, &[file.clone(), dir.clone()], &Flags::default()),
+ YES
+ );
+ assert_eq!(
+ should_display_folder_path(0, &[dir.clone(), dir.clone()], &Flags::default()),
+ YES
+ );
+ assert_eq!(
+ should_display_folder_path(0, &[file.clone(), file.clone()], &Flags::default()),
+ YES // doesn't matter since there is no folder
+ );
+
+ drop(dir); // to avoid clippy complains about previous .clone()
+ drop(file);
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn test_folder_path_with_links() {
+ let tmp_dir = tempdir().expect("failed to create temp dir");
+
+ let file_path = tmp_dir.path().join("file");
+ std::fs::File::create(&file_path).expect("failed to create the file");
+ let file = Meta::from_path(&file_path, false).unwrap();
+
+ let dir_path = tmp_dir.path().join("dir");
+ std::fs::create_dir(&dir_path).expect("failed to create the dir");
+ let dir = Meta::from_path(&dir_path, false).unwrap();
+
+ let link_path = tmp_dir.path().join("link");
+ std::os::unix::fs::symlink("dir", &link_path).unwrap();
+ let link = Meta::from_path(&link_path, false).unwrap()