Repository: greglook/puget (clojure)

Issue:
colors start at beginning of line instead of beginning of element
Hello!  First, thanks so much for puget, it is a wonderful library!

While experimenting with using background colors with puget, I noticed an interesting behavior.

If I `cprint` with the default `:color-scheme`,
```Clojure
(require '[puget.printer :as puget])

(def t [nil true \space "string"
        {:omega 123N :alpha '(func x y) :gamma 3.14159}
        #{\a "heterogeneous" :set}
        (java.util.Currency/getInstance "USD")
        (java.util.Date.)
        (java.util.UUID/randomUUID)])

(puget/cprint t)
```
I get similar results to the README screenshot:
![image](https://user-images.githubusercontent.com/967328/65810238-05ba5980-e176-11e9-974a-0d1a2ba123a2.png)

But if I try background colors:
```Clojure
(defn bg-cprint [ expr ]
  (puget/cprint expr
                {:color-scheme
                 {:delimiter [:black :bold :bg-red]
                  :tag       [:black :bold :bg-red]
                  :nil       [:white :bold :bg-black]
                  :boolean   [:black :bg-green]
                  :number    [:black :bg-cyan]
                  :string    [:black :bold :bg-magenta]
                  :character [:black :bold :bg-magenta]
                  :keyword   [:black :bold :bg-yellow]
                  :symbol    nil
                  :function-symbol [:black :bold :bg-blue]
                  :class-delimiter [:black :bg-blue]
                  :class-name      [:black :bold :bg-blue]}}))
(bg-cprint t)
```
notice that the color starts at the beginning of the line rather than the beginning of the element:
![image](https://user-images.githubusercontent.com/967328/65810252-2b476300-e176-11e9-8a19-b1ba46da8f12.png)

A nested expression:
```Clojure
(bg-cprint ["item1"
            ["item2"
             ["item 3"
              ["item 4"
               ["item5"
                ["item6"
                 ["item7"
                  ["item8"
                   ["item9"
                    ["item10"
                     ["item11"
                      ["item12"
                       ["item13"
                        ["item14"
                         ["item15"]]]]]]]]]]]]]]])
```
Illustrates the behavior more clearly:
![image](https://user-images.githubusercontent.com/967328/65810263-46b26e00-e176-11e9-9e60-9c0ec6edc095.png)

I don't expect it would be terribly hard to start coloring at the beginning of an element rather than the beginning of the line. I am happy to take a crack at a PR if that would be helpful.

I noticed this while exploring coloring schemes here https://github.com/lambdaisland/deep-diff/issues/14

Pull request description:
Bumped fipp to 0.6.21 to pick up https://github.com/brandonbloom/fipp/issues/66
which resolves #44.

To verify, beefed up ansi unit tests to include a test that almost matches
example in puget README and a test that clearly shows indentation.

Of note: While developing these tests I used kaocha because it has excellent
expected vs actual diff reporting. Because kaocha uses puget, I figured it might
not be the best idea to use kaocha for puget testing and therefore have not
proposed to include it.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c02007..9c3eb7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 
-...
+### Fixed
+- Ansi codes now respect indentation and start at beginning of element rather
+  than beginning of line.
+  [#44](//github.com/greglook/puget/issues/44)
 
 ## [1.1.2] - 2019-04-04
 
diff --git a/project.clj b/project.clj
index 6a70346..fa65167 100644
--- a/project.clj
+++ b/project.clj
@@ -14,7 +14,7 @@
   :dependencies
   [[org.clojure/clojure "1.10.0"]
    [mvxcvi/arrangement "1.2.0"]
-   [fipp "0.6.17"]]
+   [fipp "0.6.21"]]
 
   :cljfmt
   {:remove-consecutive-blank-lines? false


Test patch:
diff --git a/test/puget/color/ansi_test.clj b/test/puget/color/ansi_test.clj
index 190d260..bebeca0 100644
--- a/test/puget/color/ansi_test.clj
+++ b/test/puget/color/ansi_test.clj
@@ -1,7 +1,51 @@
 (ns puget.color.ansi-test
   (:require
+    [clojure.string :as str]
     [clojure.test :refer :all]
-    [puget.color.ansi :as ansi]))
+    [puget.color.ansi :as ansi]
+    [puget.printer :as printer]))
+
+
+(def c-delimiter       [:bold :red])
+(def c-tag             [:red])
+(def c-nil             [:bold :black])
+(def c-boolean         [:green])
+(def c-number          [:cyan])
+(def c-string          [:bold :magenta])
+(def c-character       [:magenta])
+(def c-keyword         [:bold :yellow])
+(def c-symbol          nil)
+(def c-function-symbol [:bold :blue])
+(def c-class-delimiter [:blue])
+(def c-class-name      [:white])
+
+
+(def test-color-scheme
+  {:delimiter       c-delimiter
+   :tag             c-tag
+   :nil             c-nil
+   :boolean         c-boolean
+   :number          c-number
+   :string          c-string
+   :character       c-character
+   :keyword         c-keyword
+   :symbol          c-symbol
+   :function-symbol c-function-symbol
+   :class-delimiter c-class-delimiter
+   :class-name      c-class-name})
+
+
+(defn- escape-ansi
+  "In the name of testability, split 's' into segments and replace ESC[1;23]m with printable ⦕1;23⦖."
+  [s]
+  (-> s
+      (str/replace #"\u001b\[([0-9;]*)[mK]" "፨⦕$1⦖፨")
+      (str/split #"፨")
+      (->> (filter seq))))
+
+
+(defn- ansi [text codes]
+  (apply ansi/sgr text codes))
 
 
 (deftest colored-text
@@ -9,3 +53,82 @@
         color (ansi/sgr text :red)]
     (is (< (count text) (count color)))
     (is (= text (ansi/strip color)))))
+
+
+(deftest ansi-example-test
+  (let [test-data [nil true \space "string"
+                   {:omega 123N :alpha '(func x y) :gamma 3.14159}
+                   #{\a "heterogeneous" :set}
+                   #_(java.util.Currency/getInstance "USD")
+                   (java.util.Date. 1570322071178)
+                   (java.util.UUID/fromString "b537346e-8ad1-4bce-8bab-60fcd4007530")]]
+    (is (= (escape-ansi
+             (str (ansi "[" c-delimiter)
+                  (ansi "nil" c-nil)
+                  "\n " (ansi "true" c-boolean)
+                  "\n " (ansi "\\space" c-character)
+                  "\n " (ansi "\"string\"" c-string)
+
+                  "\n " (ansi "{" c-delimiter)
+                  (ansi ":alpha" c-keyword)
+                  " " (ansi "(" c-delimiter) (ansi "func" c-function-symbol) " x y" (ansi ")" c-delimiter)
+                  ", " (ansi ":gamma" c-keyword) " " (ansi "3.14159" c-number)
+                  ", " (ansi ":omega" c-keyword) " " (ansi "123N" c-number)
+                  (ansi "}" c-delimiter)
+
+                  "\n " (ansi "#{" c-delimiter)
+                  (ansi "\\a" c-character)
+                  " " (ansi "\"heterogeneous\"" c-string)
+                  " " (ansi ":set" c-keyword)
+                  (ansi "}" c-delimiter)
+
+                  "\n " (ansi "#inst" c-tag)
+                  " " (ansi "\"2019-10-06T00:34:31.178-00:00\"" c-string)
+
+                  "\n " (ansi "#uuid" c-tag)
+                  " " (ansi "\"b537346e-8ad1-4bce-8bab-60fcd4007530\"" c-string)
+                  (ansi "]" c-delimiter)))
+           (escape-ansi
+             (printer/cprint-str test-data
+                                 {:color-markup :ansi
+                                  :color-scheme test-color-scheme}))))))
+
+
+(deftest ansi-nested-test
+  (let [test-data ["item1"
+                   ["item2"
+                    ["item3"
+                     ["item4"
+                      ["item5"
+                       ["item6"
+                        ["item7"
+                         ["item8"
+                          ["item9"
+                           ["item10"
+                            ["item11"
+                             ["item12"
+                              ["item13"
+                               ["item14"
+                                ["item15"]]]]]]]]]]]]]]]]
+    (is (= (escape-ansi
+             (str (ansi "[" c-delimiter)
+                  (ansi "\"item1\"" c-string)
+                  "\n " (ansi "[" c-delimiter) (ansi "\"item2\"" c-string)
+                  "\n  " (ansi "[" c-delimiter) (ansi "\"item3\"" c-string)
+                  "\n   " (ansi "[" c-delimiter) (ansi "\"item4\"" c-string)
+                  "\n    " (ansi "[" c-delimiter) (ansi "\"item5\"" c-string)
+                  "\n     " (ansi "[" c-delimiter) (ansi "\"item6\"" c-string)
+                  "\n      " (ansi "[" c-delimiter) (ansi "\"item7\"" c-string)
+                  "\n       " (ansi "[" c-delimiter) (ansi "\"item8\"" c-string)
+                  "\n        " (ansi "[" c-delimiter) (ansi "\"item9\"" c-string)
+                  "\n         " (ansi "[" c-delimiter) (ansi "\"item10\"" c-string)
+                  " " (ansi "[" c-delimiter) (ansi "\"item11\"" c-string)
+                  " " (ansi "[" c-delimiter) (ansi "\"item12\"" c-string)
+                  " " (ansi "[" c-delimiter) (ansi "\"item13\"" c-string)
+                  " " (ansi "[" c-delimiter) (ansi "\"item14\"" c-string)
+                  " " (ansi "[" c-delimiter) (ansi "\"item15\"" c-string)
+                  (apply str (repeat 15 (ansi "]" c-delimiter)))))
+           (escape-ansi
+             (printer/cprint-str test-data
+                                 {:color-markup :ansi
+                                  :color-scheme test-color-scheme}))))))