Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ghc-doclayout for openSUSE:Factory checked in at 2024-10-28 15:18:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-doclayout (Old) and /work/SRC/openSUSE:Factory/.ghc-doclayout.new.2020 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "ghc-doclayout" Mon Oct 28 15:18:29 2024 rev:11 rq:1218550 version:0.5 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-doclayout/ghc-doclayout.changes 2023-04-04 21:19:56.673008367 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-doclayout.new.2020/ghc-doclayout.changes 2024-10-28 15:20:19.675470117 +0100 @@ -1,0 +2,35 @@ +Mon Sep 9 05:57:29 UTC 2024 - Peter Simons <psimons@suse.com> + +- Update doclayout to version 0.5. + ## 0.5 + + * Extract existing HasChars definition into a module + Text.DocLayout.HasChars (Evan Silberman). + + * Add a `build` method to the signature of HasChars [API change] + (Evan Silberman). This has a default definition, and is only needed with + the ANSI renderer, so existing users should not need to add anything to + their HasChars instances. + + * Introduce support for ANSI-styled output (Evan Silberman) [API change]. + `renderPlain` and `renderANSI` are now exported; the old `render` + is a synonym of `renderPlain`. In addition, various functions are + exported to add ANSI formatting (including bold, italics, + underline, strikeout, links, and colors) to a Doc. The Attributed + type is also now exported. + + * Change type of Block constructor, replacing `[a]` with `[Attributed a]`, + which carries a Font along with an inner string type [API change] + (Evan Silberman). + + * Introduce FlatDocs and use them for rendering (Evan Silberman). + This is an internal concept, not part of the public API. + FlatDoc is an "intermediate representation" for the Doc "interpreter". + The general design is that any Doc can be turned into a list of FlatDocs + that carry equivalent information. The main point of doing this + is to replace the nested Styled and Linked Docs, which form a more + complicated tree structure than previously existed in DocLayout. + + * Deprecate `unfoldD`. + +------------------------------------------------------------------- Old: ---- doclayout-0.4.0.1.tar.gz New: ---- doclayout-0.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-doclayout.spec ++++++ --- /var/tmp/diff_new_pack.lriC5Y/_old 2024-10-28 15:20:20.631509931 +0100 +++ /var/tmp/diff_new_pack.lriC5Y/_new 2024-10-28 15:20:20.635510098 +0100 @@ -1,7 +1,7 @@ # # spec file for package ghc-doclayout # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,7 +20,7 @@ %global pkgver %{pkg_name}-%{version} %bcond_with tests Name: ghc-%{pkg_name} -Version: 0.4.0.1 +Version: 0.5 Release: 0 Summary: A prettyprinting library for laying out text documents License: BSD-3-Clause @@ -105,9 +105,9 @@ %license LICENSE %dir %{_datadir}/%{pkg_name}-%{version} %{_datadir}/%{pkg_name}-%{version}/README.md -%{_datadir}/%{pkg_name}-%{version}/changelog.md %files devel -f %{name}-devel.files +%doc changelog.md %files -n ghc-%{pkg_name}-doc -f ghc-%{pkg_name}-doc.files %license LICENSE ++++++ doclayout-0.4.0.1.tar.gz -> doclayout-0.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/changelog.md new/doclayout-0.5/changelog.md --- old/doclayout-0.4.0.1/changelog.md 2001-09-09 03:46:40.000000000 +0200 +++ new/doclayout-0.5/changelog.md 2001-09-09 03:46:40.000000000 +0200 @@ -1,5 +1,36 @@ # doclayout +## 0.5 + + * Extract existing HasChars definition into a module + Text.DocLayout.HasChars (Evan Silberman). + + * Add a `build` method to the signature of HasChars [API change] + (Evan Silberman). This has a default definition, and is only needed with + the ANSI renderer, so existing users should not need to add anything to + their HasChars instances. + + * Introduce support for ANSI-styled output (Evan Silberman) [API change]. + `renderPlain` and `renderANSI` are now exported; the old `render` + is a synonym of `renderPlain`. In addition, various functions are + exported to add ANSI formatting (including bold, italics, + underline, strikeout, links, and colors) to a Doc. The Attributed + type is also now exported. + + * Change type of Block constructor, replacing `[a]` with `[Attributed a]`, + which carries a Font along with an inner string type [API change] + (Evan Silberman). + + * Introduce FlatDocs and use them for rendering (Evan Silberman). + This is an internal concept, not part of the public API. + FlatDoc is an "intermediate representation" for the Doc "interpreter". + The general design is that any Doc can be turned into a list of FlatDocs + that carry equivalent information. The main point of doing this + is to replace the nested Styled and Linked Docs, which form a more + complicated tree structure than previously existed in DocLayout. + + * Deprecate `unfoldD`. + ## 0.4.0.1 * Add clause for Empty to renderList (#22). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/doclayout.cabal new/doclayout-0.5/doclayout.cabal --- old/doclayout-0.4.0.1/doclayout.cabal 2001-09-09 03:46:40.000000000 +0200 +++ new/doclayout-0.5/doclayout.cabal 2001-09-09 03:46:40.000000000 +0200 @@ -1,5 +1,5 @@ name: doclayout -version: 0.4.0.1 +version: 0.5 synopsis: A prettyprinting library for laying out text documents. description: doclayout is a prettyprinting library for laying out text documents, with several features not present @@ -10,7 +10,7 @@ license-file: LICENSE author: John MacFarlane maintainer: jgm@berkeley.edu -copyright: 2016-19 John MacFarlane +copyright: 2016-24 John MacFarlane category: Text build-type: Simple extra-source-files: udhr/txt/*.txt, @@ -18,12 +18,15 @@ udhr/languages.txt, src/Text/unicodeWidth.inc data-files: README.md - changelog.md +extra-source-files: changelog.md cabal-version: >=1.10 library hs-source-dirs: src exposed-modules: Text.DocLayout + other-modules: Text.DocLayout.HasChars, + Text.DocLayout.ANSIFont, + Text.DocLayout.Attributed build-depends: base >= 4.12 && < 5, text, containers, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/src/Text/DocLayout/ANSIFont.hs new/doclayout-0.5/src/Text/DocLayout/ANSIFont.hs --- old/doclayout-0.4.0.1/src/Text/DocLayout/ANSIFont.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/doclayout-0.5/src/Text/DocLayout/ANSIFont.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,107 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DeriveDataTypeable #-} +module Text.DocLayout.ANSIFont + ( Font(..) + , baseFont + , StyleReq(..) + , Weight(..) + , Shape(..) + , Color8(..) + , Underline(..) + , Strikeout(..) + , Foreground(..) + , Background(..) + , (~>) + , renderFont + , renderOSC8 + ) where + +import Data.Data (Data) +import Data.String +import Data.Text (Text) + +data Font = Font + { ftWeight :: Weight, + ftShape :: Shape, + ftUnderline :: Underline, + ftStrikeout :: Strikeout, + ftForeground :: Foreground, + ftBackground :: Background, + ftLink :: Maybe Text + } + deriving (Show, Eq, Read, Data, Ord) + +baseFont :: Font +baseFont = Font Normal Roman ULNone Unstruck FGDefault BGDefault Nothing + +data Weight = Normal | Bold deriving (Show, Eq, Read, Data, Ord) +data Shape = Roman | Italic deriving (Show, Eq, Read, Data, Ord) +data Color8 = Black | Red | Green | Yellow | Blue | Magenta | Cyan | White deriving (Show, Eq, Enum, Read, Data, Ord) +data Underline = ULNone | ULSingle | ULDouble | ULCurly deriving (Show, Eq, Read, Data, Ord) +data Strikeout = Unstruck | Struck deriving (Show, Eq, Read, Data, Ord) +data Foreground = FGDefault | FG Color8 deriving (Show, Eq, Read, Data, Ord) +data Background = BGDefault | BG Color8 deriving (Show, Eq, Read, Data, Ord) + +data StyleReq + = RWeight Weight + | RShape Shape + | RForeground Foreground + | RBackground Background + | RUnderline Underline + | RStrikeout Strikeout + deriving (Show, Eq, Read, Data, Ord) + +(~>) :: Font -> StyleReq -> Font +(~>) f (RWeight w) = f{ftWeight = w} +(~>) f (RShape s) = f{ftShape = s} +(~>) f (RForeground c) = f{ftForeground = c} +(~>) f (RBackground c) = f{ftBackground = c} +(~>) f (RUnderline u) = f{ftUnderline = u} +(~>) f (RStrikeout u) = f{ftStrikeout = u} + +rawSGR :: (Semigroup a, IsString a) => a -> a +rawSGR n = "\ESC[" <> n <> "m" + +class SGR b where + renderSGR :: (Semigroup a, IsString a) => b -> a + +instance SGR Weight where + renderSGR Normal = rawSGR "22" + renderSGR Bold = rawSGR "1" + +instance SGR Shape where + renderSGR Roman = rawSGR "23" + renderSGR Italic = rawSGR "3" + +instance SGR Foreground where + renderSGR FGDefault = rawSGR "39" + renderSGR (FG a) = (rawSGR . fromString . show . (+) 30 . fromEnum) a + +instance SGR Background where + renderSGR BGDefault = rawSGR "49" + renderSGR (BG a) = (rawSGR . fromString . show . (+) 40 . fromEnum) a + +instance SGR Underline where + renderSGR ULNone = rawSGR "24" + renderSGR ULSingle = rawSGR "4" + renderSGR ULDouble = rawSGR "21" + renderSGR ULCurly = rawSGR "4:3" + +instance SGR Strikeout where + renderSGR Unstruck = rawSGR "29" + renderSGR Struck = rawSGR "9" + +renderFont :: (Semigroup a, IsString a) => Font -> a +renderFont f + | f == baseFont = rawSGR "0" + | otherwise = + renderSGR (ftWeight f) + <> renderSGR (ftShape f) + <> renderSGR (ftForeground f) + <> renderSGR (ftBackground f) + <> renderSGR (ftUnderline f) + <> renderSGR (ftStrikeout f) + +renderOSC8 :: (Semigroup a, IsString a) => Maybe a -> a +renderOSC8 Nothing = "\ESC]8;;\ESC\\" +renderOSC8 (Just t) = "\ESC]8;;" <> t <> "\ESC\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/src/Text/DocLayout/Attributed.hs new/doclayout-0.5/src/Text/DocLayout/Attributed.hs --- old/doclayout-0.4.0.1/src/Text/DocLayout/Attributed.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/doclayout-0.5/src/Text/DocLayout/Attributed.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,50 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE FlexibleInstances #-} +module Text.DocLayout.Attributed (Attributed(..), Attr(..), Link, fromList, singleton) + where + +import Data.String +import Text.DocLayout.ANSIFont (Font, baseFont) +import Data.Data (Data, Typeable) +import GHC.Generics +import Data.Sequence ((><)) +import qualified Data.Sequence as S +import Data.Text (Text) + +type Link = Maybe Text + +-- | Font attributes. +data Attr a = Attr Link Font a + deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, + Data, Typeable, Generic) + +instance Semigroup a => Semigroup (Attr a) where + (<>) (Attr l f x) (Attr _ _ y) = Attr l f $ x <> y -- This is arbitrary + +instance (IsString a, Monoid a) => Monoid (Attr a) where + mempty = Attr Nothing baseFont (fromString "") + +-- | A sequence of strings with font attributes. +newtype Attributed a = Attributed (S.Seq (Attr a)) + deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, + Data, Typeable, Generic) + +fromList :: [Attr a] -> Attributed a +fromList = Attributed . S.fromList + +singleton :: Attr a -> Attributed a +singleton = Attributed . S.singleton + +instance IsString a => IsString (Attr a) where + fromString x = Attr Nothing baseFont (fromString x) + +instance IsString a => IsString (Attributed a) where + fromString x = Attributed $ S.singleton $ Attr Nothing baseFont (fromString x) + +instance Semigroup a => Semigroup (Attributed a) where + (<>) (Attributed a) (Attributed b) = Attributed $ a >< b + +instance Monoid a => Monoid (Attributed a) where + mempty = Attributed S.empty diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/src/Text/DocLayout/HasChars.hs new/doclayout-0.5/src/Text/DocLayout/HasChars.hs --- old/doclayout-0.4.0.1/src/Text/DocLayout/HasChars.hs 1970-01-01 01:00:00.000000000 +0100 +++ new/doclayout-0.5/src/Text/DocLayout/HasChars.hs 2001-09-09 03:46:40.000000000 +0200 @@ -0,0 +1,89 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +module Text.DocLayout.HasChars (HasChars(..)) where +import Prelude +import Data.String +import qualified Data.Text as T +import qualified Data.Text.Lazy as TL +import Data.Text (Text) +import qualified Data.Text.Lazy.Builder as B +import Data.List (foldl', uncons) +import Data.Maybe (fromMaybe) +import Text.DocLayout.Attributed +import Data.Sequence (Seq(..), (|>)) +import qualified Data.Sequence as S + +-- | Class abstracting over various string types that +-- can fold over characters. Minimal definition is 'foldrChar' +-- and 'foldlChar', but defining the other methods can give better +-- performance. +class (IsString a, Semigroup a, Monoid a, Show a) => HasChars a where + foldrChar :: (Char -> b -> b) -> b -> a -> b + foldlChar :: (b -> Char -> b) -> b -> a -> b + replicateChar :: Int -> Char -> a + replicateChar n c = fromString (replicate n c) + isNull :: a -> Bool + isNull = foldrChar (\_ _ -> False) True + splitLines :: a -> [a] + splitLines s = (fromString firstline : otherlines) + where + (firstline, otherlines) = foldrChar go ([],[]) s + go '\n' (cur,lns) = ([], fromString cur : lns) + go c (cur,lns) = (c:cur, lns) + build :: a -> B.Builder + build = foldrChar (mappend . B.singleton) (B.fromString "") + +instance HasChars Text where + foldrChar = T.foldr + foldlChar = T.foldl' + splitLines = T.splitOn "\n" + replicateChar n c = T.replicate n (T.singleton c) + isNull = T.null + build = B.fromText + +instance HasChars String where + foldrChar = foldr + foldlChar = foldl' + splitLines = lines . (++"\n") + replicateChar = replicate + isNull = null + build = B.fromString + +instance HasChars TL.Text where + foldrChar = TL.foldr + foldlChar = TL.foldl' + splitLines = TL.splitOn "\n" + replicateChar n c = TL.replicate (fromIntegral n) (TL.singleton c) + isNull = TL.null + build = B.fromLazyText + +instance HasChars a => HasChars (Attr a) where + foldrChar f a (Attr _ _ x) = foldrChar f a x + foldlChar f a (Attr _ _ x) = foldlChar f a x + splitLines (Attr l f x) = Attr l f <$> splitLines x + build (Attr _ _ x) = build x + +instance (HasChars a) => HasChars (Attributed a) where + foldrChar _ acc (Attributed S.Empty) = acc + foldrChar f acc (Attributed (xs :|> (Attr _ _ x))) = + let l = foldrChar f acc x + innerFold e a = foldrChar f a e + in foldr innerFold l xs + foldlChar _ acc (Attributed S.Empty) = acc + foldlChar f acc (Attributed ((Attr _ _ x) :<| xs)) = + let l = foldlChar f acc x + innerFold e a = foldlChar f a e + in foldr innerFold l xs + splitLines (Attributed s) = fmap Attributed $ reverse $ go ([], S.empty) s + where + go (lns, cur) S.Empty = cur : lns + go (lns, cur) (x :<| xs) = + case splitLines x of + [] -> go (cur : lns, S.empty) xs + [k1] -> go (lns, cur |> k1) xs + k1 : ks -> + let (end, most) = fromMaybe (S.empty, []) $ uncons $ reverse $ S.singleton <$> ks + in go (most ++ (cur |> k1) : lns, end) xs + build = foldMap build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/src/Text/DocLayout.hs new/doclayout-0.5/src/Text/DocLayout.hs --- old/doclayout-0.4.0.1/src/Text/DocLayout.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/doclayout-0.5/src/Text/DocLayout.hs 2001-09-09 03:46:40.000000000 +0200 @@ -27,6 +27,8 @@ module Text.DocLayout ( -- * Rendering render + , renderPlain + , renderANSI -- * Doc constructors , cr , blankline @@ -54,6 +56,22 @@ , parens , quotes , doubleQuotes + , bold + , italic + , underlined + , strikeout + , fg + , bg + , Color + , black + , red + , green + , yellow + , blue + , magenta + , cyan + , white + , link , empty -- * Functions for concatenating documents , (<+>) @@ -84,6 +102,7 @@ -- * Types , Doc(..) , HasChars(..) + , Attributed ) where @@ -96,14 +115,21 @@ import Data.Bifunctor (second) import Data.Char (isSpace, ord) import Data.List (foldl', intersperse) +import Data.List.NonEmpty (NonEmpty((:|))) +import qualified Data.List.NonEmpty as N import qualified Data.IntMap.Strict as IM import qualified Data.Map.Strict as M import qualified Data.Map.Internal as MInt import Data.Data (Data, Typeable) +import Data.Foldable (toList) import Data.String import qualified Data.Text as T -import qualified Data.Text.Lazy as TL import Data.Text (Text) +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Builder as B +import Text.DocLayout.HasChars +import Text.DocLayout.ANSIFont +import Text.DocLayout.Attributed #if MIN_VERSION_base(4,11,0) #else import Data.Semigroup @@ -111,51 +137,13 @@ import Text.Emoji (baseEmojis) --- | Class abstracting over various string types that --- can fold over characters. Minimal definition is 'foldrChar' --- and 'foldlChar', but defining the other methods can give better --- performance. -class (IsString a, Semigroup a, Monoid a, Show a) => HasChars a where - foldrChar :: (Char -> b -> b) -> b -> a -> b - foldlChar :: (b -> Char -> b) -> b -> a -> b - replicateChar :: Int -> Char -> a - replicateChar n c = fromString (replicate n c) - isNull :: a -> Bool - isNull = foldrChar (\_ _ -> False) True - splitLines :: a -> [a] - splitLines s = (fromString firstline : otherlines) - where - (firstline, otherlines) = foldrChar go ([],[]) s - go '\n' (cur,lns) = ([], fromString cur : lns) - go c (cur,lns) = (c:cur, lns) - -instance HasChars Text where - foldrChar = T.foldr - foldlChar = T.foldl' - splitLines = T.splitOn "\n" - replicateChar n c = T.replicate n (T.singleton c) - isNull = T.null - -instance HasChars String where - foldrChar = foldr - foldlChar = foldl' - splitLines = lines . (++"\n") - replicateChar = replicate - isNull = null - -instance HasChars TL.Text where - foldrChar = TL.foldr - foldlChar = TL.foldl' - splitLines = TL.splitOn "\n" - replicateChar n c = TL.replicate (fromIntegral n) (TL.singleton c) - isNull = TL.null - -- | Document, including structure relevant for layout. data Doc a = Text Int a -- ^ Text with specified width. - | Block Int [a] -- ^ A block with a width and lines. + | Block Int [Attributed a] -- ^ A block with a width and lines. | VFill Int a -- ^ A vertically expandable block; -- when concatenated with a block, expands to height -- of block, with each line containing the specified text. + | CookedText Int (Attributed a) -- ^ Text which doesn't need further cooking | Prefixed Text (Doc a) -- ^ Doc with each line prefixed with text. -- Note that trailing blanks are omitted from the prefix -- when the line after it is empty. @@ -167,10 +155,13 @@ | NewLine -- ^ newline. | BlankLines Int -- ^ Ensure a number of blank lines. | Concat (Doc a) (Doc a) -- ^ Two documents concatenated. + | Styled StyleReq (Doc a) + | Linked Text (Doc a) -- ^ A hyperlink | Empty deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Data, Typeable, Generic) + instance Semigroup (Doc a) where x <> Empty = x Empty <> x = x @@ -183,6 +174,7 @@ instance HasChars a => IsString (Doc a) where fromString = text +{-# DEPRECATED unfoldD "unfoldD will be removed from the API." #-} -- | Unfold a 'Doc' into a flat list. unfoldD :: Doc a -> [Doc a] unfoldD Empty = [] @@ -268,26 +260,89 @@ z -> x <> z _ -> d +-- Elements of a document with Styled and Linked subtrees flattened out into +-- a linear structure with open and close tags. An implementation detail of +-- the rendering process. +data FlatDoc a = FText Int a + | FBlock Int [Attributed a] + | FVFill Int a + | FCookedText Int (Attributed a) + | FPrefixed Text (NonEmpty (FlatDoc a)) + | FBeforeNonBlank (NonEmpty (FlatDoc a)) + | FFlush (NonEmpty (FlatDoc a)) + | FBreakingSpace + | FAfterBreak (NonEmpty (FlatDoc a)) + | FCarriageReturn + | FNewLine + | FBlankLines Int + | FStyleOpen StyleReq + | FStyleClose + | FLinkOpen Text + | FLinkClose + deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, + Data, Typeable, Generic) + +-- Given a Doc, return an equivalent list of FlatDocs, to be processed by +-- renderList. Worth noting: +-- * Treelike docs (Styled, and Linked) are turned into lists beginning +-- with an "open" tag and ending with a "close" tag, with the flattened +-- inner content in between. +-- * Other Docs with inner content are eliminated if the inner content is +-- empty, otherwise the inner content is itself flattened and made into +-- a NonEmpty. +flatten :: HasChars a => Doc a -> [FlatDoc a] +flatten (Text n a) = [FText n a] +flatten (Block n a) = [FBlock n a] +flatten (VFill n a) = [FVFill n a] +flatten (CookedText n a) = [FCookedText n a] +flatten (Prefixed p d) | null f = [] + | otherwise = [FPrefixed p (N.fromList f)] + where f = (normalize . flatten) d +flatten (BeforeNonBlank d) | null f = [] + | otherwise = [FBeforeNonBlank (N.fromList f)] + where f = flatten d +flatten (Flush d) | null f = [] + | otherwise = [FFlush (N.fromList f)] + where f = flatten d +flatten BreakingSpace = [FBreakingSpace] +flatten CarriageReturn = [FCarriageReturn] +flatten (AfterBreak t) | null f = [] + | otherwise = [FAfterBreak (N.fromList f)] + where f = flatten $ fromString $ T.unpack t +flatten NewLine = [FNewLine] +flatten (BlankLines n) = [FBlankLines n] +flatten Empty = [] +flatten (Concat x y) = flatten x <> flatten y +flatten (Linked l x) = FLinkOpen l : flatten x <> [FLinkClose] +flatten (Styled f x) = FStyleOpen f : flatten x <> [FStyleClose] + type DocState a = State (RenderState a) () data RenderState a = RenderState{ - output :: [a] -- ^ In reverse order + output :: [Attr a] -- ^ In reverse order , prefix :: Text , usePrefix :: Bool , lineLength :: Maybe Int -- ^ 'Nothing' means no wrapping , column :: Int , newlines :: Int -- ^ Number of preceding newlines + , fontStack :: [Font] + , linkTarget :: Maybe Text -- ^ Current link target } +peekFont :: RenderState a -> Font +peekFont st = case fontStack st of + [] -> baseFont + x:_ -> x + newline :: HasChars a => DocState a newline = do st' <- get let rawpref = prefix st' when (column st' == 0 && usePrefix st' && not (T.null rawpref)) $ do let pref = fromString $ T.unpack $ T.dropWhileEnd isSpace rawpref - modify $ \st -> st{ output = pref : output st + modify $ \st -> st{ output = Attr Nothing baseFont pref : output st , column = column st + realLength pref } - modify $ \st -> st { output = "\n" : output st + modify $ \st -> st { output = Attr Nothing baseFont "\n" : output st , column = 0 , newlines = newlines st + 1 } @@ -295,19 +350,48 @@ outp :: HasChars a => Int -> a -> DocState a outp off s = do -- offset >= 0 (0 might be combining char) st' <- get - let pref = fromString $ T.unpack $ prefix st' - when (column st' == 0 && usePrefix st' && not (isNull pref)) $ - modify $ \st -> st{ output = pref : output st + let pref = if usePrefix st' then fromString $ T.unpack $ prefix st' else mempty + let font = peekFont st' + when (column st' == 0 && not (isNull pref && font == baseFont)) $ + modify $ \st -> st{ output = Attr Nothing baseFont pref : output st , column = column st + realLength pref } - modify $ \st -> st{ output = s : output st + modify $ \st -> st{ output = Attr (linkTarget st) font s : output st , column = column st + off , newlines = 0 } --- | Render a 'Doc'. @render (Just n)@ will use --- a line length of @n@ to reflow text on breakable spaces. --- @render Nothing@ will not reflow text. +-- | Synonym for 'renderPlain'. render :: HasChars a => Maybe Int -> Doc a -> a -render linelen doc = mconcat . reverse . output $ +render = renderPlain + +-- | Render a 'Doc' with ANSI escapes. @renderANSI (Just n)@ will use +-- a line length of @n@ to reflow text on breakable spaces. +-- @renderANSI Nothing@ will not reflow text. +renderANSI :: HasChars a => Maybe Int -> Doc a -> TL.Text +renderANSI n d = B.toLazyText $ go $ prerender n d where + go s = (\(_,_,o) -> o) (go' s) <> B.fromText (renderFont baseFont) <> B.fromText (renderOSC8 Nothing) + go' (Attributed s) = foldl attrRender (Nothing, baseFont, B.fromText "") s + +-- | Render a 'Doc' without using ANSI escapes. @renderPlain (Just n)@ will use +-- a line length of @n@ to reflow text on breakable spaces. +-- @renderPlain Nothing@ will not reflow text. +renderPlain :: HasChars a => Maybe Int -> Doc a -> a +renderPlain n d = go $ prerender n d where + go (Attributed s) = foldMap attrStrip s + +attrStrip :: HasChars a => Attr a -> a +attrStrip (Attr _ _ y) | isNull y = "" + | otherwise = y + +attrRender :: HasChars a => (Link, Font, B.Builder) -> Attr a -> (Link, Font, B.Builder) +attrRender (l, f, acc) (Attr m g y) + | isNull y = (l, f, acc) + | otherwise = (m, g, acc <> B.fromText newFont <> B.fromText newLink <> build y) + where + newLink = if l == m then mempty else renderOSC8 m + newFont = if f == g then mempty else renderFont g + +prerender :: HasChars a => Maybe Int -> Doc a -> Attributed a +prerender linelen doc = fromList . reverse . output $ execState (renderDoc doc) startingState where startingState = RenderState{ output = mempty @@ -315,38 +399,38 @@ , usePrefix = True , lineLength = linelen , column = 0 - , newlines = 2 } + , newlines = 2 + , fontStack = [] + , linkTarget = Nothing } renderDoc :: HasChars a => Doc a -> DocState a -renderDoc = renderList . normalize . unfoldD +renderDoc = renderList . normalize . flatten -normalize :: HasChars a => [Doc a] -> [Doc a] +normalize :: HasChars a => [FlatDoc a] -> [FlatDoc a] normalize [] = [] -normalize (Concat{} : xs) = normalize xs -- should not happen after unfoldD -normalize (Empty : xs) = normalize xs -- should not happen after unfoldD -normalize [NewLine] = normalize [CarriageReturn] -normalize [BlankLines _] = normalize [CarriageReturn] -normalize [BreakingSpace] = [] -normalize (BlankLines m : BlankLines n : xs) = - normalize (BlankLines (max m n) : xs) -normalize (BlankLines num : BreakingSpace : xs) = - normalize (BlankLines num : xs) -normalize (BlankLines m : CarriageReturn : xs) = normalize (BlankLines m : xs) -normalize (BlankLines m : NewLine : xs) = normalize (BlankLines m : xs) -normalize (NewLine : BlankLines m : xs) = normalize (BlankLines m : xs) -normalize (NewLine : BreakingSpace : xs) = normalize (NewLine : xs) -normalize (NewLine : CarriageReturn : xs) = normalize (NewLine : xs) -normalize (CarriageReturn : CarriageReturn : xs) = - normalize (CarriageReturn : xs) -normalize (CarriageReturn : BlankLines m : xs) = normalize (BlankLines m : xs) -normalize (CarriageReturn : BreakingSpace : xs) = - normalize (CarriageReturn : xs) -normalize (BreakingSpace : CarriageReturn : xs) = - normalize (CarriageReturn:xs) -normalize (BreakingSpace : NewLine : xs) = normalize (NewLine:xs) -normalize (BreakingSpace : BlankLines n : xs) = normalize (BlankLines n:xs) -normalize (BreakingSpace : BreakingSpace : xs) = normalize (BreakingSpace:xs) +normalize [FNewLine] = normalize [FCarriageReturn] +normalize [FBlankLines _] = normalize [FCarriageReturn] +normalize [FBreakingSpace] = [] +normalize (FBlankLines m : FBlankLines n : xs) = + normalize (FBlankLines (max m n) : xs) +normalize (FBlankLines num : FBreakingSpace : xs) = + normalize (FBlankLines num : xs) +normalize (FBlankLines m : FCarriageReturn : xs) = normalize (FBlankLines m : xs) +normalize (FBlankLines m : FNewLine : xs) = normalize (FBlankLines m : xs) +normalize (FNewLine : FBlankLines m : xs) = normalize (FBlankLines m : xs) +normalize (FNewLine : FBreakingSpace : xs) = normalize (FNewLine : xs) +normalize (FNewLine : FCarriageReturn : xs) = normalize (FNewLine : xs) +normalize (FCarriageReturn : FCarriageReturn : xs) = + normalize (FCarriageReturn : xs) +normalize (FCarriageReturn : FBlankLines m : xs) = normalize (FBlankLines m : xs) +normalize (FCarriageReturn : FBreakingSpace : xs) = + normalize (FCarriageReturn : xs) +normalize (FBreakingSpace : FCarriageReturn : xs) = + normalize (FCarriageReturn:xs) +normalize (FBreakingSpace : FNewLine : xs) = normalize (FNewLine:xs) +normalize (FBreakingSpace : FBlankLines n : xs) = normalize (FBlankLines n:xs) +normalize (FBreakingSpace : FBreakingSpace : xs) = normalize (FBreakingSpace:xs) normalize (x:xs) = x : normalize xs mergeBlocks :: HasChars a => Int -> (Int, [a]) -> (Int, [a]) -> (Int, [a]) @@ -364,45 +448,93 @@ else take h lns2 pad n s = s <> replicateChar (n - realLength s) ' ' -renderList :: HasChars a => [Doc a] -> DocState a +renderList :: HasChars a => [FlatDoc a] -> DocState a renderList [] = return () -renderList (Empty : xs) = renderList xs - -renderList (Text off s : xs) = do +renderList (FText off s : xs) = do outp off s renderList xs -renderList (Prefixed pref d : xs) = do +renderList (FCookedText off s : xs) = do + st' <- get + let pref = if usePrefix st' then fromString $ T.unpack $ prefix st' else mempty + let elems (Attributed x) = reverse $ toList x + when (column st' == 0 && not (isNull pref)) $ + modify $ \st -> st{ output = Attr Nothing baseFont pref : output st + , column = column st + realLength pref } + modify $ \st -> st{ output = elems s ++ output st + , column = column st + off + , newlines = 0 } + renderList xs + +-- FStyleOpen and FStyleClose are balanced by construction when we create +-- them in `flatten`, so we can just pop the stack when we encounter +-- FStyleClose +renderList (FStyleOpen style : xs) = do + st <- get + let prevFont = peekFont st + let nextFont = prevFont ~> style + modify $ \s -> s{fontStack = nextFont : fontStack s} + renderList xs + +renderList (FStyleClose : xs) = do + modify $ \s -> s{fontStack = drop 1 $ fontStack s} + renderList xs + +-- Nested links are nonsensical, we only handle the outermost and +-- silently ignore any attempts to have a link inside a link + +-- Nested links are nonsensical, we only handle the outermost and +-- silently ignore any attempts to have a link inside a link +renderList (FLinkOpen target : xs) = do + st <- get + case linkTarget st of + Nothing -> do + modify $ \s -> s{linkTarget = Just target} + renderList xs + _ -> do + let (next, rest) = break isLinkClose xs + renderList (next <> drop 1 rest) + where + isLinkClose FLinkClose = True + isLinkClose _ = False + +renderList (FLinkClose : xs) = do + modify $ \s -> s{linkTarget = Nothing} + renderList xs + +renderList (FPrefixed pref d : xs) = do st <- get let oldPref = prefix st put st{ prefix = prefix st <> pref } - renderDoc d + renderList $ normalize $ N.toList d modify $ \s -> s{ prefix = oldPref } -- renderDoc CarriageReturn renderList xs -renderList (Flush d : xs) = do +renderList (FFlush d : xs) = do st <- get let oldUsePrefix = usePrefix st put st{ usePrefix = False } - renderDoc d + renderList $ normalize $ N.toList d modify $ \s -> s{ usePrefix = oldUsePrefix } renderList xs -renderList (BeforeNonBlank d : xs) = - case xs of +renderList (FBeforeNonBlank d : xs) = do + let next = dropWhile (not . isPrintable) xs + case next of (x:_) | startsBlank x -> renderList xs - | otherwise -> renderDoc d >> renderList xs + | otherwise -> renderList (normalize $ N.toList d) >> renderList xs [] -> renderList xs -renderList (BlankLines num : xs) = do + +renderList (FBlankLines num : xs) = do st <- get case output st of _ | newlines st > num -> return () | otherwise -> replicateM_ (1 + num - newlines st) newline renderList xs -renderList (CarriageReturn : xs) = do +renderList (FCarriageReturn : xs) = do st <- get if newlines st > 0 then renderList xs @@ -410,12 +542,12 @@ newline renderList xs -renderList (NewLine : xs) = do +renderList (FNewLine : xs) = do newline renderList xs -renderList (BreakingSpace : xs) = do - let isBreakingSpace BreakingSpace = True +renderList (FBreakingSpace : xs) = do + let isBreakingSpace FBreakingSpace = True isBreakingSpace _ = False let xs' = dropWhile isBreakingSpace xs let next = takeWhile (not . isBreakable) xs' @@ -426,41 +558,39 @@ _ -> when (column st > 0) $ outp 1 " " renderList xs' -renderList (AfterBreak t : xs) = do +renderList (FAfterBreak t : xs) = do st <- get if newlines st > 0 - then renderList (fromString (T.unpack t) : xs) + then renderList (toList t <> xs) else renderList xs -renderList (b : xs) | isBlock b = do +-- FBlock and FVFill are all that's left +renderList (b : xs) = do + st <- get + let font = peekFont st let (bs, rest) = span isBlock xs -- ensure we have right padding unless end of line - let heightOf (Block _ ls) = length ls + let heightOf (FBlock _ ls) = length ls heightOf _ = 1 let maxheight = maximum $ map heightOf (b:bs) - let toBlockSpec (Block w ls) = (w, ls) - toBlockSpec (VFill w t) = (w, take maxheight $ repeat t) + let toBlockSpec (FBlock w ls) = (w, ls) + toBlockSpec (FVFill w t) = (w, map (singleton . (Attr (linkTarget st) font)) (take maxheight $ repeat t)) toBlockSpec _ = (0, []) let (_, lns') = foldl (mergeBlocks maxheight) (toBlockSpec b) (map toBlockSpec bs) - st <- get let oldPref = prefix st case column st - realLength oldPref of n | n > 0 -> modify $ \s -> s{ prefix = oldPref <> T.replicate n " " } _ -> return () - renderList $ intersperse CarriageReturn (map literal lns') + renderList $ intersperse FCarriageReturn (mapMaybe cook lns') modify $ \s -> s{ prefix = oldPref } renderList rest -renderList (x:_) = error $ "renderList encountered " ++ show x - -isBreakable :: HasChars a => Doc a -> Bool -isBreakable BreakingSpace = True -isBreakable CarriageReturn = True -isBreakable NewLine = True -isBreakable (BlankLines _) = True -isBreakable (Concat Empty y) = isBreakable y -isBreakable (Concat x _) = isBreakable x +isBreakable :: HasChars a => FlatDoc a -> Bool +isBreakable FBreakingSpace = True +isBreakable FCarriageReturn = True +isBreakable FNewLine = True +isBreakable (FBlankLines _) = True isBreakable _ = False startsBlank' :: HasChars a => a -> Bool @@ -469,33 +599,43 @@ go Nothing c = Just (isSpace c) go (Just b) _ = Just b -startsBlank :: HasChars a => Doc a -> Bool -startsBlank (Text _ t) = startsBlank' t -startsBlank (Block n ls) = n > 0 && all startsBlank' ls -startsBlank (VFill n t) = n > 0 && startsBlank' t -startsBlank (BeforeNonBlank x) = startsBlank x -startsBlank (Prefixed _ x) = startsBlank x -startsBlank (Flush x) = startsBlank x -startsBlank BreakingSpace = True -startsBlank (AfterBreak t) = startsBlank (Text 0 t) -startsBlank CarriageReturn = True -startsBlank NewLine = True -startsBlank (BlankLines _) = True -startsBlank (Concat Empty y) = startsBlank y -startsBlank (Concat x _) = startsBlank x -startsBlank Empty = True - -isBlock :: Doc a -> Bool -isBlock Block{} = True -isBlock VFill{} = True +startsBlank :: HasChars a => FlatDoc a -> Bool +startsBlank (FText _ t) = startsBlank' t +startsBlank (FCookedText _ t) = startsBlank' t +startsBlank (FBlock n ls) = n > 0 && all startsBlank' ls +startsBlank (FVFill n t) = n > 0 && startsBlank' t +startsBlank (FBeforeNonBlank (x :| _)) = startsBlank x +startsBlank (FPrefixed _ (x :| _)) = startsBlank x +startsBlank (FFlush (x :| _)) = startsBlank x +startsBlank FBreakingSpace = True +startsBlank (FAfterBreak (t :| _)) = startsBlank t +startsBlank FCarriageReturn = True +startsBlank FNewLine = True +startsBlank (FBlankLines _) = True +startsBlank (FStyleOpen _) = True +startsBlank (FLinkOpen _) = True +startsBlank FStyleClose = True +startsBlank FLinkClose = True + +isPrintable :: FlatDoc a -> Bool +isPrintable FLinkOpen{} = False +isPrintable FLinkClose{} = False +isPrintable FStyleOpen{} = False +isPrintable FStyleClose{} = False +isPrintable _ = True + +isBlock :: FlatDoc a -> Bool +isBlock FBlock{} = True +isBlock FVFill{} = True isBlock _ = False -offsetOf :: Doc a -> Int -offsetOf (Text o _) = o -offsetOf (Block w _) = w -offsetOf (VFill w _) = w -offsetOf BreakingSpace = 1 -offsetOf _ = 0 +offsetOf :: FlatDoc a -> Int +offsetOf (FText o _) = o +offsetOf (FBlock w _) = w +offsetOf (FVFill w _) = w +offsetOf (FCookedText w _) = w +offsetOf FBreakingSpace = 1 +offsetOf _ = 0 -- | Create a 'Doc' from a stringlike value. literal :: HasChars a => a -> Doc a @@ -509,6 +649,10 @@ splitLines x {-# NOINLINE literal #-} +cook :: HasChars a => Attributed a -> Maybe (FlatDoc a) +cook x | isNull x = Nothing + | otherwise = let !len = realLength x in Just (FCookedText len x) + -- | A literal string. (Like 'literal', but restricted to String.) text :: HasChars a => String -> Doc a text = literal . fromString @@ -592,7 +736,10 @@ Text n _ -> (l, c + n) Block n _ -> (l, c + n) VFill n _ -> (l, c + n) + CookedText n _ -> (l, c + n) Empty -> (l, c) + Styled _ d -> getOffset breakWhen (l, c) d + Linked _ d -> getOffset breakWhen (l, c) d CarriageReturn -> (max l c, 0) NewLine -> (max l c, 0) BlankLines _ -> (max l c, 0) @@ -646,12 +793,14 @@ height :: HasChars a => Doc a -> Int height = length . splitLines . render Nothing -block :: HasChars a => (a -> a) -> Int -> Doc a -> Doc a +block :: HasChars a => (Attributed a -> Attributed a) -> Int -> Doc a -> Doc a block filler width d | width < 1 && not (isEmpty d) = block filler 1 d | otherwise = Block width ls where - ls = map filler $ chop width $ render (Just width) d + preimage = prerender (Just width) d + reboxed = chop width preimage + ls = map filler reboxed -- | An expandable border that, when placed next to a box, -- expands to the height of the box. Strings cycle through the @@ -707,6 +856,69 @@ doubleQuotes :: HasChars a => Doc a -> Doc a doubleQuotes = inside (char '"') (char '"') +styled :: HasChars a => StyleReq -> Doc a -> Doc a +styled _ Empty = Empty +styled s x = Styled s x + +-- | Puts a 'Doc' in boldface. +bold :: HasChars a => Doc a -> Doc a +bold = styled (RWeight Bold) + +-- | Puts a 'Doc' in italics. +italic :: HasChars a => Doc a -> Doc a +italic = styled (RShape Italic) + +-- | Underlines a 'Doc'. +underlined :: HasChars a => Doc a -> Doc a +underlined = styled (RUnderline ULSingle) + +-- | Puts a line through a 'Doc'. +strikeout :: HasChars a => Doc a -> Doc a +strikeout = styled (RStrikeout Struck) + +-- The Color type is here as an opaque alias to Color8 for the public interface +-- and there's trivial smart constructors for the individual colors to +-- hopefully allow for easier extension to supporting indexed and rgb colors in +-- the future, without dramatically changing the public API. + +type Color = Color8 + +-- | Set foreground color. +fg :: HasChars a => Color -> Doc a -> Doc a +fg = styled . RForeground . FG + +-- | Set background color. +bg :: HasChars a => Color -> Doc a -> Doc a +bg = styled . RBackground . BG + +blue :: Color +blue = Blue + +black :: Color +black = Black + +red :: Color +red = Red + +green :: Color +green = Green + +yellow :: Color +yellow = Yellow + +magenta :: Color +magenta = Magenta + +cyan :: Color +cyan = Cyan + +white :: Color +white = White + +-- | Make Doc a hyperlink. +link :: HasChars a => Text -> Doc a -> Doc a +link = Linked + -- | Returns width of a character in a monospace font: 0 for a combining -- character, 1 for a regular character, 2 for an East Asian wide character. -- Ambiguous characters are treated as width 1. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/doclayout-0.4.0.1/test/test.hs new/doclayout-0.5/test/test.hs --- old/doclayout-0.4.0.1/test/test.hs 2001-09-09 03:46:40.000000000 +0200 +++ new/doclayout-0.5/test/test.hs 2001-09-09 03:46:40.000000000 +0200 @@ -287,6 +287,11 @@ (literal "a" <> cr <> literal "\nb" <> cr <> literal "c") "a\n\nb\nc" + , renderTest "breaking within styled text" + (Just 5) + ("hi" <+> (fg blue ("mom" <+> "and" <+> "dad"))) + "hi\nmom\nand\ndad" + , testCase "length of normal text" $ realLength ("This is going to be too long anyway" :: String) @?= 35
participants (1)
-
Source-Sync