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

Issue:
Stop lsd on windows from showing every regular file as green
- os: Windows 11
- `lsd --version`: lsd 0.22.0
- `echo $TERM`: N/A
- `echo $LS_COLORS`: N/A

Windows NTFS doesnt have POSIX file permissions, so they need to be emulated.  This results in all files being marked as executable, which is necessary but annoying, since every regular file is shown in green.  Is there a way to make executable files white?


Discussion / hints:
Can one not create a new theme and select unique colours based on the file extension? 
![image](https://user-images.githubusercontent.com/13314239/187677026-4a533dfd-7008-4e76-9eb5-6ea32e1f3454.png)
Note that the  NTUSER.DAT* files are supposed to be hidden

![image](https://user-images.githubusercontent.com/13314239/187677217-8168718b-416f-41e3-90a9-11bd728b32e2.png)
![image](https://user-images.githubusercontent.com/13314239/187677415-baf7b858-2f8b-46a7-b913-d3e033abdc60.png)
ll is an alias for lsd -l
You can change the color of items by using [LS_COLORS](https://gist.github.com/thomd/7667642), but the problem here is that the executable flag overrides the colors set based on extension.
> You can change the color of items by using [LS_COLORS](https://gist.github.com/thomd/7667642), but the problem here is that the executable flag overrides the colors set based on extension.

Is `lsd `ignoring the hidden flag of files by default?
What do you mean by hidden flag?
In Windows you can set a file to be Hidden.
Oh, I was not aware of it. I don't think we are handling that as of now.
@meain Genuinely reconsider please. Ignoring hidden files/folders would make a world of difference for Windows or WSL users. 90% of apps/programs on Windows don't use '.' or '*_*' for hidden files/folders like they would on *Nix, and instead rely on Windows default hidden flag. I know it's a hassle, but such is life for supporting Windows.

If exa implements this before lsd, it'd be a big enough deal for me to switch despite needing to self-compile exa.
I don't use Windows and don't have enough context to take a decision on this. How is it done in other programs like [ripgrep](https://github.com/BurntSushi/ripgrep) and [fd](https://github.com/sharkdp/fd)? Do they take into account hidden flag in Windows? @TeamTamoad since you are a Windows user, let me know if you have any thought around it.
Actually, I'm a Linux user. I just happened to have a Windows laptop to test on some issue. 
I think implementing these kind of feature is not a bad thing because it makes the user happy 😃. I've looked into `ripgrep` and `fd`, they've implemented hidden files on Windows correctly (see [this](https://www.github.com/BurntSushi/ripgrep/issues/1154) and [this](https://www.github.com/sharkdp/fd/issues/379))
However, the problem is that we need someone to implement this. Since I've had a full-time job 3 months ago, I have much less time to contribute to this project.
Thanks @TeamTamoad for linking to rg and fd issues. I think it makes sense to have lsd respect hidden files in windows when not using `-a` or `-A`. That said, I thin this is different from the original issue which was about file permissions. I'll create a separate issue (https://github.com/Peltoche/lsd/issues/752) for respecting hidden flag in windows.

PS: No pressure on your to pick it up btw. Just thought I would get your thoughts on this as I have seen you comment on Windows related issues. :D.
While we are on the topic, my understanding is that there is no such thing as "executable" flag on windows, or is there a flag we can query which `lsd` is not already doing? If there is no such thing as a executable, although we can override the color of executable using `LS_COLORS`, I don't might changing the it so that `lsd` does not treat every file as executable.
windows does not have an executable flag, but we could make files with an executable extention like .exe, .bat, .cmd, .ps1, etc show as green.
I think in that case the quickest option would be to just use the option suggested by https://github.com/Peltoche/lsd/issues/712#issuecomment-1232868513 .
Oh by the way, windows has 2 different kinds of hidden files.  There's normal hidden files and operating system protected files.  Those NTUSER.dat files are the second kind.  The user almost never has to interact with OS protected files, I've never done so once in my 10 years or so of using Windows.  I would suggest making a dedicated command line flag to show OS protected files, and normal hidden files can be shown with just -a or -A.
> Oh by the way, windows has 2 different kinds of hidden files. There's normal hidden files and operating system protected files. Those NTUSER.dat files are the second kind. The user almost never has to interact with OS protected files, I've never done so once in my 10 years or so of using Windows. I would suggest making a dedicated command line flag to show OS protected files, and normal hidden files can be shown with just -a or -A.

You are right. I like your suggestion.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecb53df19..40f7963b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 - Do not quote filename when piping into another program from [TeamTamoad](https://github.com/TeamTamoad)
 - Respect `hidden` flag on Windows [#752](https://github.com/Peltoche/lsd/issues/752)
+- Do not show every file are `executable` (green) on Windows
+  [#712](https://github.com/Peltoche/lsd/issues/712). Executables will be marked
+  based on the file extension: `exe`, `msi`, `bat` and `ps1`.
+  [`LS_COLORS`](README.md#Colors) can be used to customize.
 
 ## [0.23.1] - 2022-09-13
 
diff --git a/src/meta/filetype.rs b/src/meta/filetype.rs
index 27b59f7d0..e3fa1cc9e 100644
--- a/src/meta/filetype.rs
+++ b/src/meta/filetype.rs
@@ -1,5 +1,4 @@
 use crate::color::{ColoredString, Colors, Elem};
-use crate::meta::Permissions;
 use std::fs::Metadata;
 
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -16,11 +15,14 @@ pub enum FileType {
 }
 
 impl FileType {
+    #[cfg(windows)]
+    const EXECUTABLE_EXTENSIONS: &[&'static str] = &["exe", "msi", "bat", "ps1"];
+
     #[cfg(unix)]
     pub fn new(
         meta: &Metadata,
         symlink_meta: Option<&Metadata>,
-        permissions: &Permissions,
+        permissions: &crate::meta::Permissions,
     ) -> Self {
         use std::os::unix::fs::FileTypeExt;
 
@@ -54,22 +56,22 @@ impl FileType {
     }
 
     #[cfg(windows)]
-    pub fn new(
-        meta: &Metadata,
-        symlink_meta: Option<&Metadata>,
-        permissions: &Permissions,
-    ) -> Self {
+    pub fn new(meta: &Metadata, symlink_meta: Option<&Metadata>, path: &std::path::Path) -> Self {
         let file_type = meta.file_type();
 
         if file_type.is_file() {
-            FileType::File {
-                exec: permissions.is_executable(),
-                uid: permissions.setuid,
-            }
+            let exec = path
+                .extension()
+                .map(|ext| {
+                    Self::EXECUTABLE_EXTENSIONS
+                        .iter()
+                        .map(std::ffi::OsStr::new)
+                        .any(|exec_ext| ext == exec_ext)
+                })
+                .unwrap_or(false);
+            FileType::File { exec, uid: false }
         } else if file_type.is_dir() {
-            FileType::Directory {
-                uid: permissions.setuid,
-            }
+            FileType::Directory { uid: false }
         } else if file_type.is_symlink() {
             FileType::SymLink {
                 // if broken, defaults to false
@@ -107,11 +109,9 @@ impl FileType {
 mod test {
     use super::FileType;
     use crate::color::{Colors, ThemeOption};
-    use crate::meta::Meta;
     #[cfg(unix)]
     use crate::meta::Permissions;
     use crossterm::style::{Color, Stylize};
-    #[cfg(unix)]
     use std::fs::File;
     #[cfg(unix)]
     use std::os::unix::fs::symlink;
@@ -143,11 +143,16 @@ mod test {
     #[test]
     fn test_dir_type() {
         let tmp_dir = tempdir().expect("failed to create temp dir");
-        let meta = Meta::from_path(tmp_dir.path(), false).expect("failed to get tempdir path");
+        #[cfg(not(windows))]
+        let meta = crate::meta::Meta::from_path(tmp_dir.path(), false)
+            .expect("failed to get tempdir path");
         let metadata = tmp_dir.path().metadata().expect("failed to get metas");
 
         let colors = Colors::new(ThemeOption::NoLscolors);
+        #[cfg(not(windows))]
         let file_type = FileType::new(&metadata, None, &meta.permissions);
+        #[cfg(windows)]
+        let file_type = FileType::new(&metadata, None, tmp_dir.path());
 
         assert_eq!(
             "d".to_string().with(Color::AnsiValue(33)),
@@ -275,4 +280,42 @@ mod test {
             file_type.render(&colors)
         );
     }
+
+    #[cfg(windows)]
+    #[test]
+    fn test_file_executable() {
+        let tmp_dir = tempdir().expect("failed to create temp dir");
+        for ext in FileType::EXECUTABLE_EXTENSIONS {
+            // Create the file;
+            let file_path = tmp_dir.path().join(format!("file.{ext}"));
+            File::create(&file_path).expect("failed to create file");
+            let meta = file_path.metadata().expect("failed to get metas");
+
+            let colors = Colors::new(ThemeOption::NoLscolors);
+            let file_type = FileType::new(&meta, None, &file_path);
+
+            assert_eq!(
+                ".".to_string().with(Color::AnsiValue(40)),
+                file_type.render(&colors)
+            );
+        }
+    }
+
+    #[cfg(windows)]
+    #[test]
+    fn test_file_not_executable() {
+        let tmp_dir = tempdir().expect("failed to create temp dir");
+        // Create the file;
+        let file_path = tmp_dir.path().join("file.txt");
+        File::create(&file_path).expect("failed to create file");
+        let meta = file_path.metadata().expect("failed to get metas");
+
+        let colors = Colors::new(ThemeOption::NoLscolors);
+        let file_type = FileType::new(&meta, None, &file_path);
+
+        assert_eq!(
+            ".".to_string().with(Color::AnsiValue(184)),
+            file_type.render(&colors)
+        );
+    }
 }
diff --git a/src/meta/mod.rs b/src/meta/mod.rs
index aaa3ff539..286685edd 100644
--- a/src/meta/mod.rs
+++ b/src/meta/mod.rs
@@ -253,7 +253,13 @@ impl Meta {
         let (owner, permissions) = windows_utils::get_file_data(path)?;
 
         let access_control = AccessControl::for_path(path);
+
+        #[cfg(not(windows))]
         let file_type = FileType::new(&metadata, symlink_meta.as_ref(), &permissions);
+
+        #[cfg(windows)]
+        let file_type = FileType::new(&metadata, symlink_meta.as_ref(), path);
+
         let name = Name::new(path, file_type);
         let inode = INode::from(&metadata);
         let links = Links::from(&metadata);
diff --git a/src/meta/permissions.rs b/src/meta/permissions.rs
index 79a0e4735..7b09252c9 100644
--- a/src/meta/permissions.rs
+++ b/src/meta/permissions.rs
@@ -127,6 +127,7 @@ impl Permissions {
         ColoredString::new(Colors::default_style(), res)
     }
 
+    #[cfg(not(windows))]
     pub fn is_executable(&self) -> bool {
         self.user_execute || self.group_execute || self.other_execute
     }


Test patch: