packages feed

patat 0.14.0.0 → 0.14.2.0

raw patch · 10 files changed

+272/−90 lines, 10 filesdep +JuicyPixelsdep ~ansi-terminaldep ~pandocdep ~tasty-quickcheckPVP ok

version bump matches the API change (PVP)

Dependencies added: JuicyPixels

Dependency ranges changed: ansi-terminal, pandoc, tasty-quickcheck

API changes (from Hackage documentation)

+ Patat.Images.Internal: withEscapeSequence :: IO () -> IO ()
+ Patat.Images.WezTerm: backend :: Backend
+ Patat.Images.WezTerm: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Images.WezTerm.Config
+ Patat.Images.WezTerm: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Images.WezTerm.Pane
+ Patat.Images.WezTerm: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Images.WezTerm.Size
+ Patat.Images.WezTerm: instance GHC.Classes.Eq Patat.Images.WezTerm.Config
+ Patat.Images.WezTerm: instance GHC.Show.Show Patat.Images.WezTerm.Pane
+ Patat.Images.WezTerm: instance GHC.Show.Show Patat.Images.WezTerm.Size

Files

CHANGELOG.md view
@@ -1,5 +1,19 @@ # Changelog +## 0.14.2.0 (2024-03-10)++ *  Fix WezTerm image rendering when using panes (#182).+ *  Bump `ansi-terminal` upper bound to 1.2 and lower bound to 1.1.+    This adds the ability to set colors on underlines.+ *  Bump `pandoc` upper bound to 3.7.+ *  Bump `tasty-quickcheck` upper bound to 0.12.++## 0.14.1.0 (2024-02-22)++ *  Add image support for WezTerm (#177).+ *  Fix image support in Kitty (#179).+ *  Fix image scroll issue for iTerm2.+ ## 0.14.0.0 (2024-02-06)   *  Align based on final layout for incremental lists and other fragments
README.md view
@@ -1,7 +1,7 @@ 🥔 patat ======== -![CI](https://github.com/jaspervdj/patat/workflows/CI/badge.svg) [![Hackage](https://img.shields.io/hackage/v/patat.svg)](https://hackage.haskell.org/package/patat) [![GitHub tag](https://img.shields.io/github/tag/jaspervdj/patat.svg)]()+![CI](https://github.com/jaspervdj/patat/actions/workflows/haskell-ci.yml/badge.svg) [![Hackage](https://img.shields.io/hackage/v/patat.svg)](https://hackage.haskell.org/package/patat) [![GitHub tag](https://img.shields.io/github/tag/jaspervdj/patat.svg)]()  `patat` (**P**resentations **A**top **T**he **A**NSI **T**erminal) is a feature-rich presentation tool that runs in the terminal.@@ -517,6 +517,15 @@ needs to support 24-bit RGB for this to work.  When creating portable presentations, it might be better to stick with the named colours listed above. +To customize the color of the `underline`, combine the prefix with a color, for+example:++```yaml+patat:+    theme:+        strong: [underline, underlineVividBlue]+```+ ### Syntax Highlighting  `patat` uses [Kate] Syntax Highlighting files.  `patat` ships with support for@@ -625,9 +634,13 @@  -   `backend: iterm2`: uses [iTerm2](https://iterm2.com/)'s special escape     sequence to render the image.  This even works with animated GIFs!+    Note that it can have issues running under `tmux`.  -   `backend: kitty`: uses     [Kitty's icat command](https://sw.kovidgoyal.net/kitty/kittens/icat.html).++-   `backend: wezterm`: uses the iTerm2 image protocol as implemented by+    WezTerm.  -   `backend: w3m`: uses the `w3mimgdisplay` executable to draw directly onto     the window.  This has been tested in `urxvt` and `xterm`, but is known to
lib/Patat/Images.hs view
@@ -17,6 +17,7 @@ import qualified Patat.Images.ITerm2         as ITerm2 import qualified Patat.Images.Kitty          as Kitty import qualified Patat.Images.W3m            as W3m+import qualified Patat.Images.WezTerm        as WezTerm import           Patat.Presentation.Internal  @@ -52,9 +53,10 @@ -- depending on platform availability. backends :: [(T.Text, Backend)] backends =-    [ ("iterm2", ITerm2.backend)-    , ("kitty",  Kitty.backend)-    , ("w3m",    W3m.backend)+    [ ("iterm2",  ITerm2.backend)+    , ("kitty",   Kitty.backend)+    , ("w3m",     W3m.backend)+    , ("wezterm", WezTerm.backend)     ]  
lib/Patat/Images/ITerm2.hs view
@@ -11,7 +11,6 @@ import qualified Data.Aeson                  as A import qualified Data.ByteString.Base64.Lazy as B64 import qualified Data.ByteString.Lazy        as BL-import qualified Data.List                   as L import           Patat.Cleanup               (Cleanup) import qualified Patat.Images.Internal       as Internal import           System.Environment          (lookupEnv)@@ -42,17 +41,7 @@ drawImage :: FilePath -> IO Cleanup drawImage path = do     content <- BL.readFile path-    withEscapeSequence $ do+    Internal.withEscapeSequence $ do         putStr "1337;File=inline=1;width=100%;height=100%:"         BL.putStr (B64.encode content)     return mempty------------------------------------------------------------------------------------withEscapeSequence :: IO () -> IO ()-withEscapeSequence f = do-    term <- lookupEnv "TERM"-    let inScreen = maybe False ("screen" `L.isPrefixOf`) term-    putStr $ if inScreen then "\ESCPtmux;\ESC\ESC]" else "\ESC]"-    f-    putStrLn $ if inScreen then "\a\ESC\\" else "\a"
lib/Patat/Images/Internal.hs view
@@ -6,16 +6,19 @@     , Backend (..)     , BackendNotSupported (..)     , Handle (..)+    , withEscapeSequence     ) where   ---------------------------------------------------------------------------------import           Control.Exception (Exception)-import qualified Data.Aeson        as A-import           Data.Data         (Data)-import           Data.Typeable     (Typeable)+import           Control.Exception  (Exception)+import qualified Data.Aeson         as A+import           Data.Data          (Data)+import qualified Data.List          as L+import           Data.Typeable      (Typeable) import           Patat.Cleanup-+import           System.Environment+import qualified System.IO          as IO  -------------------------------------------------------------------------------- data Config a = Auto | Explicit a deriving (Eq)@@ -38,3 +41,15 @@ data Handle = Handle     { hDrawImage  :: FilePath -> IO Cleanup     }++--------------------------------------------------------------------------------+withEscapeSequence :: IO () -> IO ()+withEscapeSequence f = do+    term <- lookupEnv "TERM"+    let inScreen = maybe False ("screen" `L.isPrefixOf`) term+    putStr $ if inScreen then "\ESCPtmux;\ESC\ESC]" else "\ESC]"+    f+    putStr $ if inScreen then "\a\ESC\\" else "\a"+    -- Make sure we don't print a newline after the image, or it may scroll+    -- slightly out of view to the top.  We flush instead.+    IO.hFlush IO.stdout
lib/Patat/Images/Kitty.hs view
@@ -6,15 +6,16 @@   ---------------------------------------------------------------------------------import           Control.Exception           (throwIO)-import           Control.Monad               (unless, void, when)-import qualified Data.Aeson                  as A-import qualified Data.List                   as L-import           Patat.Cleanup               (Cleanup)-import qualified Patat.Images.Internal       as Internal-import Data.Functor (($>))-import           System.Environment          (lookupEnv)-import           System.Process              (readProcess)+import           Control.Exception     (throwIO)+import           Control.Monad         (unless, void, when)+import qualified Data.Aeson            as A+import           Data.Functor          (($>))+import qualified Data.List             as L+import           Patat.Cleanup         (Cleanup)+import qualified Patat.Images.Internal as Internal+import           System.Environment    (lookupEnv)+import qualified System.IO             as IO+import qualified System.Process        as Process   --------------------------------------------------------------------------------@@ -42,5 +43,10 @@ drawImage :: FilePath -> IO Cleanup drawImage path = icat ["--align=center", path] $> icat ["--clear"]   where-    icat args = void $ readProcess-        "kitty" ("+kitten" : "icat" : "--transfer-mode=stream" : args) ""+    icat args = do+        (Just inh, _, _, ph) <- Process.createProcess (Process.proc "kitty"+            ("+kitten" : "icat" : "--transfer-mode=stream" : "--stdin=no" : args))+            { Process.std_in = Process.CreatePipe+            }+        IO.hClose inh+        void $ Process.waitForProcess ph
+ lib/Patat/Images/WezTerm.hs view
@@ -0,0 +1,136 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell   #-}++module Patat.Images.WezTerm+    ( backend+    ) where+++--------------------------------------------------------------------------------+import           Codec.Picture           (DynamicImage,+                                          Image (imageHeight, imageWidth),+                                          decodeImage, dynamicMap)+import           Control.Exception       (throwIO)+import           Control.Monad           (unless, when)+import qualified Data.Aeson              as A+import qualified Data.ByteString         as B+import qualified Data.ByteString.Base64  as B64+import qualified Data.Text.Lazy          as TL+import           Data.Text.Lazy.Encoding (encodeUtf8)+import           Patat.Cleanup           (Cleanup)+import qualified Patat.Images.Internal   as Internal+import           System.Directory        (findExecutable)+import           System.Environment      (lookupEnv)+import           System.Process          (readProcess)+++--------------------------------------------------------------------------------+backend :: Internal.Backend+backend = Internal.Backend new+++--------------------------------------------------------------------------------+data Config = Config deriving (Eq)+instance A.FromJSON Config where parseJSON _ = return Config+++--------------------------------------------------------------------------------+data Pane =+    Pane { paneSize     :: Size+         , paneIsActive :: Bool+         } deriving (Show)++instance A.FromJSON Pane where+    parseJSON = A.withObject "Pane" $ \o -> Pane+        <$> o A..: "size"+        <*> o A..: "is_active"+++--------------------------------------------------------------------------------+data Size =+    Size { sizePixelWidth  :: Int+         , sizePixelHeight :: Int+         } deriving (Show)++instance A.FromJSON Size where+    parseJSON = A.withObject "Size" $ \o -> Size+        <$> o A..: "pixel_width"+        <*> o A..: "pixel_height"+++--------------------------------------------------------------------------------+new :: Internal.Config Config -> IO Internal.Handle+new config = do+    when (config == Internal.Auto) $ do+        termProgram <- lookupEnv "TERM_PROGRAM"+        unless (termProgram == Just "WezTerm") $ throwIO $+            Internal.BackendNotSupported "TERM_PROGRAM not WezTerm"++    return Internal.Handle {Internal.hDrawImage = drawImage}+++--------------------------------------------------------------------------------+drawImage :: FilePath -> IO Cleanup+drawImage path = do+    content <- B.readFile path++    wez <- wezExecutable+    resp <- fmap (encodeUtf8 . TL.pack) $ readProcess wez ["cli", "list", "--format", "json"] []+    let panes = (A.decode resp :: Maybe [Pane])++    Internal.withEscapeSequence $ do+        putStr "1337;File=inline=1;doNotMoveCursor=1;"+        case decodeImage content of+            Left _    -> pure ()+            Right img -> putStr $ wezArString (imageAspectRatio img) (activePaneAspectRatio panes)+        putStr ":"+        B.putStr (B64.encode content)+    return mempty+++--------------------------------------------------------------------------------+wezArString  :: Double -> Double -> String+wezArString i p | i < p     = "width=auto;height=95%;"+                | otherwise = "width=100%;height=auto;"+++--------------------------------------------------------------------------------+wezExecutable :: IO String+wezExecutable = do+    w <- findExecutable "wezterm.exe"+    case w of+        Nothing -> return "wezterm"+        Just x  -> return x+++--------------------------------------------------------------------------------+imageAspectRatio  :: DynamicImage -> Double+imageAspectRatio i = imgW i / imgH i+    where+        imgH = fromIntegral . (dynamicMap imageHeight)+        imgW = fromIntegral . (dynamicMap imageWidth)+++--------------------------------------------------------------------------------+paneAspectRatio :: Pane -> Double+paneAspectRatio p = paneW p / paneH p+    where+        paneH = fromIntegral . sizePixelHeight . paneSize+        paneW = fromIntegral . sizePixelWidth . paneSize+++--------------------------------------------------------------------------------+activePaneAspectRatio :: Maybe [Pane] -> Double+activePaneAspectRatio Nothing = defaultAr -- This should never happen+activePaneAspectRatio (Just x) =+    case filter paneIsActive x of+        [p] -> paneAspectRatio p+        _   -> defaultAr                  -- This shouldn't either+++--------------------------------------------------------------------------------+defaultAr :: Double+defaultAr = (4 / 3 :: Double)             -- Good enough for a VT100++
lib/Patat/Main.hs view
@@ -36,6 +36,7 @@ import qualified System.Console.ANSI              as Ansi import           System.Directory                 (doesFileExist,                                                    getModificationTime)+import           System.Environment               (lookupEnv) import           System.Exit                      (exitFailure, exitSuccess) import qualified System.IO                        as IO import qualified Text.Pandoc                      as Pandoc@@ -306,7 +307,11 @@         buff <- IO.hGetBuffering IO.stdin         IO.hSetEcho      IO.stdin False         IO.hSetBuffering IO.stdin IO.NoBuffering-        Ansi.hideCursor++        -- Suppress cursor hiding for WezTerm image compatibility+        termProgram <- lookupEnv "TERM_PROGRAM"+        unless (termProgram == Just "WezTerm") $ Ansi.hideCursor+         return (echo, buff, chan)      teardown (echo, buff, _chan) = do
lib/Patat/Theme.hs view
@@ -180,41 +180,41 @@  -------------------------------------------------------------------------------- sgrToString :: Ansi.SGR -> Maybe String-sgrToString (Ansi.SetColor layer intensity color) = Just $-    (\str -> case layer of-        Ansi.Foreground -> str-        Ansi.Background -> "on" ++ capitalize str) $-    (case intensity of-        Ansi.Dull  -> "dull"-        Ansi.Vivid -> "vivid") ++-    (case color of-        Ansi.Black   -> "Black"-        Ansi.Red     -> "Red"-        Ansi.Green   -> "Green"-        Ansi.Yellow  -> "Yellow"-        Ansi.Blue    -> "Blue"-        Ansi.Magenta -> "Magenta"-        Ansi.Cyan    -> "Cyan"-        Ansi.White   -> "White")+sgrToString sgr = case sgr of+    Ansi.SetColor layer intensity color -> Just $ layerPrefix layer $+        (case intensity of+            Ansi.Dull  -> "dull"+            Ansi.Vivid -> "vivid") +++        (case color of+            Ansi.Black   -> "Black"+            Ansi.Red     -> "Red"+            Ansi.Green   -> "Green"+            Ansi.Yellow  -> "Yellow"+            Ansi.Blue    -> "Blue"+            Ansi.Magenta -> "Magenta"+            Ansi.Cyan    -> "Cyan"+            Ansi.White   -> "White") -sgrToString (Ansi.SetUnderlining Ansi.SingleUnderline) = Just "underline"+    Ansi.SetUnderlining Ansi.SingleUnderline -> Just "underline" -sgrToString (Ansi.SetConsoleIntensity Ansi.BoldIntensity) = Just "bold"+    Ansi.SetConsoleIntensity Ansi.BoldIntensity -> Just "bold" -sgrToString (Ansi.SetItalicized True) = Just "italic"+    Ansi.SetItalicized True -> Just "italic" -sgrToString (Ansi.SetRGBColor layer color) = Just $-    (\str -> case layer of-        Ansi.Foreground -> str-        Ansi.Background -> "on" ++ capitalize str) $-    "rgb#" ++ (toRGBHex $ toSRGB24 color)+    Ansi.SetRGBColor layer color -> Just $ layerPrefix layer $+        "rgb#" ++ (toRGBHex $ toSRGB24 color)++    _ -> Nothing   where     toRGBHex (RGB r g b) = concat $ map toHexByte [r, g, b]     toHexByte x = showHex2 x ""     showHex2 x | x <= 0xf = ("0" ++) . showHex x                | otherwise = showHex x -sgrToString _ = Nothing+    layerPrefix layer str = case layer of+        Ansi.Foreground -> str+        Ansi.Background -> "on" ++ capitalize str+        Ansi.Underlining -> "underline" ++ capitalize str   --------------------------------------------------------------------------------
patat.cabal view
@@ -1,5 +1,5 @@ Name:          patat-Version:       0.14.0.0+Version:       0.14.2.0 Synopsis:      Terminal-based presentations using Pandoc Description:   Terminal-based presentations using Pandoc. License:       GPL-2@@ -11,7 +11,7 @@ Category:      Text Build-type:    Simple Cabal-version: >=1.10-Tested-with:   GHC ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1+Tested-with:   GHC ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 || ==9.8.4  Extra-source-files:   CHANGELOG.md@@ -32,32 +32,33 @@   Default-language:  Haskell2010    Build-depends:-    aeson                >= 2.0  && < 2.3,-    ansi-terminal        >= 0.6  && < 1.1,-    ansi-wl-pprint       >= 0.6  && < 1.1,-    async                >= 2.2  && < 2.3,-    base                 >= 4.9  && < 5,-    base64-bytestring    >= 1.0  && < 1.3,-    bytestring           >= 0.10 && < 0.13,-    colour               >= 2.3  && < 2.4,-    containers           >= 0.5  && < 0.7,-    directory            >= 1.2  && < 1.4,-    filepath             >= 1.4  && < 1.6,-    hashable             >= 1.4  && < 1.5,-    mtl                  >= 2.2  && < 2.4,-    optparse-applicative >= 0.16 && < 0.19,-    pandoc               >= 3.1  && < 3.6,-    pandoc-types         >= 1.23 && < 1.24,-    process              >= 1.6  && < 1.7,-    random               >= 1.2  && < 1.3,-    skylighting          >= 0.10 && < 0.15,-    terminal-size        >= 0.3  && < 0.4,-    text                 >= 1.2  && < 2.2,-    time                 >= 1.4  && < 1.13,-    unordered-containers >= 0.2  && < 0.3,-    yaml                 >= 0.8  && < 0.12,-    vector               >= 0.13 && < 0.14,-    wcwidth              >= 0.0  && < 0.1,+    aeson                >= 2.0   && < 2.3,+    ansi-terminal        >= 1.1   && < 1.2,+    ansi-wl-pprint       >= 0.6   && < 1.1,+    async                >= 2.2   && < 2.3,+    base                 >= 4.9   && < 5,+    base64-bytestring    >= 1.0   && < 1.3,+    bytestring           >= 0.10  && < 0.13,+    colour               >= 2.3   && < 2.4,+    containers           >= 0.5   && < 0.7,+    directory            >= 1.2   && < 1.4,+    filepath             >= 1.4   && < 1.6,+    hashable             >= 1.4   && < 1.5,+    JuicyPixels          >= 3.3.3 && < 3.4,+    mtl                  >= 2.2   && < 2.4,+    optparse-applicative >= 0.16  && < 0.19,+    pandoc               >= 3.1   && < 3.7,+    pandoc-types         >= 1.23  && < 1.24,+    process              >= 1.6   && < 1.7,+    random               >= 1.2   && < 1.3,+    skylighting          >= 0.10  && < 0.15,+    terminal-size        >= 0.3   && < 0.4,+    text                 >= 1.2   && < 2.2,+    time                 >= 1.4   && < 1.13,+    unordered-containers >= 0.2   && < 0.3,+    yaml                 >= 0.8   && < 0.12,+    vector               >= 0.13  && < 0.14,+    wcwidth              >= 0.0   && < 0.1,     -- We don't even depend on these packages but they can break cabal install     -- because of the conflicting 'Network.URI' module.     network-uri >= 2.6,@@ -78,6 +79,7 @@     Patat.Images.ITerm2     Patat.Images.Kitty     Patat.Images.W3m+    Patat.Images.WezTerm     Patat.Main     Patat.Presentation     Patat.Presentation.Display@@ -135,7 +137,7 @@     containers   >= 0.6 && < 0.8,     doctemplates >= 0.8 && < 0.12,     mtl          >= 2.2 && < 2.4,-    pandoc       >= 3.1 && < 3.6,+    pandoc       >= 3.1 && < 3.7,     text         >= 1.2 && < 2.2,     time         >= 1.6 && < 1.13 @@ -153,13 +155,13 @@    Build-depends:     patat,-    ansi-terminal    >= 0.6  && < 1.1,+    ansi-terminal    >= 1.1  && < 1.2,     base             >= 4.8  && < 5,     directory        >= 1.2  && < 1.4,-    pandoc           >= 3.1  && < 3.6,+    pandoc           >= 3.1  && < 3.7,     tasty            >= 1.2  && < 1.6,     tasty-hunit      >= 0.10 && < 0.11,-    tasty-quickcheck >= 0.10 && < 0.11,+    tasty-quickcheck >= 0.10 && < 0.12,     text             >= 1.2  && < 2.2,     QuickCheck       >= 2.8  && < 2.15