Skip to content

Commit 23448a0

Browse files
authored
Merge pull request #131 from clj-commons/hls/reset-after-faint
Fix faint, add pout
2 parents eb68c69 + 6cf5fa6 commit 23448a0

File tree

5 files changed

+73
-46
lines changed

5 files changed

+73
-46
lines changed

CHANGES.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
## 3.2.0 - UNRELEASED
22

3-
Added `clj-commons.format.exceptions/default-frame-rules` with the defaults for `*default-frame-rules*`
4-
which makes it much easier to override the rules.
3+
Added `clj-commons.ansi/pout` to replace the `pcompose` function; they are identical, but the `pout` name makes more
4+
sense, given that `perr` exists.
5+
6+
Changed how `clj-commons.ansi/compose` creates ANSI SGR strings; this works around an issue in many terminal emulators
7+
where changing boldness from faint to normal, or faint to bold, is not implemented correctly. `compose` now resets fonts
8+
before each font change, which allows such transitions to render correctly.
9+
10+
Added `clj-commons.format.exceptions/default-frame-rules` to supply defaults for `*default-frame-rules*`
11+
which makes it much easier to override the default rules.
512

613
Added function `clj-commons.format.exceptions/format-stack-trace-element` which can be used to convert a Java
7-
StackTraceElement into demangled, readable string, using the same logic used by `format-exception.`
14+
StackTraceElement into demangled, readable string, using the same logic as `format-exception.`
15+
16+
[Closed Issues](https://github.com/clj-commons/pretty/milestone/52?closed=1)
817

918
## 3.1.1 - 22 Aug 2024
1019

deps.edn

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
99
org.clojure/core.async {:mvn/version "1.6.681"}
1010
nubank/matcher-combinators {:mvn/version "3.9.1"}
11+
io.github.tonsky/clj-reload {:mvn/version "0.7.1"}
1112
io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
1213
:git/sha "dfb30dd"}}
1314
:jvm-opts ["-Dclj-commons.ansi.enabled=true"]

src/clj_commons/ansi.clj

+44-30
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
(str csi sgr))
5656

5757
(def ^:private font-terms
58+
;; Map a keyword to a tuple of characteristic and SGR parameter value.
59+
;; We track the current value for each characteristic.
5860
(reduce merge
5961
{:bold [:bold "1"]
6062
:plain [:bold "22"]
@@ -83,11 +85,15 @@
8385
current-value)))
8486

8587
(defn- compose-font
86-
^String [active current]
88+
"Uses values in current to build a font string that will reset all fonts characteristics then,
89+
as necessary, add back needed font characteristics."
90+
^String [current]
8791
(when-color-enabled
88-
(let [codes (keep #(delta active current %) [:foreground :background :bold :italic :inverse :underlined])]
89-
(when (seq codes)
90-
(str csi (str/join ";" codes) sgr)))))
92+
(let [codes (keep #(get current %) [:foreground :background :bold :italic :inverse :underlined])]
93+
(if (seq codes)
94+
(str csi "0;" (str/join ";" codes) sgr)
95+
;; there were active characteristics, but current has none, so just reset font characteristics
96+
reset-font))))
9197

9298
(defn- split-font-def*
9399
[font-def]
@@ -138,7 +144,9 @@
138144
(throw (ex-info "invalid span declaration"
139145
{:font-decl value}))))
140146

141-
(defn- blank? [value]
147+
(defn- nil-or-empty-string?
148+
"True if an empty string, or nil; false otherwise, such as for numbers, etc."
149+
[value]
142150
(or (nil? value)
143151
(= "" value)))
144152

@@ -152,7 +160,8 @@
152160
(mod x 2)
153161
0))))
154162

155-
(defn- apply-padding [terms pad width actual-width]
163+
(defn- apply-padding
164+
[terms pad width actual-width]
156165
(let [padding-needed (- width actual-width)
157166
left-padding (case pad
158167
(:left nil) padding-needed
@@ -197,7 +206,7 @@
197206
[coll *width]
198207
(let [f (fn reducer [result input]
199208
(cond
200-
(blank? input)
209+
(nil-or-empty-string? input)
201210
result
202211

203212
(vector? input)
@@ -225,7 +234,7 @@
225234
(defn- collect-markup
226235
[state input]
227236
(cond
228-
(blank? input)
237+
(nil-or-empty-string? input)
229238
state
230239

231240
(vector? input)
@@ -236,12 +245,12 @@
236245
;; Normal (no width tracking)
237246
(let [{:keys [current]} state]
238247
(-> (reduce collect-markup
239-
(-> state
240-
(update :current update-font-data-from-font-def font)
241-
(update :stack conj current))
248+
(update state :current update-font-data-from-font-def font)
242249
inputs)
243-
(assoc :current current)
244-
(update :stack pop)))))
250+
;; At the end of the vector, return current (but not the active)
251+
;; to what it was previously. We leave active alone until we're about
252+
;; to output.
253+
(assoc :current current)))))
245254

246255
;; Lists, lazy-lists, etc: processed recursively
247256
(sequential? input)
@@ -251,27 +260,21 @@
251260
(let [{:keys [active current ^StringBuilder buffer]} state
252261
state' (if (= active current)
253262
state
254-
(let [font-str (compose-font active current)]
263+
(let [font-str (compose-font current)]
255264
(when font-str
256265
(.append buffer font-str))
257266
(cond-> (assoc state :active current)
258267
;; Signal that a reset is needed at the very end
259-
font-str (assoc :dirty? true))))]
268+
font-str
269+
(assoc :dirty? (not= font-str reset-font)))))]
260270
(.append buffer (str input))
261271
state')))
262272

263273
(defn- compose*
264274
[inputs]
265-
(let [initial-font {:foreground "39"
266-
:background "49"
267-
:bold "22"
268-
:italic "23"
269-
:inverse "27"
270-
:underlined "24"}
271-
buffer (StringBuilder. 100)
272-
{:keys [dirty?]} (collect-markup {:stack []
273-
:active initial-font
274-
:current initial-font
275+
(let [buffer (StringBuilder. 100)
276+
{:keys [dirty?]} (collect-markup {:active {}
277+
:current {}
275278
:buffer buffer}
276279
inputs)]
277280
(when dirty?
@@ -289,8 +292,8 @@
289292
`map` or `for` to be mixed into the composed string seamlessly.
290293
291294
Nested vectors represent _spans_, a sequence of values with a specific visual representation.
292-
The first element in a span vector declares the visual properties of the span: the color (including
293-
other characteristics such as bold or underline), and the width and padding (described later).
295+
The first element in a span vector declares the visual properties of the span: the font color
296+
and other font characteristics, and the width and padding (described later).
294297
Spans may be nested.
295298
296299
The declaration is usually a keyword, to define just the font.
@@ -322,7 +325,7 @@
322325
Font defs apply on top of the font def of the enclosing span, and the outer span's font def
323326
is restored at the end of the inner span, e.g. `[:red \" RED \" [:bold \"RED/BOLD\"] \" RED \"]`.
324327
325-
Alternately, a font def may be a vector of individual keyword, e.g., `[[:bold :red] ...]` rather than
328+
Alternately, a font def may be a vector of individual keywords, e.g., `[[:bold :red] ...]` rather than
326329
`[:bold.red ...]`. This works better when the exact font characteristics are determined
327330
dynamically.
328331
@@ -370,9 +373,18 @@
370373
[& inputs]
371374
(compose* inputs))
372375

373-
(defn pcompose
376+
(defn pout
374377
"Composes its inputs as with [[compose]] and then prints the results, with a newline."
375-
{:added "2.2"}
378+
{:added "3.2"}
379+
[& inputs]
380+
(println (compose* inputs)))
381+
382+
(defn pcompose
383+
"Composes its inputs as with [[compose]] and then prints the results, with a newline.
384+
385+
Deprecated: use [[pout]] instead."
386+
{:added "2.2"
387+
:deprecated "3.2.0"}
376388
[& inputs]
377389
(println (compose* inputs)))
378390

@@ -382,3 +394,5 @@
382394
[& inputs]
383395
(binding [*out* *err*]
384396
(println (compose* inputs))))
397+
398+

test/clj_commons/ansi_test.clj

+8-7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
["Simple"]
4545
"Simple"
4646

47+
4748
["String" \space :keyword \space 'symbol \space 123 \space 44.5]
4849
"String :keyword symbol 123 44.5"
4950

@@ -75,7 +76,7 @@
7576
" are operating at "
7677
[:green "98.7%"]
7778
"."]
78-
"Notice: the [CSI]33mshields[CSI]39m are operating at [CSI]32m98.7%[CSI]39m.[CSI]m"
79+
"Notice: the [CSI]0;33mshields[CSI]m are operating at [CSI]0;32m98.7%[CSI]m."
7980

8081
;; nil is allowed (this is used when formatting is optional, such as the fonts in exceptions).
8182

@@ -89,13 +90,13 @@
8990
["NORMAL"
9091
[:red "-RED"]
9192
[:bright-red "-BR/RED"]]
92-
"NORMAL[CSI]31m-RED[CSI]91m-BR/RED[CSI]m"
93+
"NORMAL[CSI]0;31m-RED[CSI]0;91m-BR/RED[CSI]m"
9394

9495
["NORMAL-"
9596
[:inverse "-INVERSE" [:bold "-INV/BOLD"]]
9697
[:inverse.bold "-INV/BOLD"]
9798
"-NORMAL"]
98-
"NORMAL-[CSI]7m-INVERSE[CSI]1m-INV/BOLD-INV/BOLD[CSI]22;27m-NORMAL[CSI]m"
99+
"NORMAL-[CSI]0;7m-INVERSE[CSI]0;1;7m-INV/BOLD-INV/BOLD[CSI]m-NORMAL"
99100

100101

101102
;; Basic tests for width:
@@ -117,8 +118,8 @@
117118
[{:width 10
118119
:font :red} "BBB"]
119120
"|")
120-
"START |[CSI]32mAAA [CSI]39m|[CSI]31m BBB[CSI]39m|[CSI]m"
121-
; 0123456789 0123456789
121+
"START |[CSI]0;32mAAA [CSI]m|[CSI]0;31m BBB[CSI]m|"
122+
; 0123456789 0123456789
122123

123124
'("START |"
124125
[{:width 10
@@ -128,8 +129,8 @@
128129
[{:width 10
129130
:font :red} "XYZ"]
130131
"|")
131-
"START |[CSI]32mAB[CSI]34mC[CSI]32m [CSI]39m|[CSI]31m XYZ[CSI]39m|[CSI]m"
132-
; 01 2 3456789 0123456789
132+
"START |[CSI]0;32mAB[CSI]0;34mC[CSI]0;32m [CSI]m|[CSI]0;31m XYZ[CSI]m|"
133+
; 01 2 3456789 0123456789
133134

134135
;; Only pads, never truncates
135136

test/clj_commons/binary_test.clj

+8-6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
(deftest binary-fonts
4444
(let [byte-data (byte-array [0x59 0x65 073 0x20 0x4e 0x00 0x00 0x09 0x80 0xff])]
45-
(is (= ["{90}0000:{39} {36}59{39} {36}65{39} {36}3B{39} {32}20{39} {36}4E{39} {90}00{39} {90}00{39} {32}09{39} {33}80{39} {33}FF{39} |{36}Ye;{32} {36}N{90}••{32}_{33}××{39} |{}"]
45+
(is (= ["{0;90}0000:{} {0;36}59{} {0;36}65{} {0;36}3B{} {0;32}20{} {0;36}4E{} {0;90}00{} {0;90}00{} {0;32}09{} {0;33}80{} {0;33}FF{} |{0;36}Ye;{0;32} {0;36}N{0;90}••{0;32}_{0;33}××{} |"]
4646
(-> (b/format-binary byte-data {:ascii true})
4747
fixup-sgr
4848
string/split-lines)))))
@@ -96,23 +96,25 @@
9696

9797
(deftest deltas-with-fonts
9898
(are [expected actual expected-output]
99-
(= expected-output
99+
(match? expected-output
100100
(format-binary-delta expected actual))
101101

102102
"123\t" "123\n"
103+
;; {} is reset font
104+
;; 0 is reset font (as a prefix)
103105
;; 90 is bright black for offset
104106
;; 36 is cyan for printable ASCII
105107
;; 32 is green for whitespace
106108
;; 102 is bright green backround,
107109
;; 101 is bright red background
108-
["{90}0000:{39} {36}31{39} {36}32{39} {36}33{39} {32;102}09{39;49} | {36}31{39} {36}32{39} {36}33{39} {32;101}0A{}"]
110+
["{0;90}0000:{} {0;36}31{} {0;36}32{} {0;36}33{} {0;32;102}09{} | {0;36}31{} {0;36}32{} {0;36}33{} {0;32;101}0A{}"]
109111

110112
"1234" "12"
111-
["{90}0000:{39} {36}31{39} {36}32{39} {36;102}33{39;49} {36;102}34{39;49} | {36}31{39} {36}32{39} {101}--{49} {101}--{}"]
113+
["{0;90}0000:{} {0;36}31{} {0;36}32{} {0;36;102}33{} {0;36;102}34{} | {0;36}31{} {0;36}32{} {0;101}--{} {0;101}--{}"]
112114

113115
;; 2 is faint for non-printable
114116
"\u001B" "\u001Cxyz"
115-
["{90}0000:{39} {32;102;2}1B{39;49;22} {102}--{49} {102}--{49} {102}--{49} | {32;101;2}1C{39;49;22} {36;101}78{39;49} {36;101}79{39;49} {36;101}7A{}"]))
116-
117+
["{0;90}0000:{} {0;32;102;2}1B{} {0;102}--{} {0;102}--{} {0;102}--{} | {0;32;101;2}1C{} {0;36;101}78{} {0;36;101}79{} {0;36;101}7A{}"]
118+
))
117119

118120

0 commit comments

Comments
 (0)