packages feed

core-text 0.2.3.6 → 0.3.0.0

raw patch · 8 files changed

+1134/−935 lines, 8 filesdep +ansi-terminaldep +colourdep −prettyprinter-ansi-terminaldep ~hashabledep ~prettyprinterbinary-addedPVP ok

version bump matches the API change (PVP)

Dependencies added: ansi-terminal, colour

Dependencies removed: prettyprinter-ansi-terminal

Dependency ranges changed: hashable, prettyprinter

API changes (from Hackage documentation)

+ Core.Text.Utilities: bold :: AnsiColour -> AnsiColour
+ Core.Text.Utilities: brightBlue :: AnsiColour
+ Core.Text.Utilities: brightCyan :: AnsiColour
+ Core.Text.Utilities: brightGreen :: AnsiColour
+ Core.Text.Utilities: brightGrey :: AnsiColour
+ Core.Text.Utilities: brightMagenta :: AnsiColour
+ Core.Text.Utilities: brightRed :: AnsiColour
+ Core.Text.Utilities: brightWhite :: AnsiColour
+ Core.Text.Utilities: brightYellow :: AnsiColour
+ Core.Text.Utilities: data AnsiColour
+ Core.Text.Utilities: dullBlue :: AnsiColour
+ Core.Text.Utilities: dullCyan :: AnsiColour
+ Core.Text.Utilities: dullGreen :: AnsiColour
+ Core.Text.Utilities: dullGrey :: AnsiColour
+ Core.Text.Utilities: dullMagenta :: AnsiColour
+ Core.Text.Utilities: dullRed :: AnsiColour
+ Core.Text.Utilities: dullWhite :: AnsiColour
+ Core.Text.Utilities: dullYellow :: AnsiColour
+ Core.Text.Utilities: highlight :: Render α => α -> Doc (Token α)
+ Core.Text.Utilities: instance GHC.Base.Monoid Core.Text.Utilities.AnsiColour
+ Core.Text.Utilities: instance GHC.Base.Semigroup Core.Text.Utilities.AnsiColour
+ Core.Text.Utilities: pureBlack :: AnsiColour
+ Core.Text.Utilities: pureBlue :: AnsiColour
+ Core.Text.Utilities: pureCyan :: AnsiColour
+ Core.Text.Utilities: pureGreen :: AnsiColour
+ Core.Text.Utilities: pureGrey :: AnsiColour
+ Core.Text.Utilities: pureMagenta :: AnsiColour
+ Core.Text.Utilities: pureRed :: AnsiColour
+ Core.Text.Utilities: pureWhite :: AnsiColour
+ Core.Text.Utilities: pureYellow :: AnsiColour
- Core.Text.Utilities: colourize :: Render α => Token α -> AnsiStyle
+ Core.Text.Utilities: colourize :: Render α => Token α -> AnsiColour
- Core.Text.Utilities: intoDocA :: Render α => α -> Doc (Token α)
+ Core.Text.Utilities: intoDocA :: α -> Doc (Token α)

Files

+ AnsiColours.png view

binary file changed (absent → 92219 bytes)

core-text.cabal view
@@ -1,13 +1,13 @@-cabal-version: 1.12+cabal-version: 1.18  -- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 474b85de516532b23adffa6ac413f511b59db0da560430db29418b117b0fbff7+-- hash: 32fe0043eab486639c92b9e8ea6c9c864fd2aa6c21f0861529c72898e51bc67a  name:           core-text-version:        0.2.3.6+version:        0.3.0.0 synopsis:       A rope type based on a finger tree over UTF-8 fragments description:    A rope data type for text, built as a finger tree over UTF-8 text                 fragments. The package also includes utiltiy functions for breaking and@@ -32,8 +32,10 @@ copyright:      © 2018-2020 Athae Eredh Siniath and Others license:        BSD3 license-file:   LICENSE-tested-with:    GHC == 8.8.3+tested-with:    GHC == 8.8.4 build-type:     Simple+extra-doc-files:+    AnsiColours.png  source-repository head   type: git@@ -52,13 +54,14 @@       lib   ghc-options: -Wall -Wwarn -fwarn-tabs   build-depends:-      base >=4.11 && <5+      ansi-terminal+    , base >=4.11 && <5     , bytestring+    , colour     , deepseq     , fingertree-    , hashable >=1.2 && <1.4-    , prettyprinter >=1.2.1.1 && <1.8-    , prettyprinter-ansi-terminal+    , hashable >=1.2+    , prettyprinter >=1.6.2     , template-haskell >=2.14 && <3     , text     , text-short
lib/Core/Text.hs view
@@ -1,36 +1,34 @@ {-# OPTIONS_HADDOCK not-home #-} -{-|-A unified Text type providing interoperability between various text-back-ends present in the Haskell ecosystem.--This is intended to be used directly:+-- |+-- A unified Text type providing interoperability between various text+-- back-ends present in the Haskell ecosystem.+--+-- This is intended to be used directly:+--+-- @+-- import "Core.Text"+-- @+--+-- as this module re-exports all of the various components making up this+-- library's text handling subsystem.+module Core.Text+  ( -- * Internal representation -@-import "Core.Text"-@+    -- |+    -- Exposes 'Bytes', a wrapper around different types of binary data, and 'Rope',+    -- a finger-tree over buffers containing text.+    module Core.Text.Bytes,+    module Core.Text.Rope, -as this module re-exports all of the various components making up this-library's text handling subsystem.--}-module Core.Text-    (-        {-* Internal representation -}-{-|-Exposes 'Bytes', a wrapper around different types of binary data, and 'Rope',-a finger-tree over buffers containing text.--}-        module Core.Text.Bytes-      , module Core.Text.Rope+    -- * Useful utilities -        {-* Useful utilities -}-{-|-Useful functions for common use cases.--}-      , module Core.Text.Utilities-    ) where+    -- |+    -- Useful functions for common use cases.+    module Core.Text.Utilities,+  )+where  import Core.Text.Bytes import Core.Text.Rope import Core.Text.Utilities-
lib/Core/Text/Breaking.hs view
@@ -3,88 +3,80 @@  -- This is an Internal module, hidden from Haddock module Core.Text.Breaking-    ( breakWords-    , breakLines-    , breakPieces-    , intoPieces-    , intoChunks-    , isNewline-    )+  ( breakWords,+    breakLines,+    breakPieces,+    intoPieces,+    intoChunks,+    isNewline,+  ) where +import Core.Text.Rope import Data.Char (isSpace)-import Data.Foldable (foldr) import Data.List (uncons)-import qualified Data.Text.Short as S (ShortText, null, break, uncons,empty)--import Core.Text.Rope--{-|-Split a passage of text into a list of words. A line is broken wherever-there is one or more whitespace characters, as defined by "Data.Char"'s-'Data.Char.isSpace'.--Examples:+import qualified Data.Text.Short as S (ShortText, break, empty, null, uncons) -@-λ> __breakWords \"This is a test\"__-[\"This\",\"is\",\"a\",\"test\"]-λ> __breakWords (\"St\" <> \"op and \" <> \"go left\")__-[\"Stop\",\"and\",\"go\",\"left\"]-λ> __breakWords emptyRope__-[]-@--}+-- |+-- Split a passage of text into a list of words. A line is broken wherever+-- there is one or more whitespace characters, as defined by "Data.Char"'s+-- 'Data.Char.isSpace'.+--+-- Examples:+--+-- @+-- λ> __breakWords \"This is a test\"__+-- [\"This\",\"is\",\"a\",\"test\"]+-- λ> __breakWords (\"St\" <> \"op and \" <> \"go left\")__+-- [\"Stop\",\"and\",\"go\",\"left\"]+-- λ> __breakWords emptyRope__+-- []+-- @ breakWords :: Rope -> [Rope] breakWords = filter (not . nullRope) . breakPieces isSpace -{-|-Split a paragraph of text into a list of its individual lines. The-paragraph will be broken wherever there is a @'\n'@ character.--Blank lines will be preserved. Note that as a special case you do /not/ get-a blank entry at the end of the a list of newline terminated strings.--@-λ> __breakLines \"Hello\\n\\nWorld\\n\"__-[\"Hello\",\"\",\"World\"]-@--}+-- |+-- Split a paragraph of text into a list of its individual lines. The+-- paragraph will be broken wherever there is a @'\n'@ character.+--+-- Blank lines will be preserved. Note that as a special case you do /not/ get+-- a blank entry at the end of the a list of newline terminated strings.+--+-- @+-- λ> __breakLines \"Hello\\n\\nWorld\\n\"__+-- [\"Hello\",\"\",\"World\"]+-- @ breakLines :: Rope -> [Rope] breakLines text =-  let-    result = breakPieces isNewline text-    n = length result - 1-    (fore,aft) = splitAt n result-  in case result of-    [] -> []-    [p] -> [p]-    _ -> if aft == [""]-        then fore-        else result+  let result = breakPieces isNewline text+      n = length result - 1+      (fore, aft) = splitAt n result+   in case result of+        [] -> []+        [p] -> [p]+        _ ->+          if aft == [""]+            then fore+            else result -{-|-Predicate testing whether a character is a newline. After-'Data.Char.isSpace' et al in "Data.Char".--}+-- |+-- Predicate testing whether a character is a newline. After+-- 'Data.Char.isSpace' et al in "Data.Char". isNewline :: Char -> Bool isNewline c = c == '\n' {-# INLINEABLE isNewline #-} -{-|-Break a Rope into pieces whereever the given predicate function returns-@True@. If found, that character will not be included on either side. Empty-runs, however, *will* be preserved.--}+-- |+-- Break a Rope into pieces whereever the given predicate function returns+-- @True@. If found, that character will not be included on either side. Empty+-- runs, however, *will* be preserved. breakPieces :: (Char -> Bool) -> Rope -> [Rope] breakPieces predicate text =-  let-    x = unRope text-    (final,result) = foldr (intoPieces predicate) (Nothing,[]) x-  in-    case final of-       Nothing -> result-       Just piece -> intoRope piece : result+  let x = unRope text+      (final, result) = foldr (intoPieces predicate) (Nothing, []) x+   in case final of+        Nothing -> result+        Just piece -> intoRope piece : result  {- Was the previous piece a match, or are we in the middle of a run of@@ -92,18 +84,15 @@ before processing into chunks. -} -- now for right fold-intoPieces :: (Char -> Bool) -> S.ShortText -> (Maybe S.ShortText,[Rope]) -> (Maybe S.ShortText,[Rope])-intoPieces predicate piece (stream,list) =-  let-    piece' = case stream of+intoPieces :: (Char -> Bool) -> S.ShortText -> (Maybe S.ShortText, [Rope]) -> (Maybe S.ShortText, [Rope])+intoPieces predicate piece (stream, list) =+  let piece' = case stream of         Nothing -> piece-        Just previous -> piece <> previous       -- more rope, less text?--    pieces = intoChunks predicate piece'-  in-    case uncons pieces of-        Nothing -> (Nothing,list)-        Just (text,remainder) -> (Just (fromRope text),remainder ++ list)+        Just previous -> piece <> previous -- more rope, less text?+      pieces = intoChunks predicate piece'+   in case uncons pieces of+        Nothing -> (Nothing, list)+        Just (text, remainder) -> (Just (fromRope text), remainder ++ list)  -- -- λ> S.break isSpace "a d"@@ -123,7 +112,7 @@ --  {--This was more easily expressed as +This was more easily expressed as    let     remainder' = S.drop 1 remainder@@ -137,17 +126,16 @@ intoChunks :: (Char -> Bool) -> S.ShortText -> [Rope] intoChunks _ piece | S.null piece = [] intoChunks predicate piece =-  let-    (chunk,remainder) = S.break predicate piece+  let (chunk, remainder) = S.break predicate piece -    -- Handle the special case that a trailing " " (generalized to predicate)-    -- is the only character left.-    (trailing,remainder') = case S.uncons remainder of-        Nothing -> (False,S.empty)-        Just (c,remaining) -> if S.null remaining-            then (predicate c,S.empty)-            else (False,remaining)-  in-    if trailing+      -- Handle the special case that a trailing " " (generalized to predicate)+      -- is the only character left.+      (trailing, remainder') = case S.uncons remainder of+        Nothing -> (False, S.empty)+        Just (c, remaining) ->+          if S.null remaining+            then (predicate c, S.empty)+            else (False, remaining)+   in if trailing         then intoRope chunk : emptyRope : []         else intoRope chunk : intoChunks predicate remainder'
lib/Core/Text/Bytes.hs view
@@ -1,150 +1,141 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE StrictData #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-unused-imports #-}        -- FIXME-{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}   -- FIXME+{-# LANGUAGE TypeSynonymInstances #-} {-# OPTIONS_HADDOCK prune #-} -{-|-Binary (as opposed to textual) data is encountered in weird corners of the-Haskell ecosystem. We tend to forget (for example) that the content-recieved from a web server is /not/ text until we convert it from UTF-8 (if-that's what it is); and of course that glosses over the fact that something-of content-type @image/jpeg@ is not text in any way, shape, or form.--Bytes also show up when working with crypto algorithms, taking hashes, and-when doing serialization to external binary formats. Although we frequently-display these in terminals (and in URLs!) as text, but we take for granted-that we have actually deserialized the data or rendered the it in-hexidecimal or base64 or...--This module presents a simple wrapper around various representations of-binary data to make it easier to interoperate with libraries supplying-or consuming bytes.--}+-- |+-- Binary (as opposed to textual) data is encountered in weird corners of the+-- Haskell ecosystem. We tend to forget (for example) that the content+-- recieved from a web server is /not/ text until we convert it from UTF-8 (if+-- that's what it is); and of course that glosses over the fact that something+-- of content-type @image/jpeg@ is not text in any way, shape, or form.+--+-- Bytes also show up when working with crypto algorithms, taking hashes, and+-- when doing serialization to external binary formats. Although we frequently+-- display these in terminals (and in URLs!) as text, but we take for granted+-- that we have actually deserialized the data or rendered the it in+-- hexidecimal or base64 or...+--+-- This module presents a simple wrapper around various representations of+-- binary data to make it easier to interoperate with libraries supplying+-- or consuming bytes. module Core.Text.Bytes-    ( Bytes-    , Binary(fromBytes, intoBytes)-    , hOutput-    , hInput-      {-* Internals -}-    , unBytes-    ) where+  ( Bytes,+    Binary (fromBytes, intoBytes),+    hOutput,+    hInput, -import qualified Data.ByteString as B (ByteString, foldl', splitAt-    , pack, unpack, length, hPut, hGetContents)-import qualified Data.ByteString.Char8 as C (pack, unpack)-import qualified Data.ByteString.Builder as B (Builder, toLazyByteString, byteString)+    -- * Internals+    unBytes,+  )+where++import qualified Data.ByteString as B+  ( ByteString,+    hGetContents,+    hPut,+    pack,+    unpack,+  )+import qualified Data.ByteString.Builder as B (Builder, byteString, toLazyByteString) import qualified Data.ByteString.Lazy as L (ByteString, fromStrict, toStrict) import Data.Hashable (Hashable) import Data.Word (Word8) import GHC.Generics (Generic)-import Data.Text.Prettyprint.Doc-    ( Doc, emptyDoc, pretty, annotate, (<+>), hsep, vcat-    , space, punctuate, hcat, group, flatAlt, sep, fillSep-    , line, line', softline, softline', hardline-    )-import Data.Text.Prettyprint.Doc.Render.Terminal (-    color, colorDull, bold, Color(..)) import System.IO (Handle) -{-|-A block of data in binary form.--}+-- |+-- A block of data in binary form. newtype Bytes-    = StrictBytes B.ByteString-    deriving (Show, Eq, Ord, Generic)+  = StrictBytes B.ByteString+  deriving (Show, Eq, Ord, Generic) -{-|-Access the strict 'ByteString' underlying the @Bytes@ type.--}+-- |+-- Access the strict 'ByteString' underlying the @Bytes@ type. unBytes :: Bytes -> B.ByteString unBytes (StrictBytes b') = b' {-# INLINE unBytes #-}  instance Hashable Bytes -{-|-Conversion to and from various types containing binary data into our-convenience Bytes type.--As often as not these conversions are /expensive/; these methods are-here just to wrap calling the relevant functions in a uniform interface.--}+-- |+-- Conversion to and from various types containing binary data into our+-- convenience Bytes type.+--+-- As often as not these conversions are /expensive/; these methods are+-- here just to wrap calling the relevant functions in a uniform interface. class Binary α where-    fromBytes :: Bytes -> α-    intoBytes :: α -> Bytes+  fromBytes :: Bytes -> α+  intoBytes :: α -> Bytes  instance Binary Bytes where-    fromBytes = id-    intoBytes = id+  fromBytes = id+  intoBytes = id -{-| from "Data.ByteString" Strict -}+-- | from "Data.ByteString" Strict instance Binary B.ByteString where-    fromBytes (StrictBytes b') = b'-    intoBytes b' = StrictBytes b'+  fromBytes (StrictBytes b') = b'+  intoBytes b' = StrictBytes b' -{-| from "Data.ByteString.Lazy" -}+-- | from "Data.ByteString.Lazy" instance Binary L.ByteString where-    fromBytes (StrictBytes b') = L.fromStrict b'-    intoBytes b' = StrictBytes (L.toStrict b')      -- expensive+  fromBytes (StrictBytes b') = L.fromStrict b'+  intoBytes b' = StrictBytes (L.toStrict b') -- expensive  instance Binary B.Builder where-    fromBytes (StrictBytes b') = B.byteString b'-    intoBytes b' = StrictBytes (L.toStrict (B.toLazyByteString b'))+  fromBytes (StrictBytes b') = B.byteString b'+  intoBytes b' = StrictBytes (L.toStrict (B.toLazyByteString b')) -{-| from "Data.Word" -}+-- | from "Data.Word" instance Binary [Word8] where-    fromBytes (StrictBytes b') = B.unpack b'-    intoBytes = StrictBytes . B.pack--{-|-Output the content of the 'Bytes' to the specified 'Handle'.--@-    hOutput h b-@--'Core.Program.Execute.output' provides a convenient way to write a @Bytes@-to a file or socket handle from within the 'Core.Program.Execute.Program'-monad.--Don't use this function to write to @stdout@ if you are using any of the-other output or logging facililities of this libarary as you will corrupt-the ordering of output on the user's terminal. Instead do:--@-    'Core.Program.Execute.write' ('intoRope' b)-@+  fromBytes (StrictBytes b') = B.unpack b'+  intoBytes = StrictBytes . B.pack -on the assumption that the bytes in question are UTF-8 (or plain ASCII)-encoded.--}+-- |+-- Output the content of the 'Bytes' to the specified 'Handle'.+--+-- @+--     hOutput h b+-- @+--+-- 'Core.Program.Execute.output' provides a convenient way to write a @Bytes@+-- to a file or socket handle from within the 'Core.Program.Execute.Program'+-- monad.+--+-- Don't use this function to write to @stdout@ if you are using any of the+-- other output or logging facililities of this libarary as you will corrupt+-- the ordering of output on the user's terminal. Instead do:+--+-- @+--     'Core.Program.Execute.write' ('intoRope' b)+-- @+--+-- on the assumption that the bytes in question are UTF-8 (or plain ASCII)+-- encoded. hOutput :: Handle -> Bytes -> IO () hOutput handle (StrictBytes b') = B.hPut handle b' -{-|-Read the (entire) contents of a handle into a Bytes object.--If you want to read the entire contents of a file, you can do:--@-    contents <- 'Core.System.Base.withFile' name 'Core.System.Base.ReadMode' 'hInput'-@--At any kind of scale, Streaming I/O is almost always for better, but for-small files you need to pick apart this is fine.--}+-- |+-- Read the (entire) contents of a handle into a Bytes object.+--+-- If you want to read the entire contents of a file, you can do:+--+-- @+--     contents <- 'Core.System.Base.withFile' name 'Core.System.Base.ReadMode' 'hInput'+-- @+--+-- At any kind of scale, Streaming I/O is almost always for better, but for+-- small files you need to pick apart this is fine. hInput :: Handle -> IO Bytes hInput handle = do-   contents <- B.hGetContents handle-   return (StrictBytes contents)+  contents <- B.hGetContents handle+  return (StrictBytes contents)  {- instance Show Bytes where     show x = case x of-        StrictBytes b' -> +        StrictBytes b' -> -}
lib/Core/Text/Parsing.hs view
@@ -1,39 +1,37 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_HADDOCK hide #-}  -- This is an Internal module, hidden from Haddock module Core.Text.Parsing-    ( calculatePositionEnd-    )+  ( calculatePositionEnd,+  ) where +import Core.Text.Rope import Data.Foldable (foldl') import qualified Data.Text.Short as S (ShortText, foldl') -import Core.Text.Rope+-- |+-- Calculate the line number and column number of a Rope (interpreting it as+-- if is a block of text in a file). By the convention observed by all leading+-- brands of text editor, lines and columns are @1@ origin, so an empty Rope+-- is position @(1,1)@. -{-|-Calculate the line number and column number of a Rope (interpreting it as-if is a block of text in a file). By the convention observed by all leading-brands of text editor, lines and columns are @1@ origin, so an empty Rope-is position @(1,1)@.--} -- Of course, if Rope itself cached position information in the FingerTree -- monoid this would be trivial.-calculatePositionEnd :: Rope -> (Int,Int)+calculatePositionEnd :: Rope -> (Int, Int) calculatePositionEnd text =-  let-    x = unRope text-    (l,c) = foldl' calculateChunk (1,1) x-  in-    (l,c)+  let x = unRope text+      (l, c) = foldl' calculateChunk (1, 1) x+   in (l, c) -calculateChunk :: (Int,Int) -> S.ShortText -> (Int,Int)+calculateChunk :: (Int, Int) -> S.ShortText -> (Int, Int) calculateChunk loc piece =-    S.foldl' f loc piece+  S.foldl' f loc piece   where-    f :: (Int,Int) -> Char -> (Int,Int)-    f !(!l,!c) ch = if ch == '\n'-        then (l+1,1)-        else (l,c+1)+    f :: (Int, Int) -> Char -> (Int, Int)+    f !(!l, !c) ch =+      if ch == '\n'+        then (l + 1, 1)+        else (l, c + 1)
lib/Core/Text/Rope.hs view
@@ -1,282 +1,309 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE StrictData #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TypeSynonymInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -{-|-If you're accustomed to working with text in almost any other programming-language, you'd be aware that a \"string\" typically refers to an in-memory-/array/ of characters. Traditionally this was a single ASCII byte per-character; more recently UTF-8 variable byte encodings which dramatically-complicates finding offsets but which gives efficient support for the-entire Unicode character space. In Haskell, the original text type,-'String', is implemented as a list of 'Char' which, because a Haskell list-is implemented as a /linked-list of boxed values/, is wildly inefficient at-any kind of scale.--In modern Haskell there are two primary ways to represent text.--First is via the [rather poorly named] @ByteString@ from the __bytestring__-package (which is an array of bytes in pinned memory). The-"Data.ByteString.Char8" submodule gives you ways to manipulate those arrays-as if they were ASCII characters. Confusingly there are both strict-(@Data.ByteString@) and lazy (@Data.ByteString.Lazy@) variants which are-often hard to tell the difference between when reading function signatures-or haddock documentation. The performance problem an immutable array backed-data type runs into is that appending a character (that is, ASCII byte) or-concatonating a string (that is, another array of ASCII bytes) is very-expensive and requires allocating a new larger array and copying the whole-thing into it. This led to the development of \"builders\" which amortize-this reallocation cost over time, but it can be cumbersome to switch-between @Builder@, the lazy @ByteString@ that results, and then having to-inevitably convert to a strict @ByteString@ because that's what the next-function in your sequence requires.--The second way is through the opaque @Text@ type of "Data.Text" from the-__text__ package, which is well tuned and high-performing but suffers from-the same design; it is likewise backed by arrays. Rather surprisingly, the-storage backing Text objects are encoded in UTF-16, meaning every time you-want to work with unicode characters that came in from /anywhere/ else and-which inevitably are UTF-8 encoded you have to convert to UTF-16 and copy-into a new array, wasting time and memory.--In this package we introduce 'Rope', a text type backed by the 2-3-'Data.FingerTree.FingerTree' data structure from the __fingertree__-package. This is not an uncommon solution in many languages as finger trees-support exceptionally efficient appending to either end and good-performance inserting anywhere else (you often find them as the backing-data type underneath text editors for this reason). Rather than 'Char' the-pieces of the rope are 'Data.Text.Short.ShortText' from the __text-short__-package, which are UTF-8 encoded and in normal memory managed by the-Haskell runtime. Conversion from other Haskell text types is not /O(1)/-(UTF-8 validity must be checked, or UTF-16 decoded, or...), but in our-benchmarking the performance has been comparable to the established types-and you may find the resultant interface for combining chunks is comparable-to using a Builder, without being forced to use a Builder.--'Rope' is used as the text type throughout this library. If you use the-functions within this package (rather than converting to other text types)-operations are quite efficient. When you do need to convert to another type-you can use 'fromRope' or 'intoRope' from the 'Textual' typeclass.+-- |+-- If you're accustomed to working with text in almost any other programming+-- language, you'd be aware that a \"string\" typically refers to an in-memory+-- /array/ of characters. Traditionally this was a single ASCII byte per+-- character; more recently UTF-8 variable byte encodings which dramatically+-- complicates finding offsets but which gives efficient support for the+-- entire Unicode character space. In Haskell, the original text type,+-- 'String', is implemented as a list of 'Char' which, because a Haskell list+-- is implemented as a /linked-list of boxed values/, is wildly inefficient at+-- any kind of scale.+--+-- In modern Haskell there are two primary ways to represent text.+--+-- First is via the [rather poorly named] @ByteString@ from the __bytestring__+-- package (which is an array of bytes in pinned memory). The+-- "Data.ByteString.Char8" submodule gives you ways to manipulate those arrays+-- as if they were ASCII characters. Confusingly there are both strict+-- (@Data.ByteString@) and lazy (@Data.ByteString.Lazy@) variants which are+-- often hard to tell the difference between when reading function signatures+-- or haddock documentation. The performance problem an immutable array backed+-- data type runs into is that appending a character (that is, ASCII byte) or+-- concatonating a string (that is, another array of ASCII bytes) is very+-- expensive and requires allocating a new larger array and copying the whole+-- thing into it. This led to the development of \"builders\" which amortize+-- this reallocation cost over time, but it can be cumbersome to switch+-- between @Builder@, the lazy @ByteString@ that results, and then having to+-- inevitably convert to a strict @ByteString@ because that's what the next+-- function in your sequence requires.+--+-- The second way is through the opaque @Text@ type of "Data.Text" from the+-- __text__ package, which is well tuned and high-performing but suffers from+-- the same design; it is likewise backed by arrays. Rather surprisingly, the+-- storage backing Text objects are encoded in UTF-16, meaning every time you+-- want to work with unicode characters that came in from /anywhere/ else and+-- which inevitably are UTF-8 encoded you have to convert to UTF-16 and copy+-- into a new array, wasting time and memory.+--+-- In this package we introduce 'Rope', a text type backed by the 2-3+-- 'Data.FingerTree.FingerTree' data structure from the __fingertree__+-- package. This is not an uncommon solution in many languages as finger trees+-- support exceptionally efficient appending to either end and good+-- performance inserting anywhere else (you often find them as the backing+-- data type underneath text editors for this reason). Rather than 'Char' the+-- pieces of the rope are 'Data.Text.Short.ShortText' from the __text-short__+-- package, which are UTF-8 encoded and in normal memory managed by the+-- Haskell runtime. Conversion from other Haskell text types is not /O(1)/+-- (UTF-8 validity must be checked, or UTF-16 decoded, or...), but in our+-- benchmarking the performance has been comparable to the established types+-- and you may find the resultant interface for combining chunks is comparable+-- to using a Builder, without being forced to use a Builder.+--+-- 'Rope' is used as the text type throughout this library. If you use the+-- functions within this package (rather than converting to other text types)+-- operations are quite efficient. When you do need to convert to another type+-- you can use 'fromRope' or 'intoRope' from the 'Textual' typeclass.+--+-- Note that we haven't tried to cover the entire gamut of operations or+-- customary convenience functions you would find in the other libraries; so+-- far 'Rope' is concentrated on aiding interoperation, being good at+-- appending (lots of) small pieces, and then efficiently taking the resultant+-- text object out to a file handle, be that the terminal console, a file, or+-- a network socket.+module Core.Text.Rope+  ( -- * Rope type+    Rope,+    emptyRope,+    singletonRope,+    replicateRope,+    replicateChar,+    widthRope,+    splitRope,+    insertRope,+    containsCharacter,+    findIndexRope, -Note that we haven't tried to cover the entire gamut of operations or-customary convenience functions you would find in the other libraries; so-far 'Rope' is concentrated on aiding interoperation, being good at-appending (lots of) small pieces, and then efficiently taking the resultant-text object out to a file handle, be that the terminal console, a file, or-a network socket.+    -- * Interoperation and Output+    Textual (fromRope, intoRope, appendRope),+    hWrite, --}-module Core.Text.Rope-    ( {-* Rope type -}-      Rope-    , emptyRope-    , singletonRope-    , replicateRope-    , replicateChar-    , widthRope-    , splitRope-    , insertRope-    , containsCharacter-    , findIndexRope-      {-* Interoperation and Output -}-    , Textual(fromRope, intoRope, appendRope)-    , hWrite-      {-* Internals -}-    , unRope-    , nullRope-    , unsafeIntoRope-    , Width(..)-    ) where+    -- * Internals+    unRope,+    nullRope,+    unsafeIntoRope,+    Width (..),+  )+where -import Control.DeepSeq (NFData(..))+import Control.DeepSeq (NFData (..))+import Core.Text.Bytes import qualified Data.ByteString as B (ByteString)-import qualified Data.ByteString.Builder as B (toLazyByteString-    , hPutBuilder)-import qualified Data.ByteString.Lazy as L (ByteString, toStrict-    , foldrChunks)-import qualified Data.FingerTree as F (FingerTree, Measured(..), empty-    , singleton, (><), (<|), (|>), search, SearchResult(..), null-    , viewl, ViewL(..))-import Data.Foldable (foldr, foldr', foldl', foldMap, toList, any)+import qualified Data.ByteString.Builder as B+  ( hPutBuilder,+    toLazyByteString,+  )+import qualified Data.ByteString.Lazy as L+  ( ByteString,+    foldrChunks,+    toStrict,+  )+import qualified Data.FingerTree as F+  ( FingerTree,+    Measured (..),+    SearchResult (..),+    ViewL (..),+    empty,+    null,+    search,+    singleton,+    viewl,+    (<|),+    (><),+    (|>),+  )+import Data.Foldable (foldl', foldr', toList) import Data.Hashable (Hashable, hashWithSalt)-import Data.String (IsString(..))+import Data.String (IsString (..)) import qualified Data.Text as T (Text)-import qualified Data.Text.Lazy as U (Text, fromChunks, foldrChunks-    , toStrict)-import qualified Data.Text.Lazy.Builder as U (Builder, toLazyText-    , fromText)-import Data.Text.Prettyprint.Doc (Pretty(..), emptyDoc)-import qualified Data.Text.Short as S (ShortText, length, any, null-    , fromText, toText, fromByteString, pack, unpack, singleton-    , append, empty, toBuilder, splitAt, findIndex, replicate)+import qualified Data.Text.Lazy as U+  ( Text,+    foldrChunks,+    fromChunks,+    toStrict,+  )+import qualified Data.Text.Lazy.Builder as U+  ( Builder,+    fromText,+    toLazyText,+  )+import Data.Text.Prettyprint.Doc (Pretty (..), emptyDoc)+import qualified Data.Text.Short as S+  ( ShortText,+    any,+    append,+    empty,+    findIndex,+    fromByteString,+    fromText,+    length,+    null,+    pack,+    replicate,+    singleton,+    splitAt,+    toBuilder,+    toText,+    unpack,+  ) import qualified Data.Text.Short.Unsafe as S (fromByteStringUnsafe) import GHC.Generics (Generic) import System.IO (Handle) -import Core.Text.Bytes--{-|-A type for textual data. A rope is text backed by a tree data structure,-rather than a single large continguous array, as is the case for strings.--There are three use cases:--/Referencing externally sourced data/--Often we interpret large blocks of data sourced from external systems as-text. Ideally we would hold onto this without copying the memory, but (as-in the case of @ByteString@ which is the most common source of data) before-we can treat it as text we have to validate the UTF-8 content. Safety-first. We also copy it out of pinned memory, allowing the Haskell runtime-to manage the storage.--/Interoperating with other libraries/--The only constant of the Haskell universe is that you won't have the right-combination of {strict, lazy} × {@Text@, @ByteString@, @String@, @[Word8]@,-etc} you need for the next function call. The 'Textual' typeclass provides-for moving between different text representations. To convert between-@Rope@ and something else use 'fromRope'; to construct a @Rope@ from-textual content in another type use 'intoRope'.--You can get at the underlying finger tree with the 'unRope' function.--/Assembling text to go out/--This involves considerable appending of data, very very occaisionally-inserting it. Often the pieces are tiny. To add text to a @Rope@ use the-'appendRope' method as below or the ('Data.Semigroup.<>') operator from-"Data.Monoid" (like you would have with a @Builder@).--Output to a @Handle@ can be done efficiently with 'hWrite'.--}+-- |+-- A type for textual data. A rope is text backed by a tree data structure,+-- rather than a single large continguous array, as is the case for strings.+--+-- There are three use cases:+--+-- /Referencing externally sourced data/+--+-- Often we interpret large blocks of data sourced from external systems as+-- text. Ideally we would hold onto this without copying the memory, but (as+-- in the case of @ByteString@ which is the most common source of data) before+-- we can treat it as text we have to validate the UTF-8 content. Safety+-- first. We also copy it out of pinned memory, allowing the Haskell runtime+-- to manage the storage.+--+-- /Interoperating with other libraries/+--+-- The only constant of the Haskell universe is that you won't have the right+-- combination of {strict, lazy} × {@Text@, @ByteString@, @String@, @[Word8]@,+-- etc} you need for the next function call. The 'Textual' typeclass provides+-- for moving between different text representations. To convert between+-- @Rope@ and something else use 'fromRope'; to construct a @Rope@ from+-- textual content in another type use 'intoRope'.+--+-- You can get at the underlying finger tree with the 'unRope' function.+--+-- /Assembling text to go out/+--+-- This involves considerable appending of data, very very occaisionally+-- inserting it. Often the pieces are tiny. To add text to a @Rope@ use the+-- 'appendRope' method as below or the ('Data.Semigroup.<>') operator from+-- "Data.Monoid" (like you would have with a @Builder@).+--+-- Output to a @Handle@ can be done efficiently with 'hWrite'. newtype Rope-    = Rope (F.FingerTree Width S.ShortText)-    deriving Generic+  = Rope (F.FingerTree Width S.ShortText)+  deriving (Generic)  instance NFData Rope where-    rnf (Rope x) = foldMap (\piece -> rnf piece) x+  rnf (Rope x) = foldMap (\piece -> rnf piece) x  instance Show Rope where-    show text = "\"" ++ fromRope text ++ "\""+  show text = "\"" ++ fromRope text ++ "\""  instance Eq Rope where-    (==) (Rope x1) (Rope x2) = (==) (stream x1) (stream x2)-      where-        stream x = foldMap S.unpack x+  (==) (Rope x1) (Rope x2) = (==) (stream x1) (stream x2)+    where+      stream x = foldMap S.unpack x  instance Ord Rope where-    compare (Rope x1) (Rope x2) = compare x1 x2+  compare (Rope x1) (Rope x2) = compare x1 x2  instance Pretty Rope where-    pretty (Rope x) = foldr ((<>) . pretty . S.toText) emptyDoc x --{-|-Access the finger tree underlying the @Rope@. You'll want the following-imports:+  pretty (Rope x) = foldr ((<>) . pretty . S.toText) emptyDoc x -@-import qualified "Data.FingerTree" as F  -- from the __fingertree__ package-import qualified "Data.Text.Short" as S  -- from the __text-short__ package-@--}+-- |+-- Access the finger tree underlying the @Rope@. You'll want the following+-- imports:+--+-- @+-- import qualified "Data.FingerTree" as F  -- from the __fingertree__ package+-- import qualified "Data.Text.Short" as S  -- from the __text-short__ package+-- @ unRope :: Rope -> F.FingerTree Width S.ShortText unRope (Rope x) = x {-# INLINE unRope #-} --{-|-The length of the @Rope@, in characters. This is the monoid used to-structure the finger tree underlying the @Rope@.--}+-- |+-- The length of the @Rope@, in characters. This is the monoid used to+-- structure the finger tree underlying the @Rope@. newtype Width = Width Int-    deriving (Eq, Ord, Show, Num, Generic)+  deriving (Eq, Ord, Show, Num, Generic)  instance F.Measured Width S.ShortText where-    measure :: S.ShortText -> Width-    measure piece = Width (S.length piece)+  measure :: S.ShortText -> Width+  measure piece = Width (S.length piece)  instance Semigroup Width where-    (<>) (Width w1) (Width w2) = Width (w1 + w2)+  (<>) (Width w1) (Width w2) = Width (w1 + w2)  instance Monoid Width where-    mempty = Width 0-    mappend = (<>)+  mempty = Width 0+  mappend = (<>)  -- here Maybe we just need type Strand = ShortText and then Rope is -- FingerTree Strand or Builder (Strand)  instance IsString Rope where-    fromString "" = emptyRope-    fromString xs = Rope . F.singleton . S.pack $ xs+  fromString "" = emptyRope+  fromString xs = Rope . F.singleton . S.pack $ xs  instance Semigroup Rope where-    (<>) text1@(Rope x1) text2@(Rope x2) =-        if F.null x2-            then text1-            else if F.null x1-                then text2-                else Rope ((F.><) x1 x2) -- god I hate these operators+  (<>) text1@(Rope x1) text2@(Rope x2) =+    if F.null x2+      then text1+      else+        if F.null x1+          then text2+          else Rope ((F.><) x1 x2) -- god I hate these operators  instance Monoid Rope where-    mempty = emptyRope-    mappend = (<>)+  mempty = emptyRope+  mappend = (<>) -{-|-An zero-length 'Rope'. You can also use @\"\"@ presuming the-__@OverloadedStrings@__ language extension is turned on in your source-file.--}+-- |+-- An zero-length 'Rope'. You can also use @\"\"@ presuming the+-- __@OverloadedStrings@__ language extension is turned on in your source+-- file. emptyRope :: Rope emptyRope = Rope F.empty-{-# INLINABLE emptyRope #-}+{-# INLINEABLE emptyRope #-} -{-|-A 'Rope' with but a single character.--}+-- |+-- A 'Rope' with but a single character. singletonRope :: Char -> Rope singletonRope = Rope . F.singleton . S.singleton --{-|-Repeat the input 'Rope' @n@ times. The follows the same semantics as other-@replicate@ functions; if you ask for zero copies you'll get an empty text-and if you ask for lots of @""@ you'll get ... an empty text.--/Implementation note/--Rather than copying the input /n/ times, this will simply add structure to hold /n/-references to the provided input text.--}+-- |+-- Repeat the input 'Rope' @n@ times. The follows the same semantics as other+-- @replicate@ functions; if you ask for zero copies you'll get an empty text+-- and if you ask for lots of @""@ you'll get ... an empty text.+--+-- /Implementation note/+--+-- Rather than copying the input /n/ times, this will simply add structure to hold /n/+-- references to the provided input text. replicateRope :: Int -> Rope -> Rope replicateRope count (Rope x) =-  let-    x' = foldr (\ _ acc -> (F.><) x acc) F.empty [1..count]-  in-    Rope x'--{-|-Repeat the input 'Char' @n@ times. This is a special case of-'replicateRope' above.--/Implementation note/+  let x' = foldr (\_ acc -> (F.><) x acc) F.empty [1 .. count]+   in Rope x' -Rather than making a huge FingerTree full of single characters, this-function will allocate a single ShortText comprised of the repeated input-character.--}+-- |+-- Repeat the input 'Char' @n@ times. This is a special case of+-- 'replicateRope' above.+--+-- /Implementation note/+--+-- Rather than making a huge FingerTree full of single characters, this+-- function will allocate a single ShortText comprised of the repeated input+-- character. replicateChar :: Int -> Char -> Rope replicateChar count = Rope . F.singleton . S.replicate count . S.singleton -{-|-Get the length of this text, in characters.--}+-- |+-- Get the length of this text, in characters. widthRope :: Rope -> Int widthRope = foldr' f 0 . unRope   where@@ -284,79 +311,71 @@  nullRope :: Rope -> Bool nullRope (Rope x) = case F.viewl x of-    F.EmptyL        -> True-    (F.:<) piece _  -> S.null piece--{-|-Break the text into two pieces at the specified offset.--Examples:--@-λ> __splitRope 0 \"abcdef\"__-(\"\", \"abcdef\")-λ> __splitRope 3 \"abcdef\"__-(\"abc\", \"def\")-λ> __splitRope 6 \"abcdef\"__-(\"abcdef\",\"\")-@--Going off either end behaves sensibly:+  F.EmptyL -> True+  (F.:<) piece _ -> S.null piece -@-λ> __splitRope 7 \"abcdef\"__-(\"abcdef\",\"\")-λ> __splitRope (-1) \"abcdef\"__-(\"\", \"abcdef\")-@--}-splitRope :: Int -> Rope -> (Rope,Rope)+-- |+-- Break the text into two pieces at the specified offset.+--+-- Examples:+--+-- @+-- λ> __splitRope 0 \"abcdef\"__+-- (\"\", \"abcdef\")+-- λ> __splitRope 3 \"abcdef\"__+-- (\"abc\", \"def\")+-- λ> __splitRope 6 \"abcdef\"__+-- (\"abcdef\",\"\")+-- @+--+-- Going off either end behaves sensibly:+--+-- @+-- λ> __splitRope 7 \"abcdef\"__+-- (\"abcdef\",\"\")+-- λ> __splitRope (-1) \"abcdef\"__+-- (\"\", \"abcdef\")+-- @+splitRope :: Int -> Rope -> (Rope, Rope) splitRope i text@(Rope x) =-  let-    pos = Width i-    result = F.search (\w1 _ -> w1 >= pos) x-  in-    case result of+  let pos = Width i+      result = F.search (\w1 _ -> w1 >= pos) x+   in case result of         F.Position before piece after ->-          let-            (Width w) = F.measure before-            (one,two) = S.splitAt (i - w) piece-          in-            (Rope ((F.|>) before one),Rope ((F.<|) two after))+          let (Width w) = F.measure before+              (one, two) = S.splitAt (i - w) piece+           in (Rope ((F.|>) before one), Rope ((F.<|) two after))         F.OnLeft -> (Rope F.empty, text)         F.OnRight -> (text, Rope F.empty)         F.Nowhere -> error "Position not found in split. Probable cause: predicate function given not monotonic. This is supposed to be unreachable" -{-|-Insert a new piece of text into an existing @Rope@ at the specified offset.--Examples:--@-λ> __insertRope 3 \"Con\" \"Def 1\"__-"DefCon 1"-λ> __insertRope 0 \"United \" \"Nations\"__-"United Nations"-@--}+-- |+-- Insert a new piece of text into an existing @Rope@ at the specified offset.+--+-- Examples:+--+-- @+-- λ> __insertRope 3 \"Con\" \"Def 1\"__+-- "DefCon 1"+-- λ> __insertRope 0 \"United \" \"Nations\"__+-- "United Nations"+-- @ insertRope :: Int -> Rope -> Rope -> Rope insertRope 0 (Rope new) (Rope x) = Rope ((F.><) new x) insertRope i (Rope new) text =-  let-    (Rope before,Rope after) = splitRope i text-  in-    Rope (mconcat [before, new, after])+  let (Rope before, Rope after) = splitRope i text+   in Rope (mconcat [before, new, after])  findIndexRope :: (Char -> Bool) -> Rope -> Maybe Int-findIndexRope predicate = fst . foldl f (Nothing,0) . unRope+findIndexRope predicate = fst . foldl f (Nothing, 0) . unRope   where     -- convert this to Maybe monad, maybe-    f :: (Maybe Int,Int) -> S.ShortText -> (Maybe Int,Int)+    f :: (Maybe Int, Int) -> S.ShortText -> (Maybe Int, Int)     f acc piece = case acc of-        (Just j,_) -> (Just j,0)-        (Nothing,!i) -> case S.findIndex predicate piece of-            Nothing -> (Nothing,i + S.length piece)-            Just !j -> (Just (i + j),0)+      (Just j, _) -> (Just j, 0)+      (Nothing, !i) -> case S.findIndex predicate piece of+        Nothing -> (Nothing, i + S.length piece)+        Just !j -> (Just (i + j), 0)  -- -- Manual instance to get around the fact that FingerTree doesn't have a@@ -371,180 +390,174 @@ -- corresponding tree. -- instance Hashable Rope where-    hashWithSalt salt (Rope x) = foldl' f salt x-      where-        f :: Int -> S.ShortText  -> Int-        f num piece = hashWithSalt num piece--{-|-Machinery to interpret a type as containing valid Unicode that can be-represented as a @Rope@ object.--/Implementation notes/+  hashWithSalt salt (Rope x) = foldl' f salt x+    where+      f :: Int -> S.ShortText -> Int+      f num piece = hashWithSalt num piece -Given that @Rope@ is backed by a finger tree, 'append' is relatively-inexpensive, plus whatever the cost of conversion is. There is a subtle-trap, however: if adding small fragments of that were obtained by slicing-(for example) a large ByteString we would end up holding on to a reference-to the entire underlying block of memory. This module is optimized to-reduce heap fragmentation by letting the Haskell runtime and garbage-collector manage the memory, so instances are expected to /copy/ these-substrings out of pinned memory.+-- |+-- Machinery to interpret a type as containing valid Unicode that can be+-- represented as a @Rope@ object.+--+-- /Implementation notes/+--+-- Given that @Rope@ is backed by a finger tree, 'append' is relatively+-- inexpensive, plus whatever the cost of conversion is. There is a subtle+-- trap, however: if adding small fragments of that were obtained by slicing+-- (for example) a large ByteString we would end up holding on to a reference+-- to the entire underlying block of memory. This module is optimized to+-- reduce heap fragmentation by letting the Haskell runtime and garbage+-- collector manage the memory, so instances are expected to /copy/ these+-- substrings out of pinned memory.+--+-- The @ByteString@ instance requires that its content be valid UTF-8. If not+-- an empty @Rope@ will be returned.+--+-- Several of the 'fromRope' implementations are expensive and involve a lot+-- of intermediate allocation and copying. If you're ultimately writing to a+-- handle prefer 'hWrite' which will write directly to the output buffer.+class Textual α where+  -- |+  -- Convert a @Rope@ into another text-like type.+  fromRope :: Rope -> α -The @ByteString@ instance requires that its content be valid UTF-8. If not-an empty @Rope@ will be returned.+  -- |+  -- Take another text-like type and convert it to a @Rope@.+  intoRope :: α -> Rope -Several of the 'fromRope' implementations are expensive and involve a lot-of intermediate allocation and copying. If you're ultimately writing to a-handle prefer 'hWrite' which will write directly to the output buffer.--}-class Textual α where-    {-|-Convert a @Rope@ into another text-like type.-    -}-    fromRope :: Rope -> α-    {-|-Take another text-like type and convert it to a @Rope@.-    -}-    intoRope :: α -> Rope-    {-|-Append some text to this @Rope@. The default implementation is basically a-convenience wrapper around calling 'intoRope' and 'mappend'ing it to your-text (which will work just fine, but for some types more efficient-implementations are possible).-    -}-    appendRope :: α -> Rope -> Rope-    appendRope thing text = text <> intoRope thing+  -- |+  -- Append some text to this @Rope@. The default implementation is basically a+  -- convenience wrapper around calling 'intoRope' and 'mappend'ing it to your+  -- text (which will work just fine, but for some types more efficient+  -- implementations are possible).+  appendRope :: α -> Rope -> Rope+  appendRope thing text = text <> intoRope thing  instance Textual (F.FingerTree Width S.ShortText) where-    fromRope = unRope-    intoRope = Rope+  fromRope = unRope+  intoRope = Rope  instance Textual Rope where-    fromRope = id-    intoRope = id-    appendRope (Rope x2) (Rope x1) = Rope ((F.><) x1 x2)+  fromRope = id+  intoRope = id+  appendRope (Rope x2) (Rope x1) = Rope ((F.><) x1 x2) -{-| from "Data.Text.Short" -}+-- | from "Data.Text.Short" instance Textual S.ShortText where-    fromRope = foldr S.append S.empty . unRope-    intoRope = Rope . F.singleton-    appendRope piece (Rope x) = Rope ((F.|>) x piece)+  fromRope = foldr S.append S.empty . unRope+  intoRope = Rope . F.singleton+  appendRope piece (Rope x) = Rope ((F.|>) x piece) -{-| from "Data.Text" Strict -}+-- | from "Data.Text" Strict instance Textual T.Text where-    fromRope = U.toStrict . U.toLazyText . foldr f mempty . unRope-      where-        f :: S.ShortText -> U.Builder -> U.Builder-        f piece built = (<>) (U.fromText (S.toText piece)) built+  fromRope = U.toStrict . U.toLazyText . foldr f mempty . unRope+    where+      f :: S.ShortText -> U.Builder -> U.Builder+      f piece built = (<>) (U.fromText (S.toText piece)) built -    intoRope t = Rope (F.singleton (S.fromText t))-    appendRope chunk (Rope x) = Rope ((F.|>) x (S.fromText chunk))+  intoRope t = Rope (F.singleton (S.fromText t))+  appendRope chunk (Rope x) = Rope ((F.|>) x (S.fromText chunk)) -{-| from "Data.Text.Lazy" -}+-- | from "Data.Text.Lazy" instance Textual U.Text where-    fromRope (Rope x) = U.fromChunks . fmap S.toText . toList $ x-    intoRope t = Rope (U.foldrChunks ((F.<|) . S.fromText) F.empty t)+  fromRope (Rope x) = U.fromChunks . fmap S.toText . toList $ x+  intoRope t = Rope (U.foldrChunks ((F.<|) . S.fromText) F.empty t) -{-| from "Data.ByteString" Strict -}+-- | from "Data.ByteString" Strict instance Textual B.ByteString where-    fromRope = L.toStrict . B.toLazyByteString . foldr g mempty . unRope-      where-        g piece built = (<>) (S.toBuilder piece) built+  fromRope = L.toStrict . B.toLazyByteString . foldr g mempty . unRope+    where+      g piece built = (<>) (S.toBuilder piece) built -    -- If the input ByteString does not contain valid UTF-8 then an empty-    -- Rope will be returned. That's not ideal.-    intoRope b' = case S.fromByteString b' of-        Just piece -> Rope (F.singleton piece)-        Nothing -> Rope F.empty         -- bad+  -- If the input ByteString does not contain valid UTF-8 then an empty+  -- Rope will be returned. That's not ideal.+  intoRope b' = case S.fromByteString b' of+    Just piece -> Rope (F.singleton piece)+    Nothing -> Rope F.empty -- bad -    -- ditto-    appendRope b' (Rope x) = case S.fromByteString b' of-        Just piece -> Rope ((F.|>) x piece)-        Nothing -> (Rope x)             -- bad+  -- ditto+  appendRope b' (Rope x) = case S.fromByteString b' of+    Just piece -> Rope ((F.|>) x piece)+    Nothing -> (Rope x) -- bad -{-| from "Data.ByteString.Lazy" -}+-- | from "Data.ByteString.Lazy" instance Textual L.ByteString where-    fromRope = B.toLazyByteString . foldr g mempty . unRope-      where-        g piece built = (<>) (S.toBuilder piece) built+  fromRope = B.toLazyByteString . foldr g mempty . unRope+    where+      g piece built = (<>) (S.toBuilder piece) built -    intoRope b' = Rope (L.foldrChunks ((F.<|) . check) F.empty b')-      where-        check chunk = case S.fromByteString chunk of-            Just piece -> piece-            Nothing -> S.empty          -- very bad+  intoRope b' = Rope (L.foldrChunks ((F.<|) . check) F.empty b')+    where+      check chunk = case S.fromByteString chunk of+        Just piece -> piece+        Nothing -> S.empty -- very bad  instance Textual Bytes where-    fromRope = intoBytes . (fromRope :: Rope -> B.ByteString)-    intoRope = intoRope . unBytes+  fromRope = intoBytes . (fromRope :: Rope -> B.ByteString)+  intoRope = intoRope . unBytes  instance Binary Rope where-    fromBytes = intoRope . unBytes-    intoBytes = intoBytes . (fromRope :: Rope -> B.ByteString)-+  fromBytes = intoRope . unBytes+  intoBytes = intoBytes . (fromRope :: Rope -> B.ByteString) -{-|-If you /know/ the input bytes are valid UTF-8 encoded characters, then-you can use this function to convert to a piece of @Rope@.--}+-- |+-- If you /know/ the input bytes are valid UTF-8 encoded characters, then+-- you can use this function to convert to a piece of @Rope@. unsafeIntoRope :: B.ByteString -> Rope unsafeIntoRope = Rope . F.singleton . S.fromByteStringUnsafe -{-| from "Data.String" -}+-- | from "Data.String" instance Textual [Char] where-    fromRope (Rope x) = foldr h [] x-      where-        h piece string = (S.unpack piece) ++ string -- ugh-    intoRope = Rope . F.singleton . S.pack--{-|-Write the 'Rope' to the given 'Handle'.--@-import "Core.Text"-import "Core.System" -- re-exports stdout--main :: IO ()-main =-  let-    text :: 'Rope'-    text = "Hello World"-  in-    'hWrite' 'System.IO.stdout' text-@-because it's tradition.--Uses 'Data.ByteString.Builder.hPutBuilder' internally which saves all kinds-of intermediate allocation and copying because we can go from the-'Data.Text.Short.ShortText's in the finger tree to-'Data.ByteString.Short.ShortByteString' to-'Data.ByteString.Builder.Builder' to the 'System.IO.Handle''s output buffer-in one go.+  fromRope (Rope x) = foldr h [] x+    where+      h piece string = (S.unpack piece) ++ string -- ugh+  intoRope = Rope . F.singleton . S.pack -If you're working in the-<https://hackage.haskell.org/package/core-program/docs/Core-Program-Execute.html#t:Program Program>-monad, then-<https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html#v:write write>-provides an efficient way to write a @Rope@ to @stdout@.--}+-- |+-- Write the 'Rope' to the given 'Handle'.+--+-- @+-- import "Core.Text"+-- import "Core.System" -- re-exports stdout+--+-- main :: IO ()+-- main =+--   let+--     text :: 'Rope'+--     text = "Hello World"+--   in+--     'hWrite' 'System.IO.stdout' text+-- @+-- because it's tradition.+--+-- Uses 'Data.ByteString.Builder.hPutBuilder' internally which saves all kinds+-- of intermediate allocation and copying because we can go from the+-- 'Data.Text.Short.ShortText's in the finger tree to+-- 'Data.ByteString.Short.ShortByteString' to+-- 'Data.ByteString.Builder.Builder' to the 'System.IO.Handle''s output buffer+-- in one go.+--+-- If you're working in the+-- <https://hackage.haskell.org/package/core-program/docs/Core-Program-Execute.html#t:Program Program>+-- monad, then+-- <https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html#v:write write>+-- provides an efficient way to write a @Rope@ to @stdout@. hWrite :: Handle -> Rope -> IO () hWrite handle (Rope x) = B.hPutBuilder handle (foldr j mempty x)   where     j piece built = (<>) (S.toBuilder piece) built -{-|-Does the text contain this character?--We've used it to ask whether there are newlines present in a @Rope@, for-example:--@-    if 'containsCharacter' \'\\n\' text-        then handleComplexCase-        else keepItSimple-@--}+-- |+-- Does the text contain this character?+--+-- We've used it to ask whether there are newlines present in a @Rope@, for+-- example:+--+-- @+--     if 'containsCharacter' \'\\n\' text+--         then handleComplexCase+--         else keepItSimple+-- @ containsCharacter :: Char -> Rope -> Bool containsCharacter q (Rope x) = any j x   where
lib/Core/Text/Utilities.hs view
@@ -1,134 +1,332 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_HADDOCK prune #-} -{-|-Useful tools for working with 'Rope's. Support for pretty printing,-multi-line strings, and...--}-module Core.Text.Utilities (-      {-* Pretty printing -}-      Render(..)-    , render-    , renderNoAnsi-      {-* Helpers -}-    , indefinite-    , breakWords-    , breakLines-    , breakPieces-    , isNewline-    , wrap-    , calculatePositionEnd-    , underline-    , leftPadWith-    , rightPadWith-      {-* Multi-line strings -}-    , quote+-- |+-- Useful tools for working with 'Rope's. Support for pretty printing,+-- multi-line strings, and...+--+-- ![ANSI colours](AnsiColours.png)+module Core.Text.Utilities+  ( -- * Pretty printing+    Render (..),+    AnsiColour,+    bold,+    render,+    renderNoAnsi,+    dullRed,+    brightRed,+    pureRed,+    dullGreen,+    brightGreen,+    pureGreen,+    dullBlue,+    brightBlue,+    pureBlue,+    dullCyan,+    brightCyan,+    pureCyan,+    dullMagenta,+    brightMagenta,+    pureMagenta,+    dullYellow,+    brightYellow,+    pureYellow,+    pureBlack,+    dullGrey,+    brightGrey,+    pureGrey,+    pureWhite,+    dullWhite,+    brightWhite, -    -- for testing-    , intoPieces-    , intoChunks+    -- * Helpers+    indefinite,+    breakWords,+    breakLines,+    breakPieces,+    isNewline,+    wrap,+    calculatePositionEnd,+    underline,+    leftPadWith,+    rightPadWith, -    , byteChunk-) where+    -- * Multi-line strings+    quote,+    -- for testing+    intoPieces,+    intoChunks,+    byteChunk,+    intoDocA,+  )+where +import Core.Text.Breaking+import Core.Text.Bytes+import Core.Text.Parsing+import Core.Text.Rope import Data.Bits (Bits (..))+import qualified Data.ByteString as B (ByteString, length, splitAt, unpack) import Data.Char (intToDigit)-import qualified Data.ByteString as B (ByteString, splitAt, length, unpack)-import qualified Data.FingerTree as F ((<|), ViewL(..), viewl)-import qualified Data.List as List (foldl', dropWhileEnd, splitAt)-import Data.Monoid ((<>))+import Data.Colour.SRGB (sRGB, sRGB24read)+import qualified Data.FingerTree as F (ViewL (..), viewl, (<|))+import qualified Data.List as List (dropWhileEnd, foldl', splitAt) import qualified Data.Text as T-import qualified Data.Text.Short as S (ShortText, uncons, toText, replicate-    , singleton)-import Data.Text.Prettyprint.Doc (Doc, layoutPretty , annotate, reAnnotateS-    , unAnnotateS, Pretty(..), pretty, emptyDoc-    , LayoutOptions(LayoutOptions)-    , PageWidth(AvailablePerLine)-    , hsep, vcat, group, flatAlt-    , softline'-    )--import Data.Text.Prettyprint.Doc.Render.Terminal (renderLazy, AnsiStyle-    , color, Color(..))-+import Data.Text.Prettyprint.Doc+  ( Doc,+    LayoutOptions (LayoutOptions),+    PageWidth (AvailablePerLine),+    Pretty (..),+    SimpleDocStream (..),+    annotate,+    emptyDoc,+    flatAlt,+    group,+    hsep,+    layoutPretty,+    pretty,+    reAnnotateS,+    softline',+    unAnnotateS,+    vcat,+  )+import Data.Text.Prettyprint.Doc.Render.Text (renderLazy)+import qualified Data.Text.Short as S+  ( ShortText,+    replicate,+    singleton,+    toText,+    uncons,+  ) import Data.Word (Word8) import Language.Haskell.TH (litE, stringL)-import Language.Haskell.TH.Quote (QuasiQuoter(QuasiQuoter))+import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter))+import System.Console.ANSI.Codes (setSGRCode)+import System.Console.ANSI.Types (ConsoleIntensity (..), ConsoleLayer (..), SGR (..)) -import Core.Text.Bytes-import Core.Text.Breaking-import Core.Text.Parsing-import Core.Text.Rope+-- |+-- An accumulation of ANSI escape codes used to add colour when pretty+-- printing to console.+newtype AnsiColour = Escapes [SGR]  -- change AnsiStyle to a custom token type, perhaps Ansi, which -- has the escape codes already converted to Rope. -{-|-Types which can be rendered "prettily", that is, formatted by a pretty-printer and embossed with beautiful ANSI colours when printed to the-terminal.+-- |+-- Types which can be rendered "prettily", that is, formatted by a pretty+-- printer and embossed with beautiful ANSI colours when printed to the+-- terminal.+--+-- Use 'render' to build text object for later use or+-- <https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html Control.Program.Logging>'s+-- <https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html#v:writeR writeR>+-- if you're writing directly to console now.+class Render α where+  -- |+  -- Which type are the annotations of your Doc going to be expressed in?+  type Token α :: * -Use 'render' to build text object for later use or-<https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html Control.Program.Logging>'s-<https://hackage.haskell.org/package/core-program/docs/Core-Program-Logging.html#v:writeR writeR>-if you're writing directly to console now.--}+  -- |+  -- Convert semantic tokens to specific ANSI escape tokens+  colourize :: Token α -> AnsiColour -class Render α where-    {-|-Which type are the annotations of your Doc going to be expressed in?-    -}-    type Token α :: *-    {-|-Convert semantic tokens to specific ANSI escape tokens-    -}-    colourize :: Token α -> AnsiStyle-    {-|-Arrange your type as a 'Doc' @ann@, annotated with your semantic-tokens.-    -}-    intoDocA :: α -> Doc (Token α)+  -- |+  -- Arrange your type as a 'Doc' @ann@, annotated with your semantic+  -- tokens.+  highlight :: α -> Doc (Token α) +-- | Nothing should be invoking 'intoDocA'.+intoDocA :: α -> Doc (Token α)+intoDocA = error "Nothing should be invoking this method directly."++{-# DEPRECATED intoDocA "method'intoDocA' has been renamed 'highlight'; implement that instead." #-}++-- | Medium \"Scarlet Red\" (@#cc0000@ from the Tango color palette).+dullRed :: AnsiColour+dullRed =+  Escapes [SetRGBColor Foreground (sRGB24read "#CC0000")]++-- | Highlighted \"Scarlet Red\" (@#ef2929@ from the Tango color palette).+brightRed :: AnsiColour+brightRed =+  Escapes [SetRGBColor Foreground (sRGB24read "#EF2929")]++-- | Pure \"Red\" (full RGB red channel only).+pureRed :: AnsiColour+pureRed =+  Escapes [SetRGBColor Foreground (sRGB 1 0 0)]++-- | Shadowed \"Chameleon\" (@#4e9a06@ from the Tango color palette).+dullGreen :: AnsiColour+dullGreen =+  Escapes [SetRGBColor Foreground (sRGB24read "#4E9A06")]++-- | Highlighted \"Chameleon\" (@#8ae234@ from the Tango color palette).+brightGreen :: AnsiColour+brightGreen =+  Escapes [SetRGBColor Foreground (sRGB24read "#8AE234")]++-- | Pure \"Green\" (full RGB green channel only).+pureGreen :: AnsiColour+pureGreen =+  Escapes [SetRGBColor Foreground (sRGB 0 1 0)]++-- | Medium \"Sky Blue\" (@#3465a4@ from the Tango color palette).+dullBlue :: AnsiColour+dullBlue =+  Escapes [SetRGBColor Foreground (sRGB24read "#3465A4")]++-- | Highlighted \"Sky Blue\" (@#729fcf@ from the Tango color palette).+brightBlue :: AnsiColour+brightBlue =+  Escapes [SetRGBColor Foreground (sRGB24read "#729FCF")]++-- | Pure \"Blue\" (full RGB blue channel only).+pureBlue :: AnsiColour+pureBlue =+  Escapes [SetRGBColor Foreground (sRGB 0 0 1)]++-- | Dull \"Cyan\" (from the __gnome-terminal__ console theme).+dullCyan :: AnsiColour+dullCyan =+  Escapes [SetRGBColor Foreground (sRGB24read "#06989A")]++-- | Bright \"Cyan\" (from the __gnome-terminal__ console theme).+brightCyan :: AnsiColour+brightCyan =+  Escapes [SetRGBColor Foreground (sRGB24read "#34E2E2")]++-- | Pure \"Cyan\" (full RGB blue + green channels).+pureCyan :: AnsiColour+pureCyan =+  Escapes [SetRGBColor Foreground (sRGB 0 1 1)]++-- | Medium \"Plum\" (@#75507b@ from the Tango color palette).+dullMagenta :: AnsiColour+dullMagenta =+  Escapes [SetRGBColor Foreground (sRGB24read "#75507B")]++-- | Highlighted \"Plum\" (@#ad7fa8@ from the Tango color palette).+brightMagenta :: AnsiColour+brightMagenta =+  Escapes [SetRGBColor Foreground (sRGB24read "#AD7FA8")]++-- | Pure \"Magenta\" (full RGB red + blue channels).+pureMagenta :: AnsiColour+pureMagenta =+  Escapes [SetRGBColor Foreground (sRGB 1 0 1)]++-- | Shadowed \"Butter\" (@#c4a000@ from the Tango color palette).+dullYellow :: AnsiColour+dullYellow =+  Escapes [SetRGBColor Foreground (sRGB24read "#C4A000")]++-- | Highlighted \"Butter\" (@#fce94f@ from the Tango color palette).+brightYellow :: AnsiColour+brightYellow =+  Escapes [SetRGBColor Foreground (sRGB24read "#FCE94F")]++-- | Pure \"Yellow\" (full RGB red + green channels).+pureYellow :: AnsiColour+pureYellow =+  Escapes [SetRGBColor Foreground (sRGB 1 1 0)]++-- | Pure \"Black\" (zero in all RGB channels).+pureBlack :: AnsiColour+pureBlack =+  Escapes [SetRGBColor Foreground (sRGB 0 0 0)]++-- | Shadowed \"Deep Aluminium\" (@#2e3436@ from the Tango color palette).+dullGrey :: AnsiColour+dullGrey =+  Escapes [SetRGBColor Foreground (sRGB24read "#2E3436")]++-- | Medium \"Dark Aluminium\" (from the Tango color palette).+brightGrey :: AnsiColour+brightGrey =+  Escapes [SetRGBColor Foreground (sRGB24read "#555753")]++-- | Pure \"Grey\" (set at @#999999@, being just over half in all RGB channels).+pureGrey :: AnsiColour+pureGrey =+  Escapes [SetRGBColor Foreground (sRGB24read "#999999")]++-- | Pure \"White\" (fully on in all RGB channels).+pureWhite :: AnsiColour+pureWhite =+  Escapes [SetRGBColor Foreground (sRGB 1 1 1)]++-- | Medium \"Light Aluminium\" (@#d3d7cf@ from the Tango color palette).+dullWhite :: AnsiColour+dullWhite =+  Escapes [SetRGBColor Foreground (sRGB24read "#D3D7CF")]++-- | Highlighted \"Light Aluminium\" (@#eeeeec@ from the Tango color palette).+brightWhite :: AnsiColour+brightWhite =+  Escapes [SetRGBColor Foreground (sRGB24read "#EEEEEC")]++-- |+-- Given an 'AnsiColour', lift it to bold intensity.+--+-- Note that many console fonts do /not/ have a bold face variant, and+-- terminal emulators that "support bold" do so by doubling the thickness of+-- the lines in the glyphs. This may or may not be desirable from a+-- readibility standpoint but really there's only so much you can do to keep+-- users who make poor font choices from making poor font choices.+bold :: AnsiColour -> AnsiColour+bold (Escapes list) =+  Escapes (SetConsoleIntensity BoldIntensity : list)++instance Semigroup AnsiColour where+  (<>) (Escapes list1) (Escapes list2) = Escapes (list1 <> list2)++instance Monoid AnsiColour where+  mempty = Escapes []+ instance Render Rope where-    type Token Rope = ()-    colourize = const mempty-    intoDocA = foldr f emptyDoc . unRope-      where-        f :: S.ShortText -> Doc () -> Doc ()-        f piece built = (<>) (pretty (S.toText piece)) built+  type Token Rope = ()+  colourize = const mempty+  highlight = foldr f emptyDoc . unRope+    where+      f :: S.ShortText -> Doc () -> Doc ()+      f piece built = (<>) (pretty (S.toText piece)) built  instance Render Char where-    type Token Char = ()-    colourize = const mempty-    intoDocA c = pretty c+  type Token Char = ()+  colourize = const mempty+  highlight c = pretty c  instance (Render a) => Render [a] where-    type Token [a] = Token a-    colourize = colourize @a-    intoDocA = mconcat . fmap intoDocA+  type Token [a] = Token a+  colourize = colourize @a+  highlight = mconcat . fmap highlight  instance Render T.Text where-    type Token T.Text = ()-    colourize = const mempty-    intoDocA t = pretty t+  type Token T.Text = ()+  colourize = const mempty+  highlight t = pretty t  -- (), aka Unit, aka **1**, aka something with only one inhabitant  instance Render Bytes where-    type Token Bytes = ()-    colourize = const (color Green)-    intoDocA = prettyBytes+  type Token Bytes = ()+  colourize = const brightGreen+  highlight = prettyBytes  prettyBytes :: Bytes -> Doc ()-prettyBytes = annotate () . vcat . twoWords-    . fmap wordToHex . byteChunk . unBytes+prettyBytes =+  annotate () . vcat . twoWords+    . fmap wordToHex+    . byteChunk+    . unBytes  twoWords :: [Doc ann] -> [Doc ann] twoWords ds = go ds@@ -136,10 +334,8 @@     go [] = []     go [x] = [softline' <> x]     go xs =-      let-        (one:two:[], remainder) = List.splitAt 2 xs-      in-        group (one <> spacer <> two) : go remainder+      let (one : two : [], remainder) = List.splitAt 2 xs+       in group (one <> spacer <> two) : go remainder      spacer = flatAlt softline' "  " @@ -147,230 +343,243 @@ byteChunk = reverse . go []   where     go acc blob =-      let-        (eight, remainder) = B.splitAt 8 blob-      in-        if B.length remainder == 0+      let (eight, remainder) = B.splitAt 8 blob+       in if B.length remainder == 0             then eight : acc             else go (eight : acc) remainder  -- Take an [up to] 8 byte (64 bit) word wordToHex :: B.ByteString -> Doc ann wordToHex eight =-  let-    ws = B.unpack eight-    ds = fmap byteToHex ws-  in-    hsep ds+  let ws = B.unpack eight+      ds = fmap byteToHex ws+   in hsep ds  byteToHex :: Word8 -> Doc ann byteToHex c = pretty hi <> pretty low   where-    !low      = byteToDigit $ c .&. 0xf-    !hi       = byteToDigit $ (c .&. 0xf0) `shiftR` 4+    !low = byteToDigit $ c .&. 0xf+    !hi = byteToDigit $ (c .&. 0xf0) `shiftR` 4      byteToDigit :: Word8 -> Char     byteToDigit = intToDigit . fromIntegral -{-|-Given an object of a type with a 'Render' instance, transform it into a-Rope saturated with ANSI escape codes representing syntax highlighting or-similar colouring, wrapping at the specified @width@.--The obvious expectation is that the next thing you're going to do is send-the Rope to console with:--@-    'Core.Program.Execute.write' ('render' 80 thing)-@--However, the /better/ thing to do is to instead use:--@-    'Core.Program.Execute.writeR' thing-@+-- |+-- Given an object of a type with a 'Render' instance, transform it into a+-- Rope saturated with ANSI escape codes representing syntax highlighting or+-- similar colouring, wrapping at the specified @width@.+--+-- The obvious expectation is that the next thing you're going to do is send+-- the Rope to console with:+--+-- @+--     'Core.Program.Execute.write' ('render' 80 thing)+-- @+--+-- However, the /better/ thing to do is to instead use:+--+-- @+--     'Core.Program.Execute.writeR' thing+-- @+--+-- which is able to pretty print the document text respecting the available+-- width of the terminal. -which is able to pretty print the document text respecting the available-width of the terminal.--} -- the annotation (_ :: α) of the parameter is to bring type a into scope -- at term level so that it can be used by TypedApplications. Which then -- needed AllowAmbiguousTypes, but with all that finally it works: -- colourize no longer needs a in its type signature. render :: Render α => Int -> α -> Rope render columns (thing :: α) =-  let-    options = LayoutOptions (AvailablePerLine (columns - 1) 1.0)-  in-    intoRope . renderLazy . reAnnotateS (colourize @α)-                . layoutPretty options . intoDocA $ thing+  let options = LayoutOptions (AvailablePerLine (columns - 1) 1.0)+   in intoRope . go [] . reAnnotateS (colourize @α)+        . layoutPretty options+        . highlight+        $ thing+  where+    go :: [AnsiColour] -> SimpleDocStream AnsiColour -> Rope+    go as x = case x of+      SFail -> error "Unhandled SFail"+      SEmpty -> emptyRope+      SChar c xs ->+        singletonRope c <> go as xs+      SText _ t xs ->+        intoRope t <> go as xs+      SLine len xs ->+        singletonRope '\n'+          <> intoRope (S.replicate len (S.singleton ' '))+          <> go as xs+      SAnnPush a xs ->+        intoRope (convert a) <> go (a : as) xs+      SAnnPop xs ->+        case as of+          [] -> error "Popped an empty stack"+          -- First discard the current one that's just been popped. Then look+          -- at the next one: if it's the last one, we reset the console back+          -- to normal mode. But if they're piled up, then return to the+          -- previous formatting.+          (_ : as') -> case as' of+            [] -> reset <> go [] xs+            (a : _) -> convert a <> go as' xs -{-|-Having gone to all the trouble to colourize your rendered types...-sometimes you don't want that. This function is like 'render', but removes-all the ANSI escape codes so it comes outformatted but as plain black &-white text.--}+    convert :: AnsiColour -> Rope+    convert (Escapes codes) = intoRope (setSGRCode codes)++    reset :: Rope+    reset = intoRope (setSGRCode [Reset])++-- |+-- Having gone to all the trouble to colourize your rendered types...+-- sometimes you don't want that. This function is like 'render', but removes+-- all the ANSI escape codes so it comes outformatted but as plain black &+-- white text. renderNoAnsi :: Render α => Int -> α -> Rope renderNoAnsi columns (thing :: α) =-  let-    options = LayoutOptions (AvailablePerLine (columns - 1) 1.0)-  in-    intoRope . renderLazy . unAnnotateS-                . layoutPretty options . intoDocA $ thing+  let options = LayoutOptions (AvailablePerLine (columns - 1) 1.0)+   in intoRope . renderLazy . unAnnotateS+        . layoutPretty options+        . highlight+        $ thing  --+ -- | Render "a" or "an" in front of a word depending on English's idea of -- whether it's a vowel or not.--- indefinite :: Rope -> Rope indefinite text =-  let-    x = unRope text-  in-    case F.viewl x of+  let x = unRope text+   in case F.viewl x of         F.EmptyL -> text         piece F.:< _ -> case S.uncons piece of-            Nothing -> text-            Just (c,_)  -> if c `elem` ['A','E','I','O','U','a','e','i','o','u']-                then intoRope ("an " F.<| x)-                else intoRope ("a " F.<| x)--{-|-Often the input text represents a paragraph, but does not have any internal-newlines (representing word wrapping). This function takes a line of text-and inserts newlines to simulate such folding, keeping the line under-the supplied maximum width.--A single word that is excessively long will be included as-is on its own-line (that line will exceed the desired maxium width).+          Nothing -> text+          Just (c, _) ->+            if c `elem` ['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u']+              then intoRope ("an " F.<| x)+              else intoRope ("a " F.<| x) -Any trailing newlines will be removed.--}+-- |+-- Often the input text represents a paragraph, but does not have any internal+-- newlines (representing word wrapping). This function takes a line of text+-- and inserts newlines to simulate such folding, keeping the line under+-- the supplied maximum width.+--+-- A single word that is excessively long will be included as-is on its own+-- line (that line will exceed the desired maxium width).+--+-- Any trailing newlines will be removed. wrap :: Int -> Rope -> Rope wrap margin text =-  let-    built = wrapHelper margin (breakWords text)-  in-    built+  let built = wrapHelper margin (breakWords text)+   in built  wrapHelper :: Int -> [Rope] -> Rope wrapHelper _ [] = ""-wrapHelper _ [x]  = x-wrapHelper margin (x:xs) =-    snd $ List.foldl' (wrapLine margin) (widthRope x, x) xs+wrapHelper _ [x] = x+wrapHelper margin (x : xs) =+  snd $ List.foldl' (wrapLine margin) (widthRope x, x) xs  wrapLine :: Int -> (Int, Rope) -> Rope -> (Int, Rope)-wrapLine margin (pos,builder) word =-  let-    wide = widthRope word-    wide' = pos + wide + 1-  in-    if wide' > margin-        then (wide , builder <> "\n" <> word)-        else (wide', builder <> " "  <> word)-+wrapLine margin (pos, builder) word =+  let wide = widthRope word+      wide' = pos + wide + 1+   in if wide' > margin+        then (wide, builder <> "\n" <> word)+        else (wide', builder <> " " <> word)  underline :: Char -> Rope -> Rope underline level text =-  let-    title = fromRope text-    line = T.map (\_ -> level) title-  in-    intoRope line+  let title = fromRope text+      line = T.map (\_ -> level) title+   in intoRope line -{-|-Pad a pieve of text on the left with a specified character to the desired-width. This function is named in homage to the famous result from Computer-Science known as @leftPad@ which has a glorious place in the history of the-world-wide web.--}+-- |+-- Pad a pieve of text on the left with a specified character to the desired+-- width. This function is named in homage to the famous result from Computer+-- Science known as @leftPad@ which has a glorious place in the history of the+-- world-wide web. leftPadWith :: Char -> Int -> Rope -> Rope leftPadWith c digits text =-    intoRope pad <> text+  intoRope pad <> text   where     pad = S.replicate len (S.singleton c)     len = digits - widthRope text --{-|-Right pad a text with the specified character.--}+-- |+-- Right pad a text with the specified character. rightPadWith :: Char -> Int -> Rope -> Rope rightPadWith c digits text =-    text <> intoRope pad+  text <> intoRope pad   where     pad = S.replicate len (S.singleton c)     len = digits - widthRope text --{-|-Multi-line string literals.--To use these you need to enable the @QuasiQuotes@ language extension-in your source file:--@-\{\-\# LANGUAGE OverloadedStrings \#\-\}-\{\-\# LANGUAGE QuasiQuotes \#\-\}-@--you are then able to easily write a string stretching over several lines.--How best to formatting multi-line string literal within your source code is-an aesthetic judgement. Sometimes you don't care about the whitespace-leading a passage (8 spaces in this example):--@-    let message = ['quote'|-        This is a test of the Emergency Broadcast System. Do not be-        alarmed. If this were a real emergency, someone would have tweeted-        about it by now.-    |]-@--because you are feeding it into a 'Data.Text.Prettyprint.Doc.Doc' for-pretty printing and know the renderer will convert the whole text into a-single line and then re-flow it. Other times you will want to have the-string as is, literally:--@-    let poem = ['quote'|-If the sun-    rises-        in the-    west-you     drank-    too much-                last week.-    |]-@--Leading whitespace from the first line and trailing whitespace from the-last line will be trimmed, so this:--@-    let value = ['quote'|-Hello-    |]-@--is translated to:--@-    let value = 'Data.String.fromString' \"Hello\\n\"-@--without the leading newline or trailing four spaces. Note that as string-literals they are presented to your code with 'Data.String.fromString' @::-String -> α@ so any type with an 'Data.String.IsString' instance (as 'Rope'-has) can be constructed from a multi-line @['quote'| ... |]@ literal.+-- |+-- Multi-line string literals.+--+-- To use these you need to enable the @QuasiQuotes@ language extension+-- in your source file:+--+-- @+-- \{\-\# LANGUAGE OverloadedStrings \#\-\}+-- \{\-\# LANGUAGE QuasiQuotes \#\-\}+-- @+--+-- you are then able to easily write a string stretching over several lines.+--+-- How best to formatting multi-line string literal within your source code is+-- an aesthetic judgement. Sometimes you don't care about the whitespace+-- leading a passage (8 spaces in this example):+--+-- @+--     let message = ['quote'|+--         This is a test of the Emergency Broadcast System. Do not be+--         alarmed. If this were a real emergency, someone would have tweeted+--         about it by now.+--     |]+-- @+--+-- because you are feeding it into a 'Data.Text.Prettyprint.Doc.Doc' for+-- pretty printing and know the renderer will convert the whole text into a+-- single line and then re-flow it. Other times you will want to have the+-- string as is, literally:+--+-- @+--     let poem = ['quote'|+-- If the sun+--     rises+--         in the+--     west+-- you     drank+--     too much+--                 last week.+--     |]+-- @+--+-- Leading whitespace from the first line and trailing whitespace from the+-- last line will be trimmed, so this:+--+-- @+--     let value = ['quote'|+-- Hello+--     |]+-- @+--+-- is translated to:+--+-- @+--     let value = 'Data.String.fromString' \"Hello\\n\"+-- @+--+-- without the leading newline or trailing four spaces. Note that as string+-- literals they are presented to your code with 'Data.String.fromString' @::+-- String -> α@ so any type with an 'Data.String.IsString' instance (as 'Rope'+-- has) can be constructed from a multi-line @['quote'| ... |]@ literal. --} -- I thought this was going to be more complicated. quote :: QuasiQuoter-quote = QuasiQuoter-    (litE . stringL . trim)        -- in an expression+quote =+  QuasiQuoter+    (litE . stringL . trim) -- in an expression     (error "Cannot use [quote| ... |] in a pattern")     (error "Cannot use [quote| ... |] as a type")     (error "Cannot use [quote| ... |] for a declaration")@@ -379,8 +588,7 @@     trim = bot . top      top [] = []-    top ('\n':cs) = cs+    top ('\n' : cs) = cs     top str = str      bot = List.dropWhileEnd (== ' ')-