Skip to content

Commit

Permalink
Merge pull request #131 from clj-commons/hls/reset-after-faint
Browse files Browse the repository at this point in the history
Fix faint, add pout
  • Loading branch information
hlship authored Sep 20, 2024
2 parents eb68c69 + 6cf5fa6 commit 23448a0
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 46 deletions.
15 changes: 12 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
## 3.2.0 - UNRELEASED

Added `clj-commons.format.exceptions/default-frame-rules` with the defaults for `*default-frame-rules*`
which makes it much easier to override the rules.
Added `clj-commons.ansi/pout` to replace the `pcompose` function; they are identical, but the `pout` name makes more
sense, given that `perr` exists.

Changed how `clj-commons.ansi/compose` creates ANSI SGR strings; this works around an issue in many terminal emulators
where changing boldness from faint to normal, or faint to bold, is not implemented correctly. `compose` now resets fonts
before each font change, which allows such transitions to render correctly.

Added `clj-commons.format.exceptions/default-frame-rules` to supply defaults for `*default-frame-rules*`
which makes it much easier to override the default rules.

Added function `clj-commons.format.exceptions/format-stack-trace-element` which can be used to convert a Java
StackTraceElement into demangled, readable string, using the same logic used by `format-exception.`
StackTraceElement into demangled, readable string, using the same logic as `format-exception.`

[Closed Issues](https://github.com/clj-commons/pretty/milestone/52?closed=1)

## 3.1.1 - 22 Aug 2024

Expand Down
1 change: 1 addition & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
org.clojure/core.async {:mvn/version "1.6.681"}
nubank/matcher-combinators {:mvn/version "3.9.1"}
io.github.tonsky/clj-reload {:mvn/version "0.7.1"}
io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
:git/sha "dfb30dd"}}
:jvm-opts ["-Dclj-commons.ansi.enabled=true"]
Expand Down
74 changes: 44 additions & 30 deletions src/clj_commons/ansi.clj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
(str csi sgr))

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

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

(defn- split-font-def*
[font-def]
Expand Down Expand Up @@ -138,7 +144,9 @@
(throw (ex-info "invalid span declaration"
{:font-decl value}))))

(defn- blank? [value]
(defn- nil-or-empty-string?
"True if an empty string, or nil; false otherwise, such as for numbers, etc."
[value]
(or (nil? value)
(= "" value)))

Expand All @@ -152,7 +160,8 @@
(mod x 2)
0))))

(defn- apply-padding [terms pad width actual-width]
(defn- apply-padding
[terms pad width actual-width]
(let [padding-needed (- width actual-width)
left-padding (case pad
(:left nil) padding-needed
Expand Down Expand Up @@ -197,7 +206,7 @@
[coll *width]
(let [f (fn reducer [result input]
(cond
(blank? input)
(nil-or-empty-string? input)
result

(vector? input)
Expand Down Expand Up @@ -225,7 +234,7 @@
(defn- collect-markup
[state input]
(cond
(blank? input)
(nil-or-empty-string? input)
state

(vector? input)
Expand All @@ -236,12 +245,12 @@
;; Normal (no width tracking)
(let [{:keys [current]} state]
(-> (reduce collect-markup
(-> state
(update :current update-font-data-from-font-def font)
(update :stack conj current))
(update state :current update-font-data-from-font-def font)
inputs)
(assoc :current current)
(update :stack pop)))))
;; At the end of the vector, return current (but not the active)
;; to what it was previously. We leave active alone until we're about
;; to output.
(assoc :current current)))))

;; Lists, lazy-lists, etc: processed recursively
(sequential? input)
Expand All @@ -251,27 +260,21 @@
(let [{:keys [active current ^StringBuilder buffer]} state
state' (if (= active current)
state
(let [font-str (compose-font active current)]
(let [font-str (compose-font current)]
(when font-str
(.append buffer font-str))
(cond-> (assoc state :active current)
;; Signal that a reset is needed at the very end
font-str (assoc :dirty? true))))]
font-str
(assoc :dirty? (not= font-str reset-font)))))]
(.append buffer (str input))
state')))

(defn- compose*
[inputs]
(let [initial-font {:foreground "39"
:background "49"
:bold "22"
:italic "23"
:inverse "27"
:underlined "24"}
buffer (StringBuilder. 100)
{:keys [dirty?]} (collect-markup {:stack []
:active initial-font
:current initial-font
(let [buffer (StringBuilder. 100)
{:keys [dirty?]} (collect-markup {:active {}
:current {}
:buffer buffer}
inputs)]
(when dirty?
Expand All @@ -289,8 +292,8 @@
`map` or `for` to be mixed into the composed string seamlessly.
Nested vectors represent _spans_, a sequence of values with a specific visual representation.
The first element in a span vector declares the visual properties of the span: the color (including
other characteristics such as bold or underline), and the width and padding (described later).
The first element in a span vector declares the visual properties of the span: the font color
and other font characteristics, and the width and padding (described later).
Spans may be nested.
The declaration is usually a keyword, to define just the font.
Expand Down Expand Up @@ -322,7 +325,7 @@
Font defs apply on top of the font def of the enclosing span, and the outer span's font def
is restored at the end of the inner span, e.g. `[:red \" RED \" [:bold \"RED/BOLD\"] \" RED \"]`.
Alternately, a font def may be a vector of individual keyword, e.g., `[[:bold :red] ...]` rather than
Alternately, a font def may be a vector of individual keywords, e.g., `[[:bold :red] ...]` rather than
`[:bold.red ...]`. This works better when the exact font characteristics are determined
dynamically.
Expand Down Expand Up @@ -370,9 +373,18 @@
[& inputs]
(compose* inputs))

(defn pcompose
(defn pout
"Composes its inputs as with [[compose]] and then prints the results, with a newline."
{:added "2.2"}
{:added "3.2"}
[& inputs]
(println (compose* inputs)))

(defn pcompose
"Composes its inputs as with [[compose]] and then prints the results, with a newline.
Deprecated: use [[pout]] instead."
{:added "2.2"
:deprecated "3.2.0"}
[& inputs]
(println (compose* inputs)))

Expand All @@ -382,3 +394,5 @@
[& inputs]
(binding [*out* *err*]
(println (compose* inputs))))


15 changes: 8 additions & 7 deletions test/clj_commons/ansi_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
["Simple"]
"Simple"


["String" \space :keyword \space 'symbol \space 123 \space 44.5]
"String :keyword symbol 123 44.5"

Expand Down Expand Up @@ -75,7 +76,7 @@
" are operating at "
[:green "98.7%"]
"."]
"Notice: the [CSI]33mshields[CSI]39m are operating at [CSI]32m98.7%[CSI]39m.[CSI]m"
"Notice: the [CSI]0;33mshields[CSI]m are operating at [CSI]0;32m98.7%[CSI]m."

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

Expand All @@ -89,13 +90,13 @@
["NORMAL"
[:red "-RED"]
[:bright-red "-BR/RED"]]
"NORMAL[CSI]31m-RED[CSI]91m-BR/RED[CSI]m"
"NORMAL[CSI]0;31m-RED[CSI]0;91m-BR/RED[CSI]m"

["NORMAL-"
[:inverse "-INVERSE" [:bold "-INV/BOLD"]]
[:inverse.bold "-INV/BOLD"]
"-NORMAL"]
"NORMAL-[CSI]7m-INVERSE[CSI]1m-INV/BOLD-INV/BOLD[CSI]22;27m-NORMAL[CSI]m"
"NORMAL-[CSI]0;7m-INVERSE[CSI]0;1;7m-INV/BOLD-INV/BOLD[CSI]m-NORMAL"


;; Basic tests for width:
Expand All @@ -117,8 +118,8 @@
[{:width 10
:font :red} "BBB"]
"|")
"START |[CSI]32mAAA [CSI]39m|[CSI]31m BBB[CSI]39m|[CSI]m"
; 0123456789 0123456789
"START |[CSI]0;32mAAA [CSI]m|[CSI]0;31m BBB[CSI]m|"
; 0123456789 0123456789

'("START |"
[{:width 10
Expand All @@ -128,8 +129,8 @@
[{:width 10
:font :red} "XYZ"]
"|")
"START |[CSI]32mAB[CSI]34mC[CSI]32m [CSI]39m|[CSI]31m XYZ[CSI]39m|[CSI]m"
; 01 2 3456789 0123456789
"START |[CSI]0;32mAB[CSI]0;34mC[CSI]0;32m [CSI]m|[CSI]0;31m XYZ[CSI]m|"
; 01 2 3456789 0123456789

;; Only pads, never truncates

Expand Down
14 changes: 8 additions & 6 deletions test/clj_commons/binary_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

(deftest binary-fonts
(let [byte-data (byte-array [0x59 0x65 073 0x20 0x4e 0x00 0x00 0x09 0x80 0xff])]
(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} |{}"]
(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}××{} |"]
(-> (b/format-binary byte-data {:ascii true})
fixup-sgr
string/split-lines)))))
Expand Down Expand Up @@ -96,23 +96,25 @@

(deftest deltas-with-fonts
(are [expected actual expected-output]
(= expected-output
(match? expected-output
(format-binary-delta expected actual))

"123\t" "123\n"
;; {} is reset font
;; 0 is reset font (as a prefix)
;; 90 is bright black for offset
;; 36 is cyan for printable ASCII
;; 32 is green for whitespace
;; 102 is bright green backround,
;; 101 is bright red background
["{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{}"]
["{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{}"]

"1234" "12"
["{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}--{}"]
["{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}--{}"]

;; 2 is faint for non-printable
"\u001B" "\u001Cxyz"
["{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{}"]))

["{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{}"]
))


0 comments on commit 23448a0

Please sign in to comment.