colours.rs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Provide standard values for the eight standard colours and custom
  2. // values for up to 256. There are terminals that can do the full RGB
  3. // spectrum, but for something as simple as discerning file types this
  4. // doesn't really seem worth it.
  5. // Bear in mind that the first eight (and their bold variants) are
  6. // user-definable and can look different on different terminals, but
  7. // the other 256 have their values fixed. Prefer using a fixed grey,
  8. // such as Fixed(244), to bold black, as bold black looks really weird
  9. // on some terminals.
  10. pub enum Colour {
  11. Black, Red, Green, Yellow, Blue, Purple, Cyan, White, Fixed(u8),
  12. }
  13. // These are the standard numeric sequences.
  14. // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  15. impl Colour {
  16. fn foreground_code(&self) -> String {
  17. match *self {
  18. Black => "30".to_string(),
  19. Red => "31".to_string(),
  20. Green => "32".to_string(),
  21. Yellow => "33".to_string(),
  22. Blue => "34".to_string(),
  23. Purple => "35".to_string(),
  24. Cyan => "36".to_string(),
  25. White => "37".to_string(),
  26. Fixed(num) => format!("38;5;{}", num),
  27. }
  28. }
  29. fn background_code(&self) -> String {
  30. match *self {
  31. Black => "40".to_string(),
  32. Red => "41".to_string(),
  33. Green => "42".to_string(),
  34. Yellow => "43".to_string(),
  35. Blue => "44".to_string(),
  36. Purple => "45".to_string(),
  37. Cyan => "46".to_string(),
  38. White => "47".to_string(),
  39. Fixed(num) => format!("48;5;{}", num),
  40. }
  41. }
  42. }
  43. // There are only three different styles: plain (no formatting), only
  44. // a foreground colour, and a catch-all for anything more complicated
  45. // than that. It's technically possible to write other cases such as
  46. // "bold foreground", but probably isn't worth writing all the code.
  47. pub enum Style {
  48. Plain,
  49. Foreground(Colour),
  50. Style(StyleStruct),
  51. }
  52. // Having a struct inside an enum is currently unfinished in Rust, but
  53. // should be put in there when that feature is complete.
  54. pub struct StyleStruct {
  55. foreground: Colour,
  56. background: Option<Colour>,
  57. bold: bool,
  58. underline: bool,
  59. }
  60. impl Style {
  61. pub fn paint(&self, input: &str) -> String {
  62. match *self {
  63. Plain => input.to_string(),
  64. Foreground(c) => c.paint(input),
  65. Style(s) => match s {
  66. StyleStruct { foreground, background, bold, underline } => {
  67. let bg = match background {
  68. Some(c) => format!("{};", c.background_code()),
  69. None => "".to_string()
  70. };
  71. let bo = if bold { "1;" } else { "" };
  72. let un = if underline { "4;" } else { "" };
  73. let painted = format!("\x1B[{}{}{}{}m{}\x1B[0m", bo, un, bg, foreground.foreground_code(), input.to_string());
  74. return painted.to_string();
  75. }
  76. }
  77. }
  78. }
  79. }
  80. impl Style {
  81. pub fn bold(&self) -> Style {
  82. match *self {
  83. Plain => Style(StyleStruct { foreground: White, background: None, bold: true, underline: false }),
  84. Foreground(c) => Style(StyleStruct { foreground: c, background: None, bold: true, underline: false }),
  85. Style(st) => Style(StyleStruct { foreground: st.foreground, background: st.background, bold: true, underline: st.underline }),
  86. }
  87. }
  88. pub fn underline(&self) -> Style {
  89. match *self {
  90. Plain => Style(StyleStruct { foreground: White, background: None, bold: false, underline: true }),
  91. Foreground(c) => Style(StyleStruct { foreground: c, background: None, bold: false, underline: true }),
  92. Style(st) => Style(StyleStruct { foreground: st.foreground, background: st.background, bold: st.bold, underline: true }),
  93. }
  94. }
  95. pub fn on(&self, background: Colour) -> Style {
  96. match *self {
  97. Plain => Style(StyleStruct { foreground: White, background: Some(background), bold: false, underline: false }),
  98. Foreground(c) => Style(StyleStruct { foreground: c, background: Some(background), bold: false, underline: false }),
  99. Style(st) => Style(StyleStruct { foreground: st.foreground, background: Some(background), bold: st.bold, underline: st.underline }),
  100. }
  101. }
  102. }
  103. impl Colour {
  104. // This is a short-cut so you don't have to use Blue.normal() just
  105. // to turn Blue into a Style. Annoyingly, this means that Blue and
  106. // Blue.normal() aren't of the same type, but this hasn't been an
  107. // issue so far.
  108. pub fn paint(&self, input: &str) -> String {
  109. let re = format!("\x1B[{}m{}\x1B[0m", self.foreground_code(), input);
  110. return re.to_string();
  111. }
  112. pub fn underline(&self) -> Style {
  113. Style(StyleStruct { foreground: *self, background: None, bold: false, underline: true })
  114. }
  115. pub fn bold(&self) -> Style {
  116. Style(StyleStruct { foreground: *self, background: None, bold: true, underline: false })
  117. }
  118. pub fn normal(&self) -> Style {
  119. Style(StyleStruct { foreground: *self, background: None, bold: false, underline: false })
  120. }
  121. pub fn on(&self, background: Colour) -> Style {
  122. Style(StyleStruct { foreground: *self, background: Some(background), bold: false, underline: false })
  123. }
  124. }
  125. pub fn strip_formatting(input: &String) -> String {
  126. let re = regex!("\x1B\\[.+?m");
  127. re.replace_all(input.as_slice(), "").to_string()
  128. }
  129. #[test]
  130. fn test_red() {
  131. let hi = Red.paint("hi");
  132. assert!(hi == "\x1B[31mhi\x1B[0m".to_string());
  133. }
  134. #[test]
  135. fn test_black() {
  136. let hi = Black.normal().paint("hi");
  137. assert!(hi == "\x1B[30mhi\x1B[0m".to_string());
  138. }
  139. #[test]
  140. fn test_yellow_bold() {
  141. let hi = Yellow.bold().paint("hi");
  142. assert!(hi == "\x1B[1;33mhi\x1B[0m".to_string());
  143. }
  144. #[test]
  145. fn test_yellow_bold_2() {
  146. let hi = Yellow.normal().bold().paint("hi");
  147. assert!(hi == "\x1B[1;33mhi\x1B[0m".to_string());
  148. }
  149. #[test]
  150. fn test_blue_underline() {
  151. let hi = Blue.underline().paint("hi");
  152. assert!(hi == "\x1B[4;34mhi\x1B[0m".to_string());
  153. }
  154. #[test]
  155. fn test_green_bold_underline() {
  156. let hi = Green.bold().underline().paint("hi");
  157. assert!(hi == "\x1B[1;4;32mhi\x1B[0m".to_string());
  158. }
  159. #[test]
  160. fn test_green_bold_underline_2() {
  161. let hi = Green.underline().bold().paint("hi");
  162. assert!(hi == "\x1B[1;4;32mhi\x1B[0m".to_string());
  163. }
  164. #[test]
  165. fn test_purple_on_white() {
  166. let hi = Purple.on(White).paint("hi");
  167. assert!(hi == "\x1B[47;35mhi\x1B[0m".to_string());
  168. }
  169. #[test]
  170. fn test_purple_on_white_2() {
  171. let hi = Purple.normal().on(White).paint("hi");
  172. assert!(hi == "\x1B[47;35mhi\x1B[0m".to_string());
  173. }
  174. #[test]
  175. fn test_cyan_bold_on_white() {
  176. let hi = Cyan.bold().on(White).paint("hi");
  177. assert!(hi == "\x1B[1;47;36mhi\x1B[0m".to_string());
  178. }
  179. #[test]
  180. fn test_cyan_underline_on_white() {
  181. let hi = Cyan.underline().on(White).paint("hi");
  182. assert!(hi == "\x1B[4;47;36mhi\x1B[0m".to_string());
  183. }
  184. #[test]
  185. fn test_cyan_bold_underline_on_white() {
  186. let hi = Cyan.bold().underline().on(White).paint("hi");
  187. assert!(hi == "\x1B[1;4;47;36mhi\x1B[0m".to_string());
  188. }
  189. #[test]
  190. fn test_cyan_underline_bold_on_white() {
  191. let hi = Cyan.underline().bold().on(White).paint("hi");
  192. assert!(hi == "\x1B[1;4;47;36mhi\x1B[0m".to_string());
  193. }
  194. #[test]
  195. fn test_fixed() {
  196. let hi = Fixed(100).paint("hi");
  197. assert!(hi == "\x1B[38;5;100mhi\x1B[0m".to_string());
  198. }
  199. #[test]
  200. fn test_fixed_on_purple() {
  201. let hi = Fixed(100).on(Purple).paint("hi");
  202. assert!(hi == "\x1B[45;38;5;100mhi\x1B[0m".to_string());
  203. }
  204. #[test]
  205. fn test_fixed_on_fixed() {
  206. let hi = Fixed(100).on(Fixed(200)).paint("hi");
  207. assert!(hi == "\x1B[48;5;200;38;5;100mhi\x1B[0m".to_string());
  208. }