Skip to main content

eza/output/render/
git.rs

1// SPDX-FileCopyrightText: 2024 Christina Sørensen
2// SPDX-License-Identifier: EUPL-1.2
3//
4// SPDX-FileCopyrightText: 2023-2024 Christina Sørensen, eza contributors
5// SPDX-FileCopyrightText: 2014 Benjamin Sago
6// SPDX-License-Identifier: MIT
7use nu_ansi_term::{AnsiString as ANSIString, Style};
8
9use crate::fs::fields as f;
10use crate::output::cell::{DisplayWidth, TextCell};
11
12impl f::Git {
13    pub fn render(self, colours: &dyn Colours) -> TextCell {
14        TextCell {
15            width: DisplayWidth::from(2),
16            contents: vec![self.staged.render(colours), self.unstaged.render(colours)].into(),
17        }
18    }
19}
20
21impl f::GitStatus {
22    fn render(self, colours: &dyn Colours) -> ANSIString<'static> {
23        #[rustfmt::skip]
24        return match self {
25            Self::NotModified  => colours.not_modified().paint("-"),
26            Self::New          => colours.new().paint("N"),
27            Self::Modified     => colours.modified().paint("M"),
28            Self::Deleted      => colours.deleted().paint("D"),
29            Self::Renamed      => colours.renamed().paint("R"),
30            Self::TypeChange   => colours.type_change().paint("T"),
31            Self::Ignored      => colours.ignored().paint("I"),
32            Self::Conflicted   => colours.conflicted().paint("U"),
33        };
34    }
35}
36
37pub trait Colours {
38    fn not_modified(&self) -> Style;
39    // FIXME: this amount of allows needed to keep clippy happy should be enough
40    // of an argument that new needs to be renamed.
41    #[allow(clippy::new_ret_no_self, clippy::wrong_self_convention)]
42    fn new(&self) -> Style;
43    fn modified(&self) -> Style;
44    fn deleted(&self) -> Style;
45    fn renamed(&self) -> Style;
46    fn type_change(&self) -> Style;
47    fn ignored(&self) -> Style;
48    fn conflicted(&self) -> Style;
49}
50
51impl f::SubdirGitRepo {
52    pub fn render(self, colours: &dyn RepoColours) -> TextCell {
53        let branch_name = match self.branch {
54            Some(name) => match name.as_ref() {
55                "main" | "master" => colours.branch_main().paint(name),
56                _ => colours.branch_other().paint(name),
57            },
58            None => colours.no_repo().paint("-"),
59        };
60
61        if let Some(status) = self.status {
62            TextCell {
63                width: DisplayWidth::from(2) + DisplayWidth::from(branch_name.as_str()),
64                contents: vec![
65                    status.render(colours),
66                    Style::default().paint(" "),
67                    branch_name,
68                ]
69                .into(),
70            }
71        } else {
72            TextCell {
73                width: DisplayWidth::from(branch_name.as_str()),
74                contents: vec![branch_name].into(),
75            }
76        }
77    }
78}
79
80impl f::SubdirGitRepoStatus {
81    pub fn render(self, colours: &dyn RepoColours) -> ANSIString<'static> {
82        match self {
83            Self::NoRepo => colours.no_repo().paint("-"),
84            Self::GitClean => colours.git_clean().paint("|"),
85            Self::GitDirty => colours.git_dirty().paint("+"),
86        }
87    }
88}
89
90pub trait RepoColours {
91    fn branch_main(&self) -> Style;
92    fn branch_other(&self) -> Style;
93    fn no_repo(&self) -> Style;
94    fn git_clean(&self) -> Style;
95    fn git_dirty(&self) -> Style;
96}
97
98#[cfg(test)]
99pub mod test {
100    use super::Colours;
101    use crate::fs::fields as f;
102    use crate::output::cell::{DisplayWidth, TextCell};
103
104    use nu_ansi_term::Color::*;
105    use nu_ansi_term::Style;
106
107    struct TestColours;
108
109    impl Colours for TestColours {
110        fn not_modified(&self) -> Style {
111            Fixed(90).normal()
112        }
113        fn new(&self) -> Style {
114            Fixed(91).normal()
115        }
116        fn modified(&self) -> Style {
117            Fixed(92).normal()
118        }
119        fn deleted(&self) -> Style {
120            Fixed(93).normal()
121        }
122        fn renamed(&self) -> Style {
123            Fixed(94).normal()
124        }
125        fn type_change(&self) -> Style {
126            Fixed(95).normal()
127        }
128        fn ignored(&self) -> Style {
129            Fixed(96).normal()
130        }
131        fn conflicted(&self) -> Style {
132            Fixed(97).normal()
133        }
134    }
135
136    #[test]
137    fn git_blank() {
138        let stati = f::Git {
139            staged: f::GitStatus::NotModified,
140            unstaged: f::GitStatus::NotModified,
141        };
142
143        let expected = TextCell {
144            width: DisplayWidth::from(2),
145            contents: vec![Fixed(90).paint("-"), Fixed(90).paint("-")].into(),
146        };
147
148        assert_eq!(expected, stati.render(&TestColours));
149    }
150
151    #[test]
152    fn git_new_changed() {
153        let stati = f::Git {
154            staged: f::GitStatus::New,
155            unstaged: f::GitStatus::Modified,
156        };
157
158        let expected = TextCell {
159            width: DisplayWidth::from(2),
160            contents: vec![Fixed(91).paint("N"), Fixed(92).paint("M")].into(),
161        };
162
163        assert_eq!(expected, stati.render(&TestColours));
164    }
165}