Repository: lsd-rs/lsd (language: rust)

Issue:
Folder contents not shown if symlinked folder and not followed by `/`
Does not seem to not show the folder contents if symlinked folder and not followed by a `/`.

## Expected behavior
If applicable, add the output of the classic ls command (`\ls -la`) in order to show the buggy file/directory.

```
$ ls symlink
a  b  c

$ ls symlink/
a  b  c
```

## Actual behavior
If the application panics run the command with the trace (`RUST_BACKTRACE=1 lsd ...`).
If it's a graphical error add a screenshot if possible.

```
$ lsd symlink
symlink ⇒ dir

$ lsd symlink/
a  b  c
```


## Steps to reproduce
```sh
mkdir dir
ln -s dir symlink
touch dir/{a,b,c}
lsd symlink
lsd symlink/ 
```


Discussion / hints:

Fix patch:
diff --git a/src/core.rs b/src/core.rs
index 740ff7d10..c188e2d1c 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -95,12 +95,7 @@ impl Core {
                     meta_list.push(meta);
                 }
                 _ => {
-                    match meta.recurse_into(
-                        depth,
-                        self.flags.display,
-                        &self.flags.ignore_globs,
-                        self.flags.dereference,
-                    ) {
+                    match meta.recurse_into(depth, &self.flags) {
                         Ok(content) => {
                             meta.content = content;
                             meta_list.push(meta);
diff --git a/src/display.rs b/src/display.rs
index eda9dc056..13155ac87 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -66,7 +66,11 @@ fn inner_display_grid(
     // print the files first.
     for meta in metas {
         // Maybe skip showing the directory meta now; show its contents later.
-        if let (true, FileType::Directory { .. }) = (skip_dirs, meta.file_type) {
+        if skip_dirs
+            && (matches!(meta.file_type, FileType::Directory{..})
+                || (matches!(meta.file_type, FileType::SymLink { is_dir: true })
+                    && flags.layout != Layout::OneLine))
+        {
             continue;
         }
 
@@ -106,7 +110,7 @@ fn inner_display_grid(
         output += &grid.fit_into_columns(flags.blocks.len()).to_string();
     }
 
-    let should_display_folder_path = should_display_folder_path(depth, &metas);
+    let should_display_folder_path = should_display_folder_path(depth, &metas, &flags);
 
     // print the folder content
     for meta in metas {
@@ -215,13 +219,17 @@ fn inner_display_tree(
     output
 }
 
-fn should_display_folder_path(depth: usize, metas: &[Meta]) -> bool {
+fn should_display_folder_path(depth: usize, metas: &[Meta], flags: &Flags) -> bool {
     if depth > 0 {
         true
     } else {
         let folder_number = metas
             .iter()
-            .filter(|x| matches!(x.file_type, FileType::Directory { .. }))
+            .filter(|x| {
+                matches!(x.file_type, FileType::Directory { .. })
+                    || (matches!(x.file_type, FileType::SymLink { is_dir: true })
+                        && flags.layout != Layout::OneLine)
+            })
             .count();
 
         folder_number > 1 || folder_number < metas.len()
diff --git a/src/meta/mod.rs b/src/meta/mod.rs
index 693553932..4a916f452 100644
--- a/src/meta/mod.rs
+++ b/src/meta/mod.rs
@@ -20,7 +20,7 @@ pub use self::owner::Owner;
 pub use self::permissions::Permissions;
 pub use self::size::Size;
 pub use self::symlink::SymLink;
-pub use crate::flags::Display;
+pub use crate::flags::{Display, Flags, Layout};
 pub use crate::icon::Icons;
 use crate::print_error;
 
@@ -28,8 +28,6 @@ use std::fs::read_link;
 use std::io::{Error, ErrorKind};
 use std::path::{Component, Path, PathBuf};
 
-use globset::GlobSet;
-
 #[derive(Clone, Debug)]
 pub struct Meta {
     pub name: Name,
@@ -49,20 +47,23 @@ impl Meta {
     pub fn recurse_into(
         &self,
         depth: usize,
-        display: Display,
-        ignore_globs: &GlobSet,
-        dereference: bool,
+        flags: &Flags,
     ) -> Result<Option<Vec<Meta>>, std::io::Error> {
         if depth == 0 {
             return Ok(None);
         }
 
-        if display == Display::DisplayDirectoryItself {
+        if flags.display == Display::DisplayDirectoryItself {
             return Ok(None);
         }
 
         match self.file_type {
             FileType::Directory { .. } => (),
+            FileType::SymLink { is_dir: true } => {
+                if flags.layout == Layout::OneLine {
+                    return Ok(None);
+                }
+            }
             _ => return Ok(None),
         }
 
@@ -76,13 +77,14 @@ impl Meta {
 
         let mut content: Vec<Meta> = Vec::new();
 
-        if let Display::DisplayAll = display {
+        if let Display::DisplayAll = flags.display {
             let mut current_meta;
 
             current_meta = self.clone();
             current_meta.name.name = ".".to_owned();
 
-            let parent_meta = Self::from_path(&self.path.join(Component::ParentDir), dereference)?;
+            let parent_meta =
+                Self::from_path(&self.path.join(Component::ParentDir), flags.dereference)?;
 
             content.push(current_meta);
             content.push(parent_meta);
@@ -95,17 +97,17 @@ impl Meta {
                 .file_name()
                 .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "invalid file name"))?;
 
-            if ignore_globs.is_match(&name) {
+            if flags.ignore_globs.is_match(&name) {
                 continue;
             }
 
-            if let Display::DisplayOnlyVisible = display {
+            if let Display::DisplayOnlyVisible = flags.display {
                 if name.to_string_lossy().starts_with('.') {
                     continue;
                 }
             }
 
-            let mut entry_meta = match Self::from_path(&path, dereference) {
+            let mut entry_meta = match Self::from_path(&path, flags.dereference) {
                 Ok(res) => res,
                 Err(err) => {
                     print_error!("lsd: {}: {}\n", path.display(), err);
@@ -113,7 +115,7 @@ impl Meta {
                 }
             };
 
-            match entry_meta.recurse_into(depth - 1, display, ignore_globs, dereference) {
+            match entry_meta.recurse_into(depth - 1, &flags) {
                 Ok(content) => entry_meta.content = content,
                 Err(err) => {
                     print_error!("lsd: {}: {}\n", path.display(), err);


Test patch:
diff --git a/tests/integration.rs b/tests/integration.rs
index c94b56d7d..554c72b6d 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -204,6 +204,62 @@ fn test_dereference_link_right_type_and_no_link() {
         .stdout(predicate::str::contains(link_icon).not());
 }
 
+#[cfg(unix)]
+#[test]
+fn test_show_folder_content_of_symlink() {
+    let dir = tempdir();
+    dir.child("target").child("inside").touch().unwrap();
+    let link = dir.path().join("link");
+    fs::symlink("target", &link).unwrap();
+
+    cmd()
+        .arg(link)
+        .assert()
+        .stdout(predicate::str::starts_with("link").not())
+        .stdout(predicate::str::starts_with("inside"));
+}
+
+#[cfg(unix)]
+#[test]
+fn test_no_show_folder_content_of_symlink_for_long() {
+    let dir = tempdir();
+    dir.child("target").child("inside").touch().unwrap();
+    let link = dir.path().join("link");
+    fs::symlink("target", &link).unwrap();
+
+    cmd()
+        .arg("-l")
+        .arg(link)
+        .assert()
+        .stdout(predicate::str::starts_with("lrw"))
+        .stdout(predicate::str::contains("⇒"));
+
+    cmd()
+        .arg("-l")
+        .arg(dir.path().join("link/"))
+        .assert()
+        .stdout(predicate::str::starts_with(".rw"))
+        .stdout(predicate::str::contains("⇒").not());
+}
+
+#[cfg(unix)]
+#[test]
+fn test_show_folder_of_symlink_for_long_multi() {
+    let dir = tempdir();
+    dir.child("target").child("inside").touch().unwrap();
+    let link = dir.path().join("link");
+    fs::symlink("target", &link).unwrap();
+
+    cmd()
+        .arg("-l")
+        .arg(dir.path().join("link/"))
+        .arg(dir.path().join("link"))
+        .assert()
+        .stdout(predicate::str::starts_with("lrw"))
+        .stdout(predicate::str::contains("link:").not()) // do not show dir content when no /
+        .stdout(predicate::str::contains("link/:"));
+}
+
 fn cmd() -> Command {
     Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap()
 }