1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::{Environment, Icon, IconMode, Pretty, SimpleBlock, Style};
use nix::{
    sys::stat,
    unistd::{self, AccessFlags},
};
use std::path::{Path, PathBuf};

enum State {
    Writeable,
    Readable,
    Unavailable,
}

impl Icon for State {
    fn icon(&self, mode: &IconMode) -> &'static str {
        use IconMode::*;
        match self {
            Self::Writeable => "",
            Self::Readable => match mode {
                Text => "R/O",
                Icons | MinimalIcons => " ",
            },
            Self::Unavailable => match mode {
                Text => "DEL",
                Icons | MinimalIcons => "󰇾 ",
            },
        }
    }
}

impl Pretty for State {
    fn pretty(&self, mode: &IconMode) -> Option<String> {
        Some(self.icon(mode).red().with_reset().to_string())
    }
}

fn get_state(work_dir: &Path) -> State {
    let read_only = unistd::access(work_dir, AccessFlags::W_OK).is_err();
    let Ok(stat_dot) = stat::stat(".") else {
        return State::Unavailable;
    };
    let Ok(stat_pwd) = stat::stat(work_dir) else {
        return State::Unavailable;
    };
    if (stat_dot.st_dev, stat_dot.st_ino) != (stat_pwd.st_dev, stat_pwd.st_ino) {
        State::Unavailable
    } else if read_only {
        State::Readable
    } else {
        State::Writeable
    }
}

pub struct Workdir {
    work_dir: PathBuf,
    git_tree: Option<PathBuf>,
    current_home: Option<(PathBuf, String)>,
    state: State,
}

impl SimpleBlock for Workdir {
    fn extend(self: Box<Self>) -> Box<dyn Pretty> {
        self
    }
}

impl From<&Environment> for Workdir {
    fn from(env: &Environment) -> Self {
        let work_dir = env.work_dir.clone();
        let git_tree = env.git_tree.clone();
        let current_home = env.current_home.clone();
        let state = get_state(&work_dir);

        Workdir {
            work_dir,
            git_tree,
            current_home,
            state,
        }
    }
}

impl Pretty for Workdir {
    fn pretty(&self, mode: &IconMode) -> Option<String> {
        let (middle, highlighted) = match (&self.git_tree, &self.current_home) {
            (Some(git_root), Some((home_root, _))) => {
                if home_root.starts_with(git_root) {
                    (None, self.work_dir.strip_prefix(home_root).ok())
                } else {
                    (
                        git_root.strip_prefix(home_root).ok(),
                        self.work_dir.strip_prefix(git_root).ok(),
                    )
                }
            }
            (Some(git_root), None) => (
                Some(git_root.as_path()),
                self.work_dir.strip_prefix(git_root).ok(),
            ),
            (None, Some((home_root, _))) => (self.work_dir.strip_prefix(home_root).ok(), None),
            (None, None) => (Some(self.work_dir.as_path()), None),
        };

        let home_str = self.current_home.as_ref().map(|(_, user)| {
            format!("~{}", user)
                .visible()
                .yellow()
                .bold()
                .with_reset()
                .invisible()
                .to_string()
        });

        let middle_str = middle.and_then(Path::to_str).map(ToString::to_string);

        let highlighted_str = highlighted.and_then(Path::to_str).map(|s| {
            format!("/{}", s)
                .visible()
                .cyan()
                .with_reset()
                .invisible()
                .to_string()
        });

        let work_dir = [home_str, middle_str]
            .into_iter()
            .filter(|x| matches!(x, Some(q) if !q.is_empty()))
            .map(Option::unwrap)
            .collect::<Vec<_>>()
            .join("/")
            + &highlighted_str.unwrap_or_default();

        Some(format!("{}{}", self.state.pretty(mode).unwrap(), work_dir))
    }
}