Browse Source

chore: replace #[allow(unused)] by `#[cfg(unix)]` when appropriate

Biggest change is splitting platform-specific code from permissions.rs
(similarly to flags.rs in the same directory).
ariasuni 3 weeks ago
parent
commit
dd6628b60b

+ 10 - 7
src/fs/fields.rs

@@ -21,18 +21,19 @@
 #![allow(clippy::struct_excessive_bools)]
 
 /// The type of a file’s group ID.
+#[cfg(unix)]
 pub type gid_t = u32;
 
 /// The type of a file’s inode.
-#[allow(unused)]
+#[cfg(unix)]
 pub type ino_t = u64;
 
 /// The type of a file’s number of links.
-#[allow(unused)]
+#[cfg(unix)]
 pub type nlink_t = u64;
 
 /// The type of a file’s user ID.
-#[allow(unused)]
+#[cfg(unix)]
 pub type uid_t = u32;
 
 /// The type of user file flags
@@ -68,6 +69,7 @@ impl Type {
 /// The file’s Unix permission bitfield, with one entry per bit.
 #[derive(Copy, Clone)]
 #[rustfmt::skip]
+#[cfg(unix)]
 pub struct Permissions {
     pub user_read:      bool,
     pub user_write:     bool,
@@ -116,6 +118,7 @@ pub struct PermissionsPlus {
 
 /// The permissions encoded as octal values
 #[derive(Copy, Clone)]
+#[cfg(unix)]
 pub struct OctalPermissions {
     pub permissions: Permissions,
 }
@@ -126,7 +129,7 @@ pub struct OctalPermissions {
 /// multiple directories. However, it’s rare (but occasionally useful!) for a
 /// regular file to have a link count greater than 1, so we highlight the
 /// block count specifically for this case.
-#[allow(unused)]
+#[cfg(unix)]
 #[derive(Copy, Clone)]
 pub struct Links {
     /// The actual link count.
@@ -139,7 +142,7 @@ pub struct Links {
 /// A file’s inode. Every directory entry on a Unix filesystem has an inode,
 /// including directories and links, so this is applicable to everything exa
 /// can deal with.
-#[allow(unused)]
+#[cfg(unix)]
 #[derive(Copy, Clone)]
 pub struct Inode(pub ino_t);
 
@@ -156,12 +159,12 @@ pub enum Blocksize {
 
 /// The ID of the user that owns a file. This will only ever be a number;
 /// looking up the username is done in the `display` module.
-#[allow(unused)]
+#[cfg(unix)]
 #[derive(Copy, Clone)]
 pub struct User(pub uid_t);
 
 /// The ID of the group that a file belongs to.
-#[allow(unused)]
+#[cfg(unix)]
 #[derive(Copy, Clone)]
 pub struct Group(pub gid_t);
 

+ 3 - 3
src/fs/mounts/mod.rs

@@ -27,7 +27,7 @@ pub struct MountedFs {
 }
 
 #[derive(Debug)]
-#[non_exhaustive]
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 pub enum Error {
     #[cfg(target_os = "macos")]
     GetFSStatError(i32),
@@ -35,18 +35,18 @@ pub enum Error {
     IOError(std::io::Error),
 }
 
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 impl std::error::Error for Error {}
 
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 impl std::fmt::Display for Error {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         // Allow unreachable_patterns for windows build
-        #[allow(unreachable_patterns)]
         match self {
             #[cfg(target_os = "macos")]
             Error::GetFSStatError(err) => write!(f, "getfsstat failed: {err}"),
             #[cfg(target_os = "linux")]
             Error::IOError(err) => write!(f, "failed to read /proc/mounts: {err}"),
-            _ => write!(f, "Unknown error"),
         }
     }
 }

+ 5 - 0
src/output/render/mod.rs

@@ -30,6 +30,10 @@ pub use self::links::Colours as LinksColours;
 
 mod permissions;
 pub use self::permissions::{Colours as PermissionsColours, PermissionsPlusRender};
+#[cfg(unix)]
+mod permissions_unix;
+#[cfg(windows)]
+mod permissions_windows;
 
 mod size;
 pub use self::size::Colours as SizeColours;
@@ -45,6 +49,7 @@ pub use self::users::Colours as UserColours;
 #[cfg(unix)]
 pub use self::users::Render as UserRender;
 
+#[cfg(unix)]
 mod octal;
 #[cfg(unix)]
 pub use self::octal::Render as OctalPermissionsRender;

+ 0 - 1
src/output/render/octal.rs

@@ -9,7 +9,6 @@ use nu_ansi_term::Style;
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
 
-#[allow(unused)]
 pub trait Render {
     fn render(&self, style: Style) -> TextCell;
 }

+ 3 - 313
src/output/render/permissions.rs

@@ -4,167 +4,16 @@
 // SPDX-FileCopyrightText: 2023-2024 Christina Sørensen, eza contributors
 // SPDX-FileCopyrightText: 2014 Benjamin Sago
 // SPDX-License-Identifier: MIT
-use std::iter;
 
-use nu_ansi_term::{AnsiString as ANSIString, Style};
-
-use crate::fs::fields as f;
-use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::cell::TextCell;
 use crate::output::render::FiletypeColours;
 
+use nu_ansi_term::Style;
+
 pub trait PermissionsPlusRender {
     fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell;
 }
 
-impl PermissionsPlusRender for Option<f::PermissionsPlus> {
-    #[cfg(unix)]
-    fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
-        if let Some(p) = self {
-            let mut chars = vec![p.file_type.render(colours)];
-            let permissions = p.permissions;
-            chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file()));
-
-            if p.xattrs {
-                chars.push(colours.attribute().paint("@"));
-            }
-
-            // As these are all ASCII characters, we can guarantee that they’re
-            // all going to be one character wide, and don’t need to compute the
-            // cell’s display width.
-            TextCell {
-                width: DisplayWidth::from(chars.len()),
-                contents: chars.into(),
-            }
-        } else {
-            let chars: Vec<_> = iter::repeat_n(colours.dash().paint("-"), 10).collect();
-            TextCell {
-                width: DisplayWidth::from(chars.len()),
-                contents: chars.into(),
-            }
-        }
-    }
-
-    #[cfg(windows)]
-    fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
-        match self {
-            Some(p) => {
-                let mut chars = vec![p.attributes.render_type(colours)];
-                chars.extend(p.attributes.render(colours));
-
-                TextCell {
-                    width: DisplayWidth::from(chars.len()),
-                    contents: chars.into(),
-                }
-            }
-            None => TextCell {
-                width: DisplayWidth::from(0),
-                contents: vec![].into(),
-            },
-        }
-    }
-}
-
-#[allow(unused)]
-pub trait RenderPermissions {
-    fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>>;
-}
-
-impl RenderPermissions for Option<f::Permissions> {
-    fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
-        match self {
-            Some(p) => {
-                let bit = |bit, chr: &'static str, style: Style| {
-                    if bit {
-                        style.paint(chr)
-                    } else {
-                        colours.dash().paint("-")
-                    }
-                };
-
-                vec![
-                    bit(p.user_read, "r", colours.user_read()),
-                    bit(p.user_write, "w", colours.user_write()),
-                    p.user_execute_bit(colours, is_regular_file),
-                    bit(p.group_read, "r", colours.group_read()),
-                    bit(p.group_write, "w", colours.group_write()),
-                    p.group_execute_bit(colours),
-                    bit(p.other_read, "r", colours.other_read()),
-                    bit(p.other_write, "w", colours.other_write()),
-                    p.other_execute_bit(colours),
-                ]
-            }
-            None => iter::repeat_n(colours.dash().paint("-"), 9).collect(),
-        }
-    }
-}
-
-impl f::Permissions {
-    fn user_execute_bit<C: Colours>(
-        &self,
-        colours: &C,
-        is_regular_file: bool,
-    ) -> ANSIString<'static> {
-        #[rustfmt::skip]
-        return match (self.user_execute, self.setuid, is_regular_file) {
-            (false, false, _)      => colours.dash().paint("-"),
-            (true,  false, false)  => colours.user_execute_other().paint("x"),
-            (true,  false, true)   => colours.user_execute_file().paint("x"),
-            (false, true,  _)      => colours.special_other().paint("S"),
-            (true,  true,  false)  => colours.special_other().paint("s"),
-            (true,  true,  true)   => colours.special_user_file().paint("s"),
-        };
-    }
-
-    fn group_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
-        #[rustfmt::skip]
-        return match (self.group_execute, self.setgid) {
-            (false, false)  => colours.dash().paint("-"),
-            (true,  false)  => colours.group_execute().paint("x"),
-            (false, true)   => colours.special_other().paint("S"),
-            (true,  true)   => colours.special_other().paint("s"),
-        };
-    }
-
-    fn other_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
-        #[rustfmt::skip]
-        return match (self.other_execute, self.sticky) {
-            (false, false)  => colours.dash().paint("-"),
-            (true,  false)  => colours.other_execute().paint("x"),
-            (false, true)   => colours.special_other().paint("T"),
-            (true,  true)   => colours.special_other().paint("t"),
-        };
-    }
-}
-
-#[cfg(windows)]
-impl f::Attributes {
-    pub fn render<C: Colours + FiletypeColours>(self, colours: &C) -> Vec<ANSIString<'static>> {
-        let bit = |bit, chr: &'static str, style: Style| {
-            if bit {
-                style.paint(chr)
-            } else {
-                colours.dash().paint("-")
-            }
-        };
-
-        vec![
-            bit(self.archive, "a", colours.normal()),
-            bit(self.readonly, "r", colours.user_read()),
-            bit(self.hidden, "h", colours.special_user_file()),
-            bit(self.system, "s", colours.special_other()),
-        ]
-    }
-
-    pub fn render_type<C: Colours + FiletypeColours>(self, colours: &C) -> ANSIString<'static> {
-        if self.reparse_point {
-            return colours.pipe().paint("l");
-        } else if self.directory {
-            return colours.directory().paint("d");
-        }
-        colours.dash().paint("-")
-    }
-}
-
 pub trait Colours {
     fn dash(&self) -> Style;
 
@@ -186,162 +35,3 @@ pub trait Colours {
 
     fn attribute(&self) -> Style;
 }
-
-#[cfg(test)]
-#[allow(unused_results)]
-pub mod test {
-    use super::{Colours, RenderPermissions};
-    use crate::fs::fields as f;
-    use crate::output::cell::TextCellContents;
-
-    use nu_ansi_term::Color::*;
-    use nu_ansi_term::Style;
-
-    struct TestColours;
-
-    #[rustfmt::skip]
-    impl Colours for TestColours {
-        fn dash(&self)                -> Style { Fixed(11).normal() }
-        fn user_read(&self)           -> Style { Fixed(101).normal() }
-        fn user_write(&self)          -> Style { Fixed(102).normal() }
-        fn user_execute_file(&self)   -> Style { Fixed(103).normal() }
-        fn user_execute_other(&self)  -> Style { Fixed(113).normal() }
-        fn group_read(&self)          -> Style { Fixed(104).normal() }
-        fn group_write(&self)         -> Style { Fixed(105).normal() }
-        fn group_execute(&self)       -> Style { Fixed(106).normal() }
-        fn other_read(&self)          -> Style { Fixed(107).normal() }
-        fn other_write(&self)         -> Style { Fixed(108).normal() }
-        fn other_execute(&self)       -> Style { Fixed(109).normal() }
-        fn special_user_file(&self)   -> Style { Fixed(110).normal() }
-        fn special_other(&self)       -> Style { Fixed(111).normal() }
-        fn attribute(&self)           -> Style { Fixed(112).normal() }
-    }
-
-    #[test]
-    fn negate() {
-        let bits = Some(f::Permissions {
-            user_read: false,
-            user_write: false,
-            user_execute: false,
-            setuid: false,
-            group_read: false,
-            group_write: false,
-            group_execute: false,
-            setgid: false,
-            other_read: false,
-            other_write: false,
-            other_execute: false,
-            sticky: false,
-        });
-
-        let expected = TextCellContents::from(vec![
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-        ]);
-
-        assert_eq!(expected, bits.render(&TestColours, false).into());
-    }
-
-    #[test]
-    fn affirm() {
-        let bits = Some(f::Permissions {
-            user_read: true,
-            user_write: true,
-            user_execute: true,
-            setuid: false,
-            group_read: true,
-            group_write: true,
-            group_execute: true,
-            setgid: false,
-            other_read: true,
-            other_write: true,
-            other_execute: true,
-            sticky: false,
-        });
-
-        let expected = TextCellContents::from(vec![
-            Fixed(101).paint("r"),
-            Fixed(102).paint("w"),
-            Fixed(103).paint("x"),
-            Fixed(104).paint("r"),
-            Fixed(105).paint("w"),
-            Fixed(106).paint("x"),
-            Fixed(107).paint("r"),
-            Fixed(108).paint("w"),
-            Fixed(109).paint("x"),
-        ]);
-
-        assert_eq!(expected, bits.render(&TestColours, true).into());
-    }
-
-    #[test]
-    fn specials() {
-        let bits = Some(f::Permissions {
-            user_read: false,
-            user_write: false,
-            user_execute: true,
-            setuid: true,
-            group_read: false,
-            group_write: false,
-            group_execute: true,
-            setgid: true,
-            other_read: false,
-            other_write: false,
-            other_execute: true,
-            sticky: true,
-        });
-
-        let expected = TextCellContents::from(vec![
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(110).paint("s"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(111).paint("s"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(111).paint("t"),
-        ]);
-
-        assert_eq!(expected, bits.render(&TestColours, true).into());
-    }
-
-    #[test]
-    fn extra_specials() {
-        let bits = Some(f::Permissions {
-            user_read: false,
-            user_write: false,
-            user_execute: false,
-            setuid: true,
-            group_read: false,
-            group_write: false,
-            group_execute: false,
-            setgid: true,
-            other_read: false,
-            other_write: false,
-            other_execute: false,
-            sticky: true,
-        });
-
-        let expected = TextCellContents::from(vec![
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(111).paint("S"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(111).paint("S"),
-            Fixed(11).paint("-"),
-            Fixed(11).paint("-"),
-            Fixed(111).paint("T"),
-        ]);
-
-        assert_eq!(expected, bits.render(&TestColours, true).into());
-    }
-}

+ 273 - 0
src/output/render/permissions_unix.rs

@@ -0,0 +1,273 @@
+// SPDX-FileCopyrightText: 2024 Christina Sørensen
+// SPDX-License-Identifier: EUPL-1.2
+//
+// SPDX-FileCopyrightText: 2023-2024 Christina Sørensen, eza contributors
+// SPDX-FileCopyrightText: 2014 Benjamin Sago
+// SPDX-License-Identifier: MIT
+use std::iter;
+
+use super::{PermissionsColours as Colours, PermissionsPlusRender};
+use crate::fs::fields as f;
+use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::render::FiletypeColours;
+
+use nu_ansi_term::{AnsiString as ANSIString, Style};
+
+impl PermissionsPlusRender for Option<f::PermissionsPlus> {
+    fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
+        if let Some(p) = self {
+            let mut chars = vec![p.file_type.render(colours)];
+            let permissions = p.permissions;
+            chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file()));
+
+            if p.xattrs {
+                chars.push(colours.attribute().paint("@"));
+            }
+
+            // As these are all ASCII characters, we can guarantee that they’re
+            // all going to be one character wide, and don’t need to compute the
+            // cell’s display width.
+            TextCell {
+                width: DisplayWidth::from(chars.len()),
+                contents: chars.into(),
+            }
+        } else {
+            let chars: Vec<_> = iter::repeat_n(colours.dash().paint("-"), 10).collect();
+            TextCell {
+                width: DisplayWidth::from(chars.len()),
+                contents: chars.into(),
+            }
+        }
+    }
+}
+
+pub trait RenderPermissions {
+    fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>>;
+}
+
+impl RenderPermissions for Option<f::Permissions> {
+    fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
+        match self {
+            Some(p) => {
+                let bit = |bit, chr: &'static str, style: Style| {
+                    if bit {
+                        style.paint(chr)
+                    } else {
+                        colours.dash().paint("-")
+                    }
+                };
+
+                vec![
+                    bit(p.user_read, "r", colours.user_read()),
+                    bit(p.user_write, "w", colours.user_write()),
+                    p.user_execute_bit(colours, is_regular_file),
+                    bit(p.group_read, "r", colours.group_read()),
+                    bit(p.group_write, "w", colours.group_write()),
+                    p.group_execute_bit(colours),
+                    bit(p.other_read, "r", colours.other_read()),
+                    bit(p.other_write, "w", colours.other_write()),
+                    p.other_execute_bit(colours),
+                ]
+            }
+            None => std::iter::repeat_n(colours.dash().paint("-"), 9).collect(),
+        }
+    }
+}
+
+impl f::Permissions {
+    fn user_execute_bit<C: Colours>(
+        &self,
+        colours: &C,
+        is_regular_file: bool,
+    ) -> ANSIString<'static> {
+        #[rustfmt::skip]
+        return match (self.user_execute, self.setuid, is_regular_file) {
+            (false, false, _)      => colours.dash().paint("-"),
+            (true,  false, false)  => colours.user_execute_other().paint("x"),
+            (true,  false, true)   => colours.user_execute_file().paint("x"),
+            (false, true,  _)      => colours.special_other().paint("S"),
+            (true,  true,  false)  => colours.special_other().paint("s"),
+            (true,  true,  true)   => colours.special_user_file().paint("s"),
+        };
+    }
+
+    fn group_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
+        #[rustfmt::skip]
+        return match (self.group_execute, self.setgid) {
+            (false, false)  => colours.dash().paint("-"),
+            (true,  false)  => colours.group_execute().paint("x"),
+            (false, true)   => colours.special_other().paint("S"),
+            (true,  true)   => colours.special_other().paint("s"),
+        };
+    }
+
+    fn other_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
+        #[rustfmt::skip]
+        return match (self.other_execute, self.sticky) {
+            (false, false)  => colours.dash().paint("-"),
+            (true,  false)  => colours.other_execute().paint("x"),
+            (false, true)   => colours.special_other().paint("T"),
+            (true,  true)   => colours.special_other().paint("t"),
+        };
+    }
+}
+
+#[cfg(test)]
+#[allow(unused_results)]
+pub mod test {
+    use nu_ansi_term::Color::*;
+    use nu_ansi_term::Style;
+
+    use super::*;
+    use crate::output::cell::TextCellContents;
+
+    struct TestColours;
+
+    #[rustfmt::skip]
+    impl Colours for TestColours {
+        fn dash(&self)                -> Style { Fixed(11).normal() }
+        fn user_read(&self)           -> Style { Fixed(101).normal() }
+        fn user_write(&self)          -> Style { Fixed(102).normal() }
+        fn user_execute_file(&self)   -> Style { Fixed(103).normal() }
+        fn user_execute_other(&self)  -> Style { Fixed(113).normal() }
+        fn group_read(&self)          -> Style { Fixed(104).normal() }
+        fn group_write(&self)         -> Style { Fixed(105).normal() }
+        fn group_execute(&self)       -> Style { Fixed(106).normal() }
+        fn other_read(&self)          -> Style { Fixed(107).normal() }
+        fn other_write(&self)         -> Style { Fixed(108).normal() }
+        fn other_execute(&self)       -> Style { Fixed(109).normal() }
+        fn special_user_file(&self)   -> Style { Fixed(110).normal() }
+        fn special_other(&self)       -> Style { Fixed(111).normal() }
+        fn attribute(&self)           -> Style { Fixed(112).normal() }
+    }
+
+    #[test]
+    #[cfg(unix)]
+    fn negate() {
+        let bits = Some(f::Permissions {
+            user_read: false,
+            user_write: false,
+            user_execute: false,
+            setuid: false,
+            group_read: false,
+            group_write: false,
+            group_execute: false,
+            setgid: false,
+            other_read: false,
+            other_write: false,
+            other_execute: false,
+            sticky: false,
+        });
+
+        let expected = TextCellContents::from(vec![
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+        ]);
+
+        assert_eq!(expected, bits.render(&TestColours, false).into());
+    }
+
+    #[test]
+    #[cfg(unix)]
+    fn affirm() {
+        let bits = Some(f::Permissions {
+            user_read: true,
+            user_write: true,
+            user_execute: true,
+            setuid: false,
+            group_read: true,
+            group_write: true,
+            group_execute: true,
+            setgid: false,
+            other_read: true,
+            other_write: true,
+            other_execute: true,
+            sticky: false,
+        });
+
+        let expected = TextCellContents::from(vec![
+            Fixed(101).paint("r"),
+            Fixed(102).paint("w"),
+            Fixed(103).paint("x"),
+            Fixed(104).paint("r"),
+            Fixed(105).paint("w"),
+            Fixed(106).paint("x"),
+            Fixed(107).paint("r"),
+            Fixed(108).paint("w"),
+            Fixed(109).paint("x"),
+        ]);
+
+        assert_eq!(expected, bits.render(&TestColours, true).into());
+    }
+
+    #[test]
+    fn specials() {
+        let bits = Some(f::Permissions {
+            user_read: false,
+            user_write: false,
+            user_execute: true,
+            setuid: true,
+            group_read: false,
+            group_write: false,
+            group_execute: true,
+            setgid: true,
+            other_read: false,
+            other_write: false,
+            other_execute: true,
+            sticky: true,
+        });
+
+        let expected = TextCellContents::from(vec![
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(110).paint("s"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(111).paint("s"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(111).paint("t"),
+        ]);
+
+        assert_eq!(expected, bits.render(&TestColours, true).into());
+    }
+
+    #[test]
+    fn extra_specials() {
+        let bits = Some(f::Permissions {
+            user_read: false,
+            user_write: false,
+            user_execute: false,
+            setuid: true,
+            group_read: false,
+            group_write: false,
+            group_execute: false,
+            setgid: true,
+            other_read: false,
+            other_write: false,
+            other_execute: false,
+            sticky: true,
+        });
+
+        let expected = TextCellContents::from(vec![
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(111).paint("S"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(111).paint("S"),
+            Fixed(11).paint("-"),
+            Fixed(11).paint("-"),
+            Fixed(111).paint("T"),
+        ]);
+
+        assert_eq!(expected, bits.render(&TestColours, true).into());
+    }
+}

+ 61 - 0
src/output/render/permissions_windows.rs

@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: 2024 Christina Sørensen
+// SPDX-License-Identifier: EUPL-1.2
+//
+// SPDX-FileCopyrightText: 2023-2024 Christina Sørensen, eza contributors
+// SPDX-FileCopyrightText: 2014 Benjamin Sago
+// SPDX-License-Identifier: MIT
+use crate::fs::fields as f;
+use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::render::FiletypeColours;
+
+use super::{PermissionsColours as Colours, PermissionsPlusRender};
+
+use nu_ansi_term::{AnsiString as ANSIString, Style};
+
+impl PermissionsPlusRender for Option<f::PermissionsPlus> {
+    fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
+        match self {
+            Some(p) => {
+                let mut chars = vec![p.attributes.render_type(colours)];
+                chars.extend(p.attributes.render(colours));
+
+                TextCell {
+                    width: DisplayWidth::from(chars.len()),
+                    contents: chars.into(),
+                }
+            }
+            None => TextCell {
+                width: DisplayWidth::from(0),
+                contents: vec![].into(),
+            },
+        }
+    }
+}
+
+impl f::Attributes {
+    pub fn render<C: Colours + FiletypeColours>(self, colours: &C) -> Vec<ANSIString<'static>> {
+        let bit = |bit, chr: &'static str, style: Style| {
+            if bit {
+                style.paint(chr)
+            } else {
+                colours.dash().paint("-")
+            }
+        };
+
+        vec![
+            bit(self.archive, "a", colours.normal()),
+            bit(self.readonly, "r", colours.user_read()),
+            bit(self.hidden, "h", colours.special_user_file()),
+            bit(self.system, "s", colours.special_other()),
+        ]
+    }
+
+    pub fn render_type<C: Colours + FiletypeColours>(self, colours: &C) -> ANSIString<'static> {
+        if self.reparse_point {
+            return colours.pipe().paint("l");
+        } else if self.directory {
+            return colours.directory().paint("d");
+        }
+        colours.dash().paint("-")
+    }
+}