plain: fix ANSI escaping with custom indenting
authorJulien Moutinho <julm@autogeree.net>
Thu, 11 Jul 2019 21:20:27 +0000 (21:20 +0000)
committerJulien Moutinho <julm@autogeree.net>
Thu, 11 Jul 2019 21:20:27 +0000 (21:20 +0000)
Symantic/Document/AnsiText.hs
Symantic/Document/Plain.hs
test/HUnit.hs

index 7da647d0991fd2dc6c1af5bc30685cd8d4a0aca2..8deaef866b5682482ce215215df5a4746031bd04 100644 (file)
@@ -20,117 +20,6 @@ import qualified Data.Text.Lazy as TL
 
 import Symantic.Document.API
 
--- * Type 'AnsiText'
-newtype AnsiText d = AnsiText { unAnsiText :: Reader [SGR] d }
-instance Show d => Show (AnsiText d) where
-       show (AnsiText d) = show $ runReader d []
-
-ansiText :: AnsiText d -> AnsiText d
-ansiText = id
-
-runAnsiText :: AnsiText d -> d
-runAnsiText (AnsiText d) = (`runReader` []) d
-
-instance From Char d => From Char (AnsiText d) where
-       from = AnsiText . return . from
-instance From String d => From String (AnsiText d) where
-       from = AnsiText . return . from
-instance From Text d => From Text (AnsiText d) where
-       from = AnsiText . return . from
-instance From TL.Text d => From TL.Text (AnsiText d) where
-       from = AnsiText . return . from
-instance From s (AnsiText d) => From (Line s) (AnsiText d) where
-       from = from . unLine
-instance From s (AnsiText d) => From (Word s) (AnsiText d) where
-       from = from . unWord
-instance From String d => IsString (AnsiText d) where
-       fromString = from
-instance Semigroup d => Semigroup (AnsiText d) where
-       AnsiText x <> AnsiText y = AnsiText $ liftA2 (<>) x y
-instance Monoid d => Monoid (AnsiText d) where
-       mempty = AnsiText (return mempty)
-       mappend = (<>)
-instance Lengthable d => Lengthable (AnsiText d) where
-       -- NOTE: AnsiText's Reader can be run with an empty value
-       -- because all 'SGR' are ignored anyway.
-       width (AnsiText ds) = width $ runReader ds mempty
-       nullWidth (AnsiText ds) = nullWidth $ runReader ds mempty
-instance Spaceable d => Spaceable (AnsiText d) where
-       newline = AnsiText $ return newline
-       space   = AnsiText $ return space
-       spaces  = AnsiText . return . spaces
-instance (Semigroup d, From [SGR] d) => Colorable16 (AnsiText d) where
-       reverse     = ansiTextSGR $ SetSwapForegroundBackground True
-       black       = ansiTextSGR $ SetColor Foreground Dull  Black
-       red         = ansiTextSGR $ SetColor Foreground Dull  Red
-       green       = ansiTextSGR $ SetColor Foreground Dull  Green
-       yellow      = ansiTextSGR $ SetColor Foreground Dull  Yellow
-       blue        = ansiTextSGR $ SetColor Foreground Dull  Blue
-       magenta     = ansiTextSGR $ SetColor Foreground Dull  Magenta
-       cyan        = ansiTextSGR $ SetColor Foreground Dull  Cyan
-       white       = ansiTextSGR $ SetColor Foreground Dull  White
-       blacker     = ansiTextSGR $ SetColor Foreground Vivid Black
-       redder      = ansiTextSGR $ SetColor Foreground Vivid Red
-       greener     = ansiTextSGR $ SetColor Foreground Vivid Green
-       yellower    = ansiTextSGR $ SetColor Foreground Vivid Yellow
-       bluer       = ansiTextSGR $ SetColor Foreground Vivid Blue
-       magentaer   = ansiTextSGR $ SetColor Foreground Vivid Magenta
-       cyaner      = ansiTextSGR $ SetColor Foreground Vivid Cyan
-       whiter      = ansiTextSGR $ SetColor Foreground Vivid White
-       onBlack     = ansiTextSGR $ SetColor Background Dull  Black
-       onRed       = ansiTextSGR $ SetColor Background Dull  Red
-       onGreen     = ansiTextSGR $ SetColor Background Dull  Green
-       onYellow    = ansiTextSGR $ SetColor Background Dull  Yellow
-       onBlue      = ansiTextSGR $ SetColor Background Dull  Blue
-       onMagenta   = ansiTextSGR $ SetColor Background Dull  Magenta
-       onCyan      = ansiTextSGR $ SetColor Background Dull  Cyan
-       onWhite     = ansiTextSGR $ SetColor Background Dull  White
-       onBlacker   = ansiTextSGR $ SetColor Background Vivid Black
-       onRedder    = ansiTextSGR $ SetColor Background Vivid Red
-       onGreener   = ansiTextSGR $ SetColor Background Vivid Green
-       onYellower  = ansiTextSGR $ SetColor Background Vivid Yellow
-       onBluer     = ansiTextSGR $ SetColor Background Vivid Blue
-       onMagentaer = ansiTextSGR $ SetColor Background Vivid Magenta
-       onCyaner    = ansiTextSGR $ SetColor Background Vivid Cyan
-       onWhiter    = ansiTextSGR $ SetColor Background Vivid White
-instance (Semigroup d, From [SGR] d) => Decorable (AnsiText d) where
-       bold      = ansiTextSGR $ SetConsoleIntensity BoldIntensity
-       underline = ansiTextSGR $ SetUnderlining SingleUnderline
-       italic    = ansiTextSGR $ SetItalicized True
-instance Justifiable d => Justifiable (AnsiText d) where
-       justify (AnsiText d) = AnsiText $ justify <$> d
-instance Indentable d => Indentable (AnsiText d) where
-       align (AnsiText d) = AnsiText $ align <$> d
-       setIndent p i (AnsiText d) = AnsiText $ ReaderT $ \inh ->
-               Identity $
-               setIndent
-                (unAnsiText p`runReader`inh) i
-                (runReader d inh)
-       incrIndent p i (AnsiText d) = AnsiText $ ReaderT $ \inh ->
-               Identity $
-               incrIndent
-                (unAnsiText p`runReader`inh) i
-                (runReader d inh)
-       fill w (AnsiText d)        = AnsiText $ fill w <$> d
-       fillOrBreak w (AnsiText d) = AnsiText $ fillOrBreak w <$> d
-instance Listable d => Listable (AnsiText d) where
-       ul ds = AnsiText $ (ul <$>) $ sequence $ unAnsiText <$> ds
-       ol ds = AnsiText $ (ol <$>) $ sequence $ unAnsiText <$> ds
-instance Wrappable d => Wrappable (AnsiText d) where
-       setWidth w (AnsiText d) = AnsiText $ setWidth w <$> d
-       breakpoint = AnsiText $ return breakpoint
-       breakspace = AnsiText $ return breakspace
-       endline    = AnsiText $ return endline
-       breakalt (AnsiText x) (AnsiText y) = AnsiText $ liftA2 breakalt x y
-
-ansiTextSGR ::
- Semigroup d => From [SGR] d =>
- SGR -> AnsiText d -> AnsiText d
-ansiTextSGR newSGR (AnsiText d) = AnsiText $ do
-       oldSGR <- ask
-       (\m -> from [newSGR] <> m <> from (Reset:List.reverse oldSGR))
-        <$> local (newSGR :) d
-
 -- * Type 'PlainText'
 -- | Drop 'Colorable16' and 'Decorable'.
 newtype PlainText d = PlainText { unPlainText :: d }
index c2c5231bb98b6523f3c3605afa9e42635ab5961f..78fe7557bd1ef0cc98b27fbec323bb942d5bda57 100644 (file)
@@ -84,6 +84,7 @@ data PlainInh d = PlainInh
  , plainInh_justify   :: !Bool
  , plainInh_indent    :: !Indent
  , plainInh_indenting :: !(Plain d)
+ , plainInh_sgr       :: ![SGR]
  }
 
 defPlainInh :: Spaceable d => PlainInh d
@@ -92,6 +93,7 @@ defPlainInh = PlainInh
  , plainInh_justify   = False
  , plainInh_indent    = 0
  , plainInh_indenting = mempty
+ , plainInh_sgr       = []
  }
 
 -- ** Type 'PlainFit'
@@ -451,6 +453,7 @@ instance (From [SGR] d, Semigroup d) => From [SGR] (Plain d) where
                then k (id, st {plainState_buffer = PlainChunk_Ignored (from sgr) : plainState_buffer st})
                else k ((from sgr <>), st)
 
+-- * Justifying
 justifyLinePlain ::
  Spaceable d =>
  PlainInh d -> PlainState d -> d
@@ -555,3 +558,70 @@ padLinePlainChunk = go
                 _ -> runPlainChunk w <> go ws lls
        go (w:ws) [] = runPlainChunk w <> go ws []
        go [] _ls = mempty
+
+-- * Escaping
+instance (Semigroup d, From [SGR] d) => Colorable16 (Plain d) where
+       reverse     = plainSGR $ SetSwapForegroundBackground True
+       black       = plainSGR $ SetColor Foreground Dull  Black
+       red         = plainSGR $ SetColor Foreground Dull  Red
+       green       = plainSGR $ SetColor Foreground Dull  Green
+       yellow      = plainSGR $ SetColor Foreground Dull  Yellow
+       blue        = plainSGR $ SetColor Foreground Dull  Blue
+       magenta     = plainSGR $ SetColor Foreground Dull  Magenta
+       cyan        = plainSGR $ SetColor Foreground Dull  Cyan
+       white       = plainSGR $ SetColor Foreground Dull  White
+       blacker     = plainSGR $ SetColor Foreground Vivid Black
+       redder      = plainSGR $ SetColor Foreground Vivid Red
+       greener     = plainSGR $ SetColor Foreground Vivid Green
+       yellower    = plainSGR $ SetColor Foreground Vivid Yellow
+       bluer       = plainSGR $ SetColor Foreground Vivid Blue
+       magentaer   = plainSGR $ SetColor Foreground Vivid Magenta
+       cyaner      = plainSGR $ SetColor Foreground Vivid Cyan
+       whiter      = plainSGR $ SetColor Foreground Vivid White
+       onBlack     = plainSGR $ SetColor Background Dull  Black
+       onRed       = plainSGR $ SetColor Background Dull  Red
+       onGreen     = plainSGR $ SetColor Background Dull  Green
+       onYellow    = plainSGR $ SetColor Background Dull  Yellow
+       onBlue      = plainSGR $ SetColor Background Dull  Blue
+       onMagenta   = plainSGR $ SetColor Background Dull  Magenta
+       onCyan      = plainSGR $ SetColor Background Dull  Cyan
+       onWhite     = plainSGR $ SetColor Background Dull  White
+       onBlacker   = plainSGR $ SetColor Background Vivid Black
+       onRedder    = plainSGR $ SetColor Background Vivid Red
+       onGreener   = plainSGR $ SetColor Background Vivid Green
+       onYellower  = plainSGR $ SetColor Background Vivid Yellow
+       onBluer     = plainSGR $ SetColor Background Vivid Blue
+       onMagentaer = plainSGR $ SetColor Background Vivid Magenta
+       onCyaner    = plainSGR $ SetColor Background Vivid Cyan
+       onWhiter    = plainSGR $ SetColor Background Vivid White
+instance (Semigroup d, From [SGR] d) => Decorable (Plain d) where
+       bold      = plainSGR $ SetConsoleIntensity BoldIntensity
+       underline = plainSGR $ SetUnderlining SingleUnderline
+       italic    = plainSGR $ SetItalicized True
+
+plainSGR ::
+ Semigroup d =>
+ From [SGR] d =>
+ SGR -> Plain d -> Plain d
+plainSGR newSGR p = before <> middle <> after
+       where
+       before = Plain $ \inh st k ->
+               let d = from [newSGR] in
+               if plainInh_justify inh
+               then k (id, st
+                { plainState_buffer =
+                       PlainChunk_Ignored d :
+                       plainState_buffer st
+                })
+               else k ((d <>), st)
+       middle = Plain $ \inh ->
+               unPlain p inh{plainInh_sgr=newSGR:plainInh_sgr inh}
+       after = Plain $ \inh st k ->
+               let d = from $ Reset : List.reverse (plainInh_sgr inh) in
+               if plainInh_justify inh
+               then k (id, st
+                { plainState_buffer =
+                       PlainChunk_Ignored d :
+                       plainState_buffer st
+                })
+               else k ((d <>), st)
index ba68245539d63f2f6c8daab8221e22491a93c6c5..ee39131d94c778f5aee3ed9a745c84cec1589364 100644 (file)
@@ -19,7 +19,6 @@ import qualified Data.List as List
 
 import Symantic.Document.API
 import Symantic.Document.Plain
-import Symantic.Document.AnsiText
 
 -- * Tests
 hunits :: TestTree
@@ -191,6 +190,11 @@ hunitPlain = testList "Plain"
  , 10 `maxWidth` spaces 2<>align(breakalt "fits" "over"<>newline<>"12345678901")
    ==> "  fits\n\
        \  12345678901"
+ -- handle escaping correctly over custom indenting
+ , 10 `maxWidth` setIndent (blue "X") 1 (red ("12"<>green "4\n5" <> "6"))
+   ==> "\ESC[31m12\ESC[32m4\n\ESC[34mX\ESC[0;31;32m5\ESC[0;31m6\ESC[0m"
+ , 10 `maxWidth` setIndent (blue "X") 1 (justify (red ("1 2 3 4"<>green " 5 6 " <> "7 ") <> "8"))
+   ==> "\ESC[31m1 2  3 4\ESC[32m 5\n\ESC[34mX\ESC[0;31;32m6 \ESC[0;31m7 \ESC[0m8"
  -- breakspace backtracking is bounded by the removable indentation
  -- (hence it can actually wrap a few words in reasonable time).
  , 80 `maxWidth`
@@ -300,9 +304,9 @@ hunitPlain = testList "Plain"
   \amet, venenatis ornare, ultrices ut, nisi."                                       
  ]
        where
-       (==>) :: IsString d => d ~ String => AnsiText (Plain d) -> d -> Assertion; infix 0 ==>
+       (==>) :: IsString d => d ~ String => Plain d -> d -> Assertion; infix 0 ==>
        p ==> exp = got @?= exp
-               where got = runPlain $ runAnsiText p
+               where got = runPlain p
 
 testList :: String -> [Assertion] -> TestTree
 testList n as = testGroup n $ List.zipWith testCase (show <$> [1::Int ..]) as