/
ansi.clj
99 lines (85 loc) · 3.84 KB
/
ansi.clj
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
(ns io.aviso.ansi
"Help with generating textual output that includes ANSI escape codes for formatting."
(:import
[java.util.regex Pattern])
(:require
[clojure.string :as str]))
(def ^:const csi
"The control sequence initiator: `ESC [`"
"\u001b[")
;; select graphic rendition
(def ^:const sgr
"The Select Graphic Rendition suffix: m"
"m")
(def ^:const
reset-font
"Resets the font, clearing bold, italic, color, and background color."
(str csi sgr))
(defmacro ^:private def-sgr-const
"Utility for defining a font-modifying constant."
[symbol-name color-name & codes]
`(def ~(vary-meta (symbol symbol-name) assoc :const true)
~(format "Constant for ANSI code to enable %s text." color-name)
(str csi ~(str/join ";" codes) sgr)))
(defmacro ^:private def-sgr-fn
"Utility for creating a function that enables some combination of SGR codes around some text, but resets
the font after the text."
[fn-name color-name & codes]
(let [arg 'text]
`(defn ~(symbol fn-name)
~(format "Wraps the provided text with ANSI codes to render as %s text." color-name)
[~arg]
(str csi ~(str/join ";" codes) sgr ~arg reset-font))))
;;; Define functions and constants for each color. The functions accept a string
;;; and wrap it with the ANSI codes to set up a rendition before the text,
;;; and reset the rendition fully back to normal after.
;;; The constants enable the rendition, and require the reset-font value to
;;; return to normal.
;;; For each color C:
;;; - functions:
;;; - C: change text to that color (e.g., "green")
;;; - C-bg: change background to that color (e.g., "green-bg")
;;; - bold-C: change text to bold variation of color (e.g., "bold-green")
;;; - bold-C-bg: change background to bold variation of color (e.g., "bold-green-bg")
;;; - constants
;;; - C-font: enable text in that color (e.g., "green-font")
;;; - C-bg-font: enable background in that color (e.g., "green-bg-font")
;;; - bold-C-font; enable bold text in that color (e.g., "bold-green-font")
;;; - bold-C-bg-font; enable background in that bold color (e.g., "bold-green-bg-font")
(defmacro ^:private define-colors
[]
`(do
~@(map-indexed
(fn [index color-name]
`(do
(def-sgr-fn ~color-name ~color-name ~(+ 30 index))
(def-sgr-fn ~(str color-name "-bg") ~(str color-name " background") ~(+ 40 index))
(def-sgr-fn ~(str "bold-" color-name) ~(str "bold " color-name) 1 ~(+ 30 index))
(def-sgr-fn ~(str "bold-" color-name "-bg") ~(str "bold " color-name " background") 1 ~(+ 40 index))
(def-sgr-const ~(str color-name "-font") ~color-name ~(+ 30 index))
(def-sgr-const ~(str color-name "-bg-font") ~(str color-name " background") ~(+ 40 index))
(def-sgr-const ~(str "bold-" color-name "-font") ~(str "bold " color-name) 1 ~(+ 30 index))
(def-sgr-const ~(str "bold-" color-name "-bg-font") ~(str "bold " color-name " background") 1 ~(+ 40 index))))
["black" "red" "green" "yellow" "blue" "magenta" "cyan" "white"])))
(define-colors)
;; ANSI defines quite a few more, but we're limiting to those that display properly in the
;; Cursive REPL.
(defmacro ^:private define-fonts
[]
`(do
~@(for [[font-name code] [['bold 1]
['italic 3]
['inverse 7]]]
`(do
(def-sgr-fn ~font-name ~font-name ~code)
(def-sgr-const ~(str font-name "-font") ~font-name ~code)))))
(define-fonts)
(def ^:const ^:private ansi-pattern (Pattern/compile "\\e\\[.*?m"))
(defn ^String strip-ansi
"Removes ANSI codes from a string, returning just the raw text."
[string]
(str/replace string ansi-pattern ""))
(defn visual-length
"Returns the length of the string, with ANSI codes stripped out."
[string]
(-> string strip-ansi .length))