packages feed

core-text 0.3.0.0 → 0.3.2.0

raw patch · 7 files changed

+879/−830 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Core.Text.Bytes: emptyBytes :: Bytes
+ Core.Text.Bytes: packBytes :: String -> Bytes
+ Core.Text.Rope: takeRope :: Int -> Rope -> Rope
+ Core.Text.Utilities: instance Core.Text.Utilities.Render GHC.Base.String

Files

LICENSE view
@@ -1,32 +1,19 @@-Opinionated Haskell Interoperability--Copyright © 2018-2019 Athae Eredh Siniath and Others-All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions-are met:+Copyright © 2018-2021 Athae Eredh Siniath and Others -    1. Redistributions of source code must retain the above copyright-       notice, this list of conditions and the following disclaimer.+Permission is hereby granted, free of charge, to any person obtaining a+copy of this software and associated documentation files (the "Software"),+to deal in the Software without restriction, including without limitation+the rights to use, copy, modify, merge, publish, distribute, sublicense,+and/or sell copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following conditions: -    2. Redistributions in binary form must reproduce the above-       copyright notice, this list of conditions and the following-       disclaimer in the documentation and/or other materials provided-       with the distribution.-      -    3. Neither the name of the project nor the names of its contributors-       may be used to endorse or promote products derived from this -       software without specific prior written permission.+The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.
core-text.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.18 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack ----- hash: 32fe0043eab486639c92b9e8ea6c9c864fd2aa6c21f0861529c72898e51bc67a+-- hash: 5360f5729fffd35e21f13c06edbd41bdc1ffc7334f3f456b64bb16738bcc742d  name:           core-text-version:        0.3.0.0+version:        0.3.2.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@@ -29,10 +29,11 @@ bug-reports:    https://github.com/aesiniath/unbeliever/issues author:         Andrew Cowie <istathar@gmail.com> maintainer:     Andrew Cowie <istathar@gmail.com>-copyright:      © 2018-2020 Athae Eredh Siniath and Others-license:        BSD3+copyright:      © 2018-2021 Athae Eredh Siniath and Others+license:        MIT license-file:   LICENSE-tested-with:    GHC == 8.8.4+tested-with:+    GHC == 8.10.6 build-type:     Simple extra-doc-files:     AnsiColours.png
lib/Core/Text/Breaking.hs view
@@ -2,81 +2,84 @@ {-# OPTIONS_HADDOCK hide #-}  -- This is an Internal module, hidden from Haddock-module Core.Text.Breaking-  ( breakWords,+module Core.Text.Breaking (+    breakWords,     breakLines,     breakPieces,     intoPieces,     intoChunks,     isNewline,-  )-where+) where  import Core.Text.Rope import Data.Char (isSpace) import Data.List (uncons) import qualified Data.Text.Short as S (ShortText, break, empty, null, uncons) --- |--- 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__--- []--- @+{- |+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@@ -86,13 +89,13 @@ -- 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-        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)+    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)  -- -- λ> S.break isSpace "a d"@@ -126,16 +129,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-        then intoRope chunk : emptyRope : []-        else intoRope chunk : intoChunks predicate remainder'+        -- 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
@@ -6,136 +6,165 @@ {-# 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.-module Core.Text.Bytes-  ( 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,+    emptyBytes,+    packBytes,     Binary (fromBytes, intoBytes),     hOutput,     hInput,      -- * Internals     unBytes,-  )-where+) where -import qualified Data.ByteString as B-  ( ByteString,+import qualified Data.ByteString as B (+    ByteString,+    empty,     hGetContents,     hPut,     pack,     unpack,-  )+ ) import qualified Data.ByteString.Builder as B (Builder, byteString, toLazyByteString)+import qualified Data.ByteString.Char8 as C (+    pack+ ) import qualified Data.ByteString.Lazy as L (ByteString, fromStrict, toStrict) import Data.Hashable (Hashable) import Data.Word (Word8) import GHC.Generics (Generic) 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+    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)--- @------ on the assumption that the bytes in question are UTF-8 (or plain ASCII)--- encoded.+{- |+A zero-length 'Bytes'.+-}+emptyBytes :: Bytes+emptyBytes = StrictBytes B.empty++{- |+For the annoyingly common case of needing to take an ASCII string literal in+your code and use it as a bunch of 'Bytes'.++Done via "Data.ByteString.Char8" so all @Char@s will be truncated to 8 bits+(/i.e./ Latin-1 characters less than 255). You should probably consider this+to be unsafe. Also note that we deliberately do not have a @[Char]@ instance+of 'Binary'; if you need to come back to a textual representation use+'intoRope'.+-}+packBytes :: String -> Bytes+packBytes = StrictBytes . C.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)+@++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.-hInput :: Handle -> IO Bytes-hInput handle = do-  contents <- B.hGetContents handle-  return (StrictBytes contents)+{- |+Read the (entire) contents of a handle into a Bytes object. -{--instance Show Bytes where-    show x = case x of-        StrictBytes b' ->+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)
lib/Core/Text/Parsing.hs view
@@ -12,12 +12,12 @@ import Data.Foldable (foldl') import qualified Data.Text.Short as S (ShortText, foldl') --- |--- 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)
lib/Core/Text/Rope.hs view
@@ -8,70 +8,70 @@ {-# 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.------ 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+{- |+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,@@ -79,6 +79,7 @@     replicateChar,     widthRope,     splitRope,+    takeRope,     insertRope,     containsCharacter,     findIndexRope,@@ -92,23 +93,22 @@     nullRope,     unsafeIntoRope,     Width (..),-  )-where+) where  import Control.DeepSeq (NFData (..)) import Core.Text.Bytes import qualified Data.ByteString as B (ByteString)-import qualified Data.ByteString.Builder as B-  ( hPutBuilder,+import qualified Data.ByteString.Builder as B (+    hPutBuilder,     toLazyByteString,-  )-import qualified Data.ByteString.Lazy as L-  ( ByteString,+ )+import qualified Data.ByteString.Lazy as L (+    ByteString,     foldrChunks,     toStrict,-  )-import qualified Data.FingerTree as F-  ( FingerTree,+ )+import qualified Data.FingerTree as F (+    FingerTree,     Measured (..),     SearchResult (..),     ViewL (..),@@ -120,25 +120,25 @@     (<|),     (><),     (|>),-  )+ ) import Data.Foldable (foldl', foldr', toList) import Data.Hashable (Hashable, hashWithSalt) import Data.String (IsString (..)) import qualified Data.Text as T (Text)-import qualified Data.Text.Lazy as U-  ( Text,+import qualified Data.Text.Lazy as U (+    Text,     foldrChunks,     fromChunks,     toStrict,-  )-import qualified Data.Text.Lazy.Builder as U-  ( Builder,+ )+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,+import qualified Data.Text.Short as S (+    ShortText,     any,     append,     empty,@@ -154,156 +154,162 @@     toBuilder,     toText,     unpack,-  )+ ) import qualified Data.Text.Short.Unsafe as S (fromByteStringUnsafe) import GHC.Generics (Generic) import System.IO (Handle) --- |--- 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+    pretty (Rope x) = foldr ((<>) . pretty . S.toText) emptyDoc x --- |--- 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--- @+{- |+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.+{- |+A 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 {-# 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'+    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/------ 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@@ -311,60 +317,75 @@  nullRope :: Rope -> Bool nullRope (Rope x) = case F.viewl x of-  F.EmptyL -> True-  (F.:<) piece _ -> S.null piece+    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:------ @--- λ> __splitRope 7 \"abcdef\"__--- (\"abcdef\",\"\")--- λ> __splitRope (-1) \"abcdef\"__--- (\"\", \"abcdef\")--- @+{- |+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-        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))-        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"+    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))+            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"--- @+{- |+Take the first _n_ characters from the beginning of the Rope.++@+λ> __takeRope 3 \"123456789\"__+\"123\"+@+-}+takeRope :: Int -> Rope -> Rope+takeRope i text =+    let (before, _) = splitRope i text+     in before++{- |+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@@ -372,10 +393,10 @@     -- convert this to Maybe monad, maybe     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@@ -390,174 +411,172 @@ -- 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+    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/------ 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.+{- |+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 -> α+    -- | Convert a @Rope@ into another text-like type.+    fromRope :: Rope -> α -  -- |-  -- Take another text-like type and convert it to a @Rope@.-  intoRope :: α -> 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" 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 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" 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 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" 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" 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+    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.------ 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
@@ -8,13 +8,14 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_HADDOCK prune #-} --- |--- 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+{- |+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,@@ -65,8 +66,7 @@     intoChunks,     byteChunk,     intoDocA,-  )-where+) where  import Core.Text.Breaking import Core.Text.Bytes@@ -79,8 +79,8 @@ import qualified Data.FingerTree as F (ViewL (..), viewl, (<|)) import qualified Data.List as List (dropWhileEnd, foldl', splitAt) import qualified Data.Text as T-import Data.Text.Prettyprint.Doc-  ( Doc,+import Data.Text.Prettyprint.Doc (+    Doc,     LayoutOptions (LayoutOptions),     PageWidth (AvailablePerLine),     Pretty (..),@@ -96,237 +96,240 @@     softline',     unAnnotateS,     vcat,-  )+ ) import Data.Text.Prettyprint.Doc.Render.Text (renderLazy)-import qualified Data.Text.Short as S-  ( ShortText,+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 System.Console.ANSI.Codes (setSGRCode) import System.Console.ANSI.Types (ConsoleIntensity (..), ConsoleLayer (..), SGR (..)) --- |--- An accumulation of ANSI escape codes used to add colour when pretty--- printing to console.+{- |+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.------ 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.+{- |+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 α :: *+    -- | 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 α -> AnsiColour+    -- | Convert semantic tokens to specific ANSI escape tokens+    colourize :: Token α -> AnsiColour -  -- |-  -- Arrange your type as a 'Doc' @ann@, annotated with your semantic-  -- tokens.-  highlight :: α -> 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")]+    Escapes [SetRGBColor Foreground (sRGB24read "#CC0000")]  -- | Highlighted \"Scarlet Red\" (@#ef2929@ from the Tango color palette). brightRed :: AnsiColour brightRed =-  Escapes [SetRGBColor Foreground (sRGB24read "#EF2929")]+    Escapes [SetRGBColor Foreground (sRGB24read "#EF2929")]  -- | Pure \"Red\" (full RGB red channel only). pureRed :: AnsiColour pureRed =-  Escapes [SetRGBColor Foreground (sRGB 1 0 0)]+    Escapes [SetRGBColor Foreground (sRGB 1 0 0)]  -- | Shadowed \"Chameleon\" (@#4e9a06@ from the Tango color palette). dullGreen :: AnsiColour dullGreen =-  Escapes [SetRGBColor Foreground (sRGB24read "#4E9A06")]+    Escapes [SetRGBColor Foreground (sRGB24read "#4E9A06")]  -- | Highlighted \"Chameleon\" (@#8ae234@ from the Tango color palette). brightGreen :: AnsiColour brightGreen =-  Escapes [SetRGBColor Foreground (sRGB24read "#8AE234")]+    Escapes [SetRGBColor Foreground (sRGB24read "#8AE234")]  -- | Pure \"Green\" (full RGB green channel only). pureGreen :: AnsiColour pureGreen =-  Escapes [SetRGBColor Foreground (sRGB 0 1 0)]+    Escapes [SetRGBColor Foreground (sRGB 0 1 0)]  -- | Medium \"Sky Blue\" (@#3465a4@ from the Tango color palette). dullBlue :: AnsiColour dullBlue =-  Escapes [SetRGBColor Foreground (sRGB24read "#3465A4")]+    Escapes [SetRGBColor Foreground (sRGB24read "#3465A4")]  -- | Highlighted \"Sky Blue\" (@#729fcf@ from the Tango color palette). brightBlue :: AnsiColour brightBlue =-  Escapes [SetRGBColor Foreground (sRGB24read "#729FCF")]+    Escapes [SetRGBColor Foreground (sRGB24read "#729FCF")]  -- | Pure \"Blue\" (full RGB blue channel only). pureBlue :: AnsiColour pureBlue =-  Escapes [SetRGBColor Foreground (sRGB 0 0 1)]+    Escapes [SetRGBColor Foreground (sRGB 0 0 1)]  -- | Dull \"Cyan\" (from the __gnome-terminal__ console theme). dullCyan :: AnsiColour dullCyan =-  Escapes [SetRGBColor Foreground (sRGB24read "#06989A")]+    Escapes [SetRGBColor Foreground (sRGB24read "#06989A")]  -- | Bright \"Cyan\" (from the __gnome-terminal__ console theme). brightCyan :: AnsiColour brightCyan =-  Escapes [SetRGBColor Foreground (sRGB24read "#34E2E2")]+    Escapes [SetRGBColor Foreground (sRGB24read "#34E2E2")]  -- | Pure \"Cyan\" (full RGB blue + green channels). pureCyan :: AnsiColour pureCyan =-  Escapes [SetRGBColor Foreground (sRGB 0 1 1)]+    Escapes [SetRGBColor Foreground (sRGB 0 1 1)]  -- | Medium \"Plum\" (@#75507b@ from the Tango color palette). dullMagenta :: AnsiColour dullMagenta =-  Escapes [SetRGBColor Foreground (sRGB24read "#75507B")]+    Escapes [SetRGBColor Foreground (sRGB24read "#75507B")]  -- | Highlighted \"Plum\" (@#ad7fa8@ from the Tango color palette). brightMagenta :: AnsiColour brightMagenta =-  Escapes [SetRGBColor Foreground (sRGB24read "#AD7FA8")]+    Escapes [SetRGBColor Foreground (sRGB24read "#AD7FA8")]  -- | Pure \"Magenta\" (full RGB red + blue channels). pureMagenta :: AnsiColour pureMagenta =-  Escapes [SetRGBColor Foreground (sRGB 1 0 1)]+    Escapes [SetRGBColor Foreground (sRGB 1 0 1)]  -- | Shadowed \"Butter\" (@#c4a000@ from the Tango color palette). dullYellow :: AnsiColour dullYellow =-  Escapes [SetRGBColor Foreground (sRGB24read "#C4A000")]+    Escapes [SetRGBColor Foreground (sRGB24read "#C4A000")]  -- | Highlighted \"Butter\" (@#fce94f@ from the Tango color palette). brightYellow :: AnsiColour brightYellow =-  Escapes [SetRGBColor Foreground (sRGB24read "#FCE94F")]+    Escapes [SetRGBColor Foreground (sRGB24read "#FCE94F")]  -- | Pure \"Yellow\" (full RGB red + green channels). pureYellow :: AnsiColour pureYellow =-  Escapes [SetRGBColor Foreground (sRGB 1 1 0)]+    Escapes [SetRGBColor Foreground (sRGB 1 1 0)]  -- | Pure \"Black\" (zero in all RGB channels). pureBlack :: AnsiColour pureBlack =-  Escapes [SetRGBColor Foreground (sRGB 0 0 0)]+    Escapes [SetRGBColor Foreground (sRGB 0 0 0)]  -- | Shadowed \"Deep Aluminium\" (@#2e3436@ from the Tango color palette). dullGrey :: AnsiColour dullGrey =-  Escapes [SetRGBColor Foreground (sRGB24read "#2E3436")]+    Escapes [SetRGBColor Foreground (sRGB24read "#2E3436")]  -- | Medium \"Dark Aluminium\" (from the Tango color palette). brightGrey :: AnsiColour brightGrey =-  Escapes [SetRGBColor Foreground (sRGB24read "#555753")]+    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")]+    Escapes [SetRGBColor Foreground (sRGB24read "#999999")]  -- | Pure \"White\" (fully on in all RGB channels). pureWhite :: AnsiColour pureWhite =-  Escapes [SetRGBColor Foreground (sRGB 1 1 1)]+    Escapes [SetRGBColor Foreground (sRGB 1 1 1)]  -- | Medium \"Light Aluminium\" (@#d3d7cf@ from the Tango color palette). dullWhite :: AnsiColour dullWhite =-  Escapes [SetRGBColor Foreground (sRGB24read "#D3D7CF")]+    Escapes [SetRGBColor Foreground (sRGB24read "#D3D7CF")]  -- | Highlighted \"Light Aluminium\" (@#eeeeec@ from the Tango color palette). brightWhite :: AnsiColour brightWhite =-  Escapes [SetRGBColor Foreground (sRGB24read "#EEEEEC")]+    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.+{- |+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)+    Escapes (SetConsoleIntensity BoldIntensity : list)  instance Semigroup AnsiColour where-  (<>) (Escapes list1) (Escapes list2) = Escapes (list1 <> list2)+    (<>) (Escapes list1) (Escapes list2) = Escapes (list1 <> list2)  instance Monoid AnsiColour where-  mempty = Escapes []+    mempty = Escapes []  instance Render Rope where-  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+    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-  highlight 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-  highlight = mconcat . fmap highlight+    type Token [a] = Token a+    colourize = colourize @a+    highlight = mconcat . fmap highlight +instance Render String where+    type Token String = Token Char+    colourize = colourize @Char+    highlight = mconcat . fmap highlight+ instance Render T.Text where-  type Token T.Text = ()-  colourize = const mempty-  highlight 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 brightGreen-  highlight = prettyBytes+    type Token Bytes = ()+    colourize = const brightGreen+    highlight = prettyBytes  prettyBytes :: Bytes -> Doc () prettyBytes =-  annotate () . vcat . twoWords-    . fmap wordToHex-    . byteChunk-    . unBytes+    annotate () . vcat . twoWords+        . fmap wordToHex+        . byteChunk+        . unBytes  twoWords :: [Doc ann] -> [Doc ann] twoWords ds = go ds@@ -334,8 +337,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' "  " @@ -343,17 +346,17 @@ byteChunk = reverse . go []   where     go acc blob =-      let (eight, remainder) = B.splitAt 8 blob-       in if B.length remainder == 0-            then eight : acc-            else go (eight : acc) remainder+        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@@ -364,63 +367,64 @@     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--- @------ which is able to pretty print the document text respecting the available--- width of the terminal.+{- |+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.+-}+ -- 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 . go [] . reAnnotateS (colourize @α)-        . layoutPretty options-        . highlight-        $ thing+    let options = LayoutOptions (AvailablePerLine (columns - 1) 1.0)+     in 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+        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'+                <> replicateChar len ' '+                <> 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      convert :: AnsiColour -> Rope     convert (Escapes codes) = intoRope (setSGRCode codes)@@ -428,161 +432,167 @@     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.+{- |+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-        . highlight-        $ 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.+{- |+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-        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)+    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).------ 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+    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)+    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-    (error "Cannot use [quote| ... |] in a pattern")-    (error "Cannot use [quote| ... |] as a type")-    (error "Cannot use [quote| ... |] for a declaration")+    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")   where     trim :: String -> String     trim = bot . top