File size: 3,476 Bytes
d90101d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
136
137
138
139
140
141
142
143
144
145
//! ZSH prompt styling utilities.
//!
//! This module provides helpers for generating ZSH-native prompt escape
//! sequences. Unlike ANSI escape codes, ZSH prompt escapes are interpreted by
//! ZSH's prompt renderer, making them work correctly in PROMPT and RPROMPT
//! contexts.

use std::fmt::{self, Display};

/// ZSH prompt color using 256-color palette.
///
/// Maps to ZSH's `%F{N}` prompt escape sequence where N is a color code.
#[derive(Debug, Clone, Copy)]
pub struct ZshColor(u8);

impl ZshColor {
    /// White (color 15)
    pub const WHITE: Self = Self(15);
    /// Cyan (color 134)
    pub const CYAN: Self = Self(134);
    /// Green (color 2)
    pub const GREEN: Self = Self(2);
    /// Yellow (color 3)
    pub const YELLOW: Self = Self(3);
    /// Dimmed gray (color 240)
    pub const DIMMED: Self = Self(240);

    /// Creates a color from a 256-color palette value.
    #[cfg(test)]
    pub const fn new(value: u8) -> Self {
        Self(value)
    }
}

impl Display for ZshColor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A styled string for ZSH prompts.
///
/// Wraps text with ZSH prompt escape sequences for colors and formatting.
#[derive(Debug, Clone)]
pub struct ZshStyled<'a> {
    text: &'a str,
    fg: Option<ZshColor>,
    bold: bool,
}

impl<'a> ZshStyled<'a> {
    /// Creates a new styled string with the given text.
    pub fn new(text: &'a str) -> Self {
        Self { text, fg: None, bold: false }
    }

    /// Sets the foreground color.
    pub fn fg(mut self, color: ZshColor) -> Self {
        self.fg = Some(color);
        self
    }

    /// Makes the text bold.
    pub fn bold(mut self) -> Self {
        self.bold = true;
        self
    }
}

impl Display for ZshStyled<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Opening escapes
        if self.bold {
            write!(f, "%B")?;
        }
        if let Some(ref color) = self.fg {
            write!(f, "%F{{{}}}", color)?;
        }

        // Text content
        write!(f, "{}", self.text)?;

        // Closing escapes (in reverse order)
        if self.fg.is_some() {
            write!(f, "%f")?;
        }
        if self.bold {
            write!(f, "%b")?;
        }

        Ok(())
    }
}

/// Extension trait for styling strings for ZSH prompts.
pub trait ZshStyle {
    /// Creates a ZSH-styled wrapper for this string.
    fn zsh(&self) -> ZshStyled<'_>;
}

impl ZshStyle for str {
    fn zsh(&self) -> ZshStyled<'_> {
        ZshStyled::new(self)
    }
}

impl ZshStyle for String {
    fn zsh(&self) -> ZshStyled<'_> {
        ZshStyled::new(self.as_str())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_plain_text() {
        let actual = "hello".zsh().to_string();
        let expected = "hello";
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bold() {
        let actual = "hello".zsh().bold().to_string();
        let expected = "%Bhello%b";
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bold_and_color() {
        let actual = "hello".zsh().bold().fg(ZshColor::WHITE).to_string();
        let expected = "%B%F{15}hello%f%b";
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_fixed_color() {
        let actual = "hello".zsh().fg(ZshColor::new(240)).to_string();
        let expected = "%F{240}hello%f";
        assert_eq!(actual, expected);
    }
}