text-rope 0.1 → 0.2
raw patch · 12 files changed
+709/−155 lines, 12 files
Files
- changelog.md +5/−0
- src/Data/Text/Lines/Internal.hs +32/−30
- src/Data/Text/Rope.hs +17/−5
- src/Data/Text/Utf16/Lines.hs +21/−102
- src/Data/Text/Utf16/Rope.hs +17/−5
- src/Data/Text/Utf16/Rope/Mixed.hs +491/−0
- test/CharLines.hs +10/−3
- test/Main.hs +2/−0
- test/MixedRope.hs +86/−0
- test/Utf16Lines.hs +10/−3
- test/Utils.hs +11/−4
- text-rope.cabal +7/−3
changelog.md view
@@ -1,3 +1,8 @@+# 0.2++* Share `TextLines` between `Char` and UTF-16 modules.+* Add `Data.Text.Utf16.Rope.Mixed`.+ # 0.1 * Initial release.
src/Data/Text/Lines/Internal.hs view
@@ -30,14 +30,17 @@ , intToWord ) where -import Prelude ((+), (-), subtract, quot, fromIntegral, seq)+import Prelude ((+), (-), (*), subtract, quot, fromIntegral, seq, error) import Control.DeepSeq (NFData, rnf) import Data.Bits (toIntegralSized)-import Data.Bool (Bool, otherwise)+import Data.Bool (Bool, otherwise, not) import Data.Char (Char) import Data.Eq (Eq, (==))+import Data.Foldable (foldMap) import Data.Function (on, (.), ($)) import Data.Int (Int)+import Data.List (map, mapAccumL, filter)+import Data.List.NonEmpty (NonEmpty(..)) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Ord (Ord, compare, (<=), (<), (>))@@ -46,6 +49,7 @@ import qualified Data.Text.Array as TA import Data.Text.Internal (Text(..)) import qualified Data.Text as T+import Data.Tuple (snd) import qualified Data.Vector.Unboxed as U import Data.Word (Word) import Foreign.C.Types (CSize(..))@@ -57,13 +61,11 @@ #if MIN_VERSION_text(2,0,0) #else-import Prelude ((*)) import Data.Bits (shiftR) #endif #ifdef DEBUG-import Prelude (error)-import Data.Bool ((&&), not)+import Data.Bool ((&&)) import Data.Char (generalCategory, GeneralCategory(..)) import Data.Eq ((/=)) import Data.List ((++))@@ -86,32 +88,27 @@ } instance NFData TextLines where- rnf (TextLines a b) = rnf a `seq` rnf b+ rnf = (`seq` ()) instance Eq TextLines where (==) = (==) `on` toText- {-# INLINE (==) #-} instance Ord TextLines where compare = compare `on` toText- {-# INLINE compare #-} instance Show TextLines where #ifdef DEBUG show (TextLines x y) = "TextLines { " ++ showText x ++ ", " ++ show y ++ " }" #else show = show . toText- {-# INLINE show #-} #endif instance IsString TextLines where fromString = fromText . fromString- {-# INLINE fromString #-} -- | Create from 'Text', linear time. fromText :: HasCallStack => Text -> TextLines fromText t = textLines t (U.fromList $ nlIndices t)-{-# INLINABLE fromText #-} nlIndices :: Text -> [Int] #if MIN_VERSION_text(2,0,0)@@ -134,7 +131,6 @@ delta = fromIntegral (unsafeDupablePerformIO $ memchr (TA.aBA arr) (2 * fromIntegral n) (2 * fromIntegral (len + off - n))) `shiftR` 1 #endif-{-# INLINABLE nlIndices #-} foreign import ccall unsafe "_hs_text_lines_memchr0A" memchr :: ByteArray# -> CSize -> CSize -> IO CSsize@@ -142,8 +138,18 @@ -- | Check whether a text is empty, O(1). null :: TextLines -> Bool null = T.null . toText-{-# INLINE null #-} +concat :: [TextLines] -> TextLines+concat ts = case ts' of+ [] -> mempty+ [x] -> x+ _ -> textLines+ (T.concat (map toText ts'))+ (U.concat (snd (mapAccumL f 0 ts')))+ where+ ts' = filter (not . null) ts+ f l (TextLines (Text _ off len) nls) = (l + len, U.map (+ (l - off)) nls)+ instance Semigroup TextLines where TextLines t1@(Text _ off1 len1) s1 <> TextLines t2@(Text _ off2 _) s2 | T.null t1 = textLines t2 s2@@ -152,15 +158,22 @@ (t1 <> t2) (U.map (subtract off1) s1 <> U.map (+ (len1 - off2)) s2) -- This relies on specific implementation of instance Semigroup Text!- {-# INLINABLE (<>) #-}- -- TODO implement sconcat via Builder + sconcat (x :| xs) = concat (x : xs)++ stimes 1 tl = tl+ stimes n (TextLines t@(Text _ off len) nls)+ | n == fromIntegral n' = textLines t' nls'+ | otherwise = error "Data.Text.Lines: stimes argument is too large"+ where+ n' = fromIntegral n+ t' = T.replicate n' t+ nls' = foldMap (\i -> U.map (\j -> j - off + len * i) nls) [0..n'-1]+ instance Monoid TextLines where mempty = textLines mempty mempty- {-# INLINE mempty #-} mappend = (<>)- {-# INLINE mappend #-}- -- TODO implement mconcat via Builder+ mconcat = concat -- | Equivalent to 'Data.List.length' . 'lines', but in O(1). --@@ -202,7 +215,6 @@ arrLen = off + len go i [] = [Text arr i (arrLen - i) | i < arrLen] go i (x : xs) = Text arr i (x - i) : go (x + 1) xs-{-# INLINABLE lines #-} -- | Split at given line, O(1). --@@ -212,7 +224,6 @@ -- splitAtLine :: HasCallStack => Word -> TextLines -> (TextLines, TextLines) splitAtLine k = splitAtPosition (Position k 0)-{-# INLINE splitAtLine #-} ------------------------------------------------------------------------------- -- Unicode code points@@ -228,7 +239,6 @@ -- length :: TextLines -> Word length = intToWord . T.length . toText-{-# INLINE length #-} -- | Represent a position in a text. data Position = Position@@ -237,7 +247,7 @@ } deriving (Eq, Ord, Show) instance NFData Position where- rnf (Position a b) = rnf a `seq` rnf b+ rnf = (`seq` ()) -- | Associativity does not hold when 'posLine' overflows. instance Semigroup Position where@@ -268,7 +278,6 @@ } where nl = if U.null nls then off else U.last nls + 1-{-# INLINABLE lengthAsPosition #-} -- | Span by a predicate, similar to @Data.Text.@'Data.Text.span'. -- Takes linear (by length of the prefix satisfying the predicate) time.@@ -286,7 +295,6 @@ n = binarySearch nls (off' + len') y = textLines (Text arr off (off' + len' - off)) (U.take n nls) z = textLines tz (U.drop n nls)-{-# INLINABLE span #-} -- | Combination of 'splitAtLine' and subsequent 'splitAt'. -- Time is linear in 'posColumn', but does not depend on 'posLine'.@@ -327,7 +335,6 @@ n = binarySearch nls (off' + len') y = textLines (Text arr off (off' + len' - off)) (U.take n nls) z = textLines tz (U.drop n nls)-{-# INLINABLE splitAtPosition #-} -- | Split at given code point, similar to @Data.Text.@'Data.Text.splitAt'. -- Takes linear time.@@ -338,7 +345,6 @@ -- splitAt :: HasCallStack => Word -> TextLines -> (TextLines, TextLines) splitAt = splitAtPosition . Position 0-{-# INLINE splitAt #-} ------------------------------------------------------------------------------- -- Utils@@ -361,15 +367,12 @@ where k = (i + j) `quot` 2 {-# SPECIALIZE binarySearch :: U.Vector Int -> Int -> Int #-}-{-# INLINABLE binarySearch #-} intToWord :: Int -> Word intToWord = fromIntegral-{-# INLINE intToWord #-} wordToInt :: Word -> Int wordToInt = fromIntegral-{-# INLINE wordToInt #-} ------------------------------------------------------------------------------- -- Debug@@ -399,6 +402,5 @@ textLines :: HasCallStack => Text -> U.Vector Int -> TextLines textLines = TextLines-{-# INLINE textLines #-} #endif
src/Data/Text/Rope.hs view
@@ -41,7 +41,7 @@ import Data.Bool (Bool(..), otherwise) import Data.Char (Char) import Data.Eq (Eq, (==))-import Data.Function ((.), ($), on, id)+import Data.Function ((.), ($), on) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Ord (Ord, compare, (<), (<=), Ordering(..))@@ -65,13 +65,23 @@ #endif -- | Rope of 'Text' chunks with logarithmic concatenation.+-- This rope offers an interface, based on code points.+-- Use "Data.Text.Utf16.Rope", if you need UTF-16 code units,+-- or "Data.Text.Utf16.Rope.Mixed", if you need both interfaces. data Rope = Empty- | Node !Rope !TL.TextLines !Rope !Word !Position+ | Node+ { _ropeLeft :: !Rope+ , _ropeMiddle :: !TL.TextLines+ , _ropeRight :: !Rope+ , _ropeCharLen :: !Word+ , _ropeCharLenAsPos :: !Position+ } instance NFData Rope where rnf Empty = ()- rnf (Node a b c d e) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d `seq` rnf e+ -- No need to deepseq strict fields, for which WHNF = NF+ rnf (Node l _ r _ _) = rnf l `seq` rnf r instance Eq Rope where (==) = (==) `on` toLazyText@@ -238,6 +248,9 @@ -- >>> lengthInLines "foo\nbar" -- 2 --+-- If you do not care about ignoring the last newline character,+-- you can use 'posLine' . 'lengthAsPosition' instead, which works in O(1).+-- lengthInLines :: Rope -> Word lengthInLines rp = case lastChar rp of Nothing -> 0@@ -245,8 +258,7 @@ -- | Glue chunks into 'TL.TextLines', linear time. toTextLines :: Rope -> TL.TextLines-toTextLines = foldMapRope id--- TODO make it fast! implement via mconcat for TextLines+toTextLines = mconcat . foldMapRope (:[]) toLazyText :: Rope -> TextLazy.Text toLazyText = foldMapRope (TextLazy.fromStrict . TL.toText)
src/Data/Text/Utf16/Lines.hs view
@@ -10,14 +10,14 @@ {-# LANGUAGE UnliftedFFITypes #-} module Data.Text.Utf16.Lines- ( TextLines- , fromText- , toText- , null+ ( I.TextLines+ , I.fromText+ , I.toText+ , I.null -- * Lines- , lines- , lengthInLines- , splitAtLine+ , I.lines+ , I.lengthInLines+ , I.splitAtLine -- * UTF-16 code units , length , splitAt@@ -28,21 +28,19 @@ import Prelude ((+), (-), seq) import Control.DeepSeq (NFData, rnf)-import Data.Bool (Bool, otherwise)-import Data.Coerce (coerce)+import Data.Bool (otherwise) import Data.Eq (Eq, (==)) import Data.Function ((.), ($)) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Ord (Ord, (<=), (>), (>=)) import Data.Semigroup (Semigroup(..))-import Data.String (IsString) import qualified Data.Text.Array as TA import Data.Text.Internal (Text(..)) import qualified Data.Text.Lines.Internal as I import qualified Data.Vector.Unboxed as U import Data.Word (Word)-import Text.Show (Show, show)+import Text.Show (Show) #if MIN_VERSION_text(2,0,0) import Prelude (fromIntegral)@@ -62,79 +60,6 @@ #define HasCallStack () #endif --- | A wrapper around 'Text' for fast line/column navigation.--- Concatenation takes linear time.------ This is a building block for 'Data.Text.Utf16.Rope.Rope',--- which provides logarithmic concatenation.-newtype TextLines = TextLines I.TextLines- deriving (Eq, Ord, IsString, Semigroup, Monoid, NFData)--instance Show TextLines where- show (TextLines t) = show t---- | Create from 'Text', linear time.-fromText :: HasCallStack => Text -> TextLines-fromText = coerce I.fromText-{-# INLINE fromText #-}---- | Extract 'Text', O(1).-toText :: TextLines -> Text-toText = coerce I.toText-{-# INLINE toText #-}---- | Check whether a text is empty, O(1).-null :: TextLines -> Bool-null = coerce I.null-{-# INLINE null #-}---- | Split into lines by @\\n@, similar to @Data.Text.@'Data.Text.lines'.--- Each line is produced in O(1).------ >>> :set -XOverloadedStrings--- >>> lines ""--- []--- >>> lines "foo"--- ["foo"]--- >>> lines "foo\n"--- ["foo"]--- >>> lines "foo\n\n"--- ["foo",""]--- >>> lines "foo\nbar"--- ["foo","bar"]----lines :: TextLines -> [Text]-lines = coerce I.lines-{-# INLINE lines #-}---- | Equivalent to 'Data.List.length' . 'lines', but in O(1).------ >>> :set -XOverloadedStrings--- >>> lengthInLines ""--- 0--- >>> lengthInLines "foo"--- 1--- >>> lengthInLines "foo\n"--- 1--- >>> lengthInLines "foo\n\n"--- 2--- >>> lengthInLines "foo\nbar"--- 2----lengthInLines :: TextLines -> Word-lengthInLines = coerce I.lengthInLines-{-# INLINE lengthInLines #-}---- | Split at given line, O(1).------ >>> :set -XOverloadedStrings--- >>> map (\l -> splitAtLine l "foo\nbar") [0..3]--- [("","foo\nbar"),("foo\n","bar"),("foo\nbar",""),("foo\nbar","")]----splitAtLine :: HasCallStack => Word -> TextLines -> (TextLines, TextLines)-splitAtLine = coerce I.splitAtLine-{-# INLINE splitAtLine #-}- lengthTextUtf16 :: Text -> Word #if MIN_VERSION_text(2,0,0) lengthTextUtf16 (Text (TA.ByteArray arr) off len) = fromIntegral $ unsafeDupablePerformIO $@@ -145,7 +70,6 @@ #else lengthTextUtf16 (Text _ _ len) = I.intToWord len #endif-{-# INLINABLE lengthTextUtf16 #-} -- | Length in UTF-16 code units. -- Takes linear time.@@ -156,9 +80,8 @@ -- >>> Data.Text.Lines.length "fя𐀀" -- 3 ---length :: TextLines -> Word-length = lengthTextUtf16 . toText-{-# INLINE length #-}+length :: I.TextLines -> Word+length = lengthTextUtf16 . I.toText -- | Represent a position in a text. data Position = Position@@ -167,7 +90,7 @@ } deriving (Eq, Ord, Show) instance NFData Position where- rnf (Position a b) = rnf a `seq` rnf b+ rnf = (`seq` ()) -- | Associativity does not hold when 'posLine' overflows. instance Semigroup Position where@@ -190,15 +113,14 @@ -- Position {posLine = 2, posColumn = 0} -- lengthAsPosition- :: TextLines+ :: I.TextLines -> Position-lengthAsPosition (TextLines (I.TextLines (Text arr off len) nls)) = Position+lengthAsPosition (I.TextLines (Text arr off len) nls) = Position { posLine = I.intToWord $ U.length nls , posColumn = lengthTextUtf16 $ Text arr nl (off + len - nl) } where nl = if U.null nls then off else U.last nls + 1-{-# INLINABLE lengthAsPosition #-} splitTextAtUtf16Index :: Word -> Text -> Maybe (Text, Text) splitTextAtUtf16Index k t@(Text arr off len)@@ -222,9 +144,8 @@ k' = I.wordToInt k c = TA.unsafeIndex arr (off + k') #endif-{-# INLINABLE splitTextAtUtf16Index #-} --- | Combination of 'splitAtLine' and subsequent 'splitAt'.+-- | Combination of 'I.splitAtLine' and subsequent 'splitAt'. -- If requested number of code units splits a code point in half, return 'Nothing'. -- Time is linear in 'posColumn', but does not depend on 'posLine'. --@@ -245,14 +166,14 @@ splitAtPosition :: HasCallStack => Position- -> TextLines- -> Maybe (TextLines, TextLines)-splitAtPosition (Position line column) (TextLines (I.TextLines (Text arr off len) nls)) =+ -> I.TextLines+ -> Maybe (I.TextLines, I.TextLines)+splitAtPosition (Position line column) (I.TextLines (Text arr off len) nls) = case splitTextAtUtf16Index column tx of Nothing -> Nothing Just (Text _ off' len', tz) -> let n = I.binarySearch nls (off' + len') in Just- ( TextLines $ I.textLines (Text arr off (off' + len' - off)) (U.take n nls)- , TextLines $ I.textLines tz (U.drop n nls))+ ( I.textLines (Text arr off (off' + len' - off)) (U.take n nls)+ , I.textLines tz (U.drop n nls)) where arrLen = off + len nl@@ -260,7 +181,6 @@ | line > I.intToWord (U.length nls) = arrLen | otherwise = nls U.! (I.wordToInt line - 1) + 1 tx = Text arr nl (arrLen - nl)-{-# INLINABLE splitAtPosition #-} -- | Split at given UTF-16 code unit. -- If requested number of code units splits a code point in half, return 'Nothing'.@@ -270,6 +190,5 @@ -- >>> map (\c -> splitAt c "fя𐀀") [0..4] -- [Just ("","fя𐀀"),Just ("f","я𐀀"),Just ("fя","𐀀"),Nothing,Just ("fя𐀀","")] ---splitAt :: HasCallStack => Word -> TextLines -> Maybe (TextLines, TextLines)+splitAt :: HasCallStack => Word -> I.TextLines -> Maybe (I.TextLines, I.TextLines) splitAt = splitAtPosition . Position 0-{-# INLINE splitAt #-}
src/Data/Text/Utf16/Rope.hs view
@@ -41,7 +41,7 @@ import Data.Bool (Bool(..), otherwise) import Data.Char (Char) import Data.Eq (Eq, (==))-import Data.Function ((.), ($), on, id)+import Data.Function ((.), ($), on) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..)) import Data.Ord (Ord, compare, (<), (<=), Ordering(..))@@ -65,13 +65,23 @@ #endif -- | Rope of 'Text' chunks with logarithmic concatenation.+-- This rope offers an interface, based on UTF-16 code units.+-- Use "Data.Text.Rope", if you need code points,+-- or "Data.Text.Utf16.Rope.Mixed", if you need both interfaces. data Rope = Empty- | Node !Rope !TL.TextLines !Rope !Word !Position+ | Node+ { _ropeLeft :: !Rope+ , _ropeMiddle :: !TL.TextLines+ , _ropeRight :: !Rope+ , _ropeUtf16Len :: !Word+ , _ropeUtf16LenAsPos :: !Position+ } instance NFData Rope where rnf Empty = ()- rnf (Node a b c d e) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d `seq` rnf e+ -- No need to deepseq strict fields, for which WHNF = NF+ rnf (Node l _ r _ _) = rnf l `seq` rnf r instance Eq Rope where (==) = (==) `on` toLazyText@@ -237,6 +247,9 @@ -- >>> lengthInLines "foo\nbar" -- 2 --+-- If you do not care about ignoring the last newline character,+-- you can use 'posLine' . 'lengthAsPosition' instead, which works in O(1).+-- lengthInLines :: Rope -> Word lengthInLines rp = case lastChar rp of Nothing -> 0@@ -244,8 +257,7 @@ -- | Glue chunks into 'TL.TextLines', linear time. toTextLines :: Rope -> TL.TextLines-toTextLines = foldMapRope id--- TODO make it fast! implement via mconcat for TextLines+toTextLines = mconcat . foldMapRope (:[]) toLazyText :: Rope -> TextLazy.Text toLazyText = foldMapRope (TextLazy.fromStrict . TL.toText)
+ src/Data/Text/Utf16/Rope/Mixed.hs view
@@ -0,0 +1,491 @@+-- |+-- Copyright: (c) 2021-2022 Andrew Lelechenko+-- Licence: BSD3+-- Maintainer: Andrew Lelechenko <andrew.lelechenko@gmail.com>++{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}++#ifdef DEBUG+#define DEFRAGMENTATION_THRESHOLD 4+#else+#define DEFRAGMENTATION_THRESHOLD 4096+#endif++module Data.Text.Utf16.Rope.Mixed+ ( Rope+ , fromText+ , fromTextLines+ , toText+ , toTextLines+ , null+ -- * Lines+ , lines+ , lengthInLines+ , splitAtLine+ -- * Code points+ , charLength+ , charSplitAt+ , charLengthAsPosition+ , charSplitAtPosition+ -- * UTF-16 code units+ , utf16Length+ , utf16SplitAt+ , utf16LengthAsPosition+ , utf16SplitAtPosition+ ) where++import Prelude ((-), (+), seq)+import Control.DeepSeq (NFData, rnf)+import Data.Bool (Bool(..), otherwise)+import Data.Char (Char)+import Data.Eq (Eq, (==))+import Data.Function ((.), ($), on)+import Data.Maybe (Maybe(..))+import Data.Monoid (Monoid(..))+import Data.Ord (Ord, compare, (<), (<=), Ordering(..))+import Data.Semigroup (Semigroup(..))+import Data.String (IsString(..))+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TextLazy+import qualified Data.Text.Lazy.Builder as Builder+import Data.Text.Lines.Internal (TextLines)+import qualified Data.Text.Lines.Internal as TL (null, fromText, toText, lines, splitAtLine)+import qualified Data.Text.Lines as Char+import qualified Data.Text.Utf16.Lines as Utf16+import Data.Word (Word)+import Text.Show (Show)++#ifdef DEBUG+import Prelude (error)+import GHC.Stack (HasCallStack)+#else+#define HasCallStack ()+import Text.Show (show)+#endif++-- | Rope of 'Text' chunks with logarithmic concatenation.+-- This rope offers two interfaces: one based on code points+-- and another one based on UTF-16 code units. This comes with a price+-- of double bookkeeping and is less performant than "Data.Text.Rope"+-- or "Data.Text.Utf16.Rope".+data Rope+ = Empty+ | Node+ { _ropeLeft :: !Rope+ , _ropeMiddle :: !TextLines+ , _ropeRight :: !Rope+ , _ropeCharLen :: !Word+ , _ropeCharLenAsPos :: !Char.Position+ , _ropeUtf16Len :: !Word+ , _ropeUtf16LenAsPos :: !Utf16.Position+ }++instance NFData Rope where+ rnf Empty = ()+ -- No need to deepseq strict fields, for which WHNF = NF+ rnf (Node l _ r _ _ _ _) = rnf l `seq` rnf r++instance Eq Rope where+ (==) = (==) `on` toLazyText++instance Ord Rope where+ compare = compare `on` toLazyText++#ifdef DEBUG+deriving instance Show Rope+#else+instance Show Rope where+ show = show . toLazyText+#endif++instance IsString Rope where+ fromString = fromTextLines . fromString++-- | Check whether a rope is empty, O(1).+null :: Rope -> Bool+null = \case+ Empty -> True+ Node{} -> False++-- | Length in code points, similar to @Data.Text.@'Data.Text.length', O(1).+--+-- >>> :set -XOverloadedStrings+-- >>> charLength "fя𐀀"+-- 3+--+charLength :: Rope -> Word+charLength = \case+ Empty -> 0+ Node _ _ _ w _ _ _ -> w++-- | Length in UTF-16 code units, O(1).+--+-- >>> :set -XOverloadedStrings+-- >>> utf16Length "fя𐀀"+-- 4+--+utf16Length :: Rope -> Word+utf16Length = \case+ Empty -> 0+ Node _ _ _ _ _ w _ -> w++-- | Measure text length as an amount of lines and columns, O(1).+--+-- >>> :set -XOverloadedStrings+-- >>> charLengthAsPosition "f𐀀"+-- Position {posLine = 0, posColumn = 2}+-- >>> charLengthAsPosition "f\n𐀀"+-- Position {posLine = 1, posColumn = 1}+-- >>> charLengthAsPosition "f\n𐀀\n"+-- Position {posLine = 2, posColumn = 0}+--+charLengthAsPosition :: Rope -> Char.Position+charLengthAsPosition = \case+ Empty -> mempty+ Node _ _ _ _ p _ _ -> p++-- | Measure text length as an amount of lines and columns, O(1).+--+-- >>> :set -XOverloadedStrings+-- >>> utf16LengthAsPosition "f𐀀"+-- Position {posLine = 0, posColumn = 3}+-- >>> utf16LengthAsPosition "f\n𐀀"+-- Position {posLine = 1, posColumn = 2}+-- >>> utf16LengthAsPosition "f\n𐀀\n"+-- Position {posLine = 2, posColumn = 0}+--+utf16LengthAsPosition :: Rope -> Utf16.Position+utf16LengthAsPosition = \case+ Empty -> mempty+ Node _ _ _ _ _ _ p -> p++instance Semigroup Rope where+ Empty <> t = t+ t <> Empty = t+ Node l1 c1 r1 u1 p1 u1' p1' <> Node l2 c2 r2 u2 p2 u2' p2' = defragment+ l1+ c1+ (Node (r1 <> l2) c2 r2 (charLength r1 + u2) (charLengthAsPosition r1 <> p2) (utf16Length r1 + u2') (utf16LengthAsPosition r1 <> p2'))+ (u1 + u2)+ (p1 <> p2)+ (u1' + u2')+ (p1' <> p2')++instance Monoid Rope where+ mempty = Empty+ mappend = (<>)++defragment :: HasCallStack => Rope -> TextLines -> Rope -> Word -> Char.Position -> Word -> Utf16.Position -> Rope+defragment !l !c !r !u !p !u' !p'+#ifdef DEBUG+ | TL.null c = error "Data.Text.Lines: violated internal invariant"+#endif+ | u < DEFRAGMENTATION_THRESHOLD+ = Node Empty (toTextLines rp) Empty u p u' p'+ | otherwise+ = rp+ where+ rp = Node l c r u p u' p'++-- | Create from 'TextLines', linear time.+fromTextLines :: TextLines -> Rope+fromTextLines tl+ | TL.null tl = Empty+ | otherwise = Node Empty tl Empty (Char.length tl) (Char.lengthAsPosition tl) (Utf16.length tl) (Utf16.lengthAsPosition tl)++node :: HasCallStack => Rope -> TextLines -> Rope -> Rope+node l c r = defragment l c r totalLength totalLengthAsPosition totalLength' totalLengthAsPosition'+ where+ totalLength = charLength l + Char.length c + charLength r+ totalLengthAsPosition = charLengthAsPosition l <> Char.lengthAsPosition c <> charLengthAsPosition r+ totalLength' = utf16Length l + Utf16.length c + utf16Length r+ totalLengthAsPosition' = utf16LengthAsPosition l <> Utf16.lengthAsPosition c <> utf16LengthAsPosition r++(|>) :: Rope -> TextLines -> Rope+tr |> tl+ | TL.null tl = tr+ | otherwise = node tr tl Empty++(<|) :: TextLines -> Rope -> Rope+tl <| tr+ | TL.null tl = tr+ | otherwise = node Empty tl tr++-- | Create from 'Text', linear time.+fromText :: Text -> Rope+fromText = fromTextLines . TL.fromText++foldMapRope :: Monoid a => (TextLines -> a) -> Rope -> a+foldMapRope f = go+ where+ go = \case+ Empty -> mempty+ Node l c r _ _ _ _ -> go l `mappend` f c `mappend` go r++data Lines = Lines ![Text] !Bool++instance Semigroup Lines where+ Lines [] _ <> ls = ls+ ls <> Lines [] _ = ls+ Lines xs x <> Lines ys y = Lines (if x then xs <> ys else go xs ys) y+ where+ go [] vs = vs+ go [u] (v : vs) = (u <> v) : vs+ go (u : us) vs = u : go us vs++instance Monoid Lines where+ mempty = Lines [] False+ mappend = (<>)++-- | Split into lines by @\\n@, similar to @Data.Text.@'Data.Text.lines'.+-- Each line is produced in O(1).+--+-- >>> :set -XOverloadedStrings+-- >>> lines ""+-- []+-- >>> lines "foo"+-- ["foo"]+-- >>> lines "foo\n"+-- ["foo"]+-- >>> lines "foo\n\n"+-- ["foo",""]+-- >>> lines "foo\nbar"+-- ["foo","bar"]+--+lines :: Rope -> [Text]+lines = (\(Lines ls _) -> ls) . foldMapRope+ -- This assumes that there are no empty chunks:+ (\tl -> Lines (TL.lines tl) (T.last (TL.toText tl) == '\n'))++lastChar :: Rope -> Maybe Char+lastChar = \case+ Empty -> Nothing+ -- This assumes that there are no empty chunks:+ Node _ c Empty _ _ _ _ -> Just $ T.last $ TL.toText c+ Node _ _ r _ _ _ _ -> lastChar r++-- | Equivalent to 'Data.List.length' . 'lines', but in logarithmic time.+--+-- >>> :set -XOverloadedStrings+-- >>> lengthInLines ""+-- 0+-- >>> lengthInLines "foo"+-- 1+-- >>> lengthInLines "foo\n"+-- 1+-- >>> lengthInLines "foo\n\n"+-- 2+-- >>> lengthInLines "foo\nbar"+-- 2+--+-- If you do not care about ignoring the last newline character,+-- you can use 'Char.posLine' . 'charLengthAsPosition' instead, which works in O(1).+--+lengthInLines :: Rope -> Word+lengthInLines rp = case lastChar rp of+ Nothing -> 0+ Just ch -> Char.posLine (charLengthAsPosition rp) + (if ch == '\n' then 0 else 1)++-- | Glue chunks into 'TextLines', linear time.+toTextLines :: Rope -> TextLines+toTextLines = mconcat . foldMapRope (:[])++toLazyText :: Rope -> TextLazy.Text+toLazyText = foldMapRope (TextLazy.fromStrict . TL.toText)++-- | Glue chunks into 'Text', linear time.+toText :: Rope -> Text+toText = TextLazy.toStrict . Builder.toLazyText . foldMapRope (Builder.fromText . TL.toText)++-- | Split at given code point, similar to @Data.Text.@'Data.Text.splitAt'.+-- Takes linear time.+--+-- >>> :set -XOverloadedStrings+-- >>> map (\c -> charSplitAt c "fя𐀀") [0..4]+-- [("","fя𐀀"),("f","я𐀀"),("fя","𐀀"),("fя𐀀",""),("fя𐀀","")]+--+charSplitAt :: HasCallStack => Word -> Rope -> (Rope, Rope)+charSplitAt !len = \case+ Empty -> (Empty, Empty)+ Node l c r _ _ _ _+ | len <= ll -> case charSplitAt len l of+ (before, after) -> (before, node after c r)+ | len <= llc -> case Char.splitAt (len - ll) c of+ (before, after) -> (l |> before, after <| r)+ | otherwise -> case charSplitAt (len - llc) r of+ (before, after) -> (node l c before, after)+ where+ ll = charLength l+ llc = ll + Char.length c++-- | Split at given UTF-16 code unit.+-- If requested number of code units splits a code point in half, return 'Nothing'.+-- Takes linear time.+--+-- >>> :set -XOverloadedStrings+-- >>> map (\c -> utf16SplitAt c "fя𐀀") [0..4]+-- [Just ("","fя𐀀"),Just ("f","я𐀀"),Just ("fя","𐀀"),Nothing,Just ("fя𐀀","")]+--+utf16SplitAt :: HasCallStack => Word -> Rope -> Maybe (Rope, Rope)+utf16SplitAt !len = \case+ Empty -> Just (Empty, Empty)+ Node l c r _ _ _ _+ | len <= ll -> case utf16SplitAt len l of+ Nothing -> Nothing+ Just (before, after) -> Just (before, node after c r)+ | len <= llc -> case Utf16.splitAt (len - ll) c of+ Nothing -> Nothing+ Just (before, after) -> Just (l |> before, after <| r)+ | otherwise -> case utf16SplitAt (len - llc) r of+ Nothing -> Nothing+ Just (before, after) -> Just (node l c before, after)+ where+ ll = utf16Length l+ llc = ll + Utf16.length c++-- | Split at given line, logarithmic time.+--+-- >>> :set -XOverloadedStrings+-- >>> map (\l -> splitAtLine l "foo\nbar") [0..3]+-- [("","foo\nbar"),("foo\n","bar"),("foo\nbar",""),("foo\nbar","")]+--+splitAtLine :: HasCallStack => Word -> Rope -> (Rope, Rope)+splitAtLine !len = \case+ Empty -> (Empty, Empty)+ Node l c r _ _ _ _+ | len <= ll -> case splitAtLine len l of+ (before, after) -> (before, node after c r)+ | len <= llc -> case TL.splitAtLine (len - ll) c of+ (before, after) -> (l |> before, after <| r)+ | otherwise -> case splitAtLine (len - llc) r of+ (before, after) -> (node l c before, after)+ where+ -- posLine is the same both in Char.lengthAsPosition and Utf16.lengthAsPosition+ ll = Char.posLine (charLengthAsPosition l)+ llc = ll + Char.posLine (Char.lengthAsPosition c)++charSubOnRope :: Rope -> Char.Position -> Char.Position -> Char.Position+charSubOnRope rp (Char.Position xl xc) (Char.Position yl yc) = case xl `compare` yl of+ GT -> Char.Position (xl - yl) xc+ EQ -> Char.Position 0 (xc - yc)+ LT -> Char.Position 0 (xc - charLength rp')+ where+ (_, rp') = splitAtLine xl rp++utf16SubOnRope :: Rope -> Utf16.Position -> Utf16.Position -> Utf16.Position+utf16SubOnRope rp (Utf16.Position xl xc) (Utf16.Position yl yc) = case xl `compare` yl of+ GT -> Utf16.Position (xl - yl) xc+ EQ -> Utf16.Position 0 (xc - yc)+ LT -> Utf16.Position 0 (xc - utf16Length rp')+ where+ (_, rp') = splitAtLine xl rp++charSubOnLines :: Char.TextLines -> Char.Position -> Char.Position -> Char.Position+charSubOnLines tl (Char.Position xl xc) (Char.Position yl yc) = case xl `compare` yl of+ GT -> Char.Position (xl - yl) xc+ EQ -> Char.Position 0 (xc - yc)+ LT -> Char.Position 0 (xc - Char.length tl')+ where+ (_, tl') = Char.splitAtLine xl tl++utf16SubOnLines :: Utf16.TextLines -> Utf16.Position -> Utf16.Position -> Utf16.Position+utf16SubOnLines tl (Utf16.Position xl xc) (Utf16.Position yl yc) = case xl `compare` yl of+ GT -> Utf16.Position (xl - yl) xc+ EQ -> Utf16.Position 0 (xc - yc)+ LT -> Utf16.Position 0 (xc - Utf16.length tl')+ where+ (_, tl') = Utf16.splitAtLine xl tl++-- | Combination of 'splitAtLine' and subsequent 'charSplitAt'.+-- Time is linear in 'Char.posColumn' and logarithmic in 'Char.posLine'.+--+-- >>> :set -XOverloadedStrings+-- >>> charSplitAtPosition (Position 1 0) "f\n𐀀я"+-- ("f\n","𐀀я")+-- >>> charSplitAtPosition (Position 1 1) "f\n𐀀я"+-- ("f\n𐀀","я")+-- >>> charSplitAtPosition (Position 1 2) "f\n𐀀я"+-- ("f\n𐀀я","")+-- >>> charSplitAtPosition (Position 0 2) "f\n𐀀я"+-- ("f\n","𐀀я")+-- >>> charSplitAtPosition (Position 0 3) "f\n𐀀я"+-- ("f\n𐀀","я")+-- >>> charSplitAtPosition (Position 0 4) "f\n𐀀я"+-- ("f\n𐀀я","")+--+charSplitAtPosition :: HasCallStack => Char.Position -> Rope -> (Rope, Rope)+charSplitAtPosition (Char.Position 0 0) = (mempty,)+charSplitAtPosition !len = \case+ Empty -> (Empty, Empty)+ Node l c r _ _ _ _+ | len <= ll -> case charSplitAtPosition len l of+ (before, after)+ | null after -> case charSplitAtPosition len' (c <| r) of+ (r', r'') -> (l <> r', r'')+ | otherwise -> (before, node after c r)+ | len <= llc -> case Char.splitAtPosition len' c of+ (before, after)+ | TL.null after -> case charSplitAtPosition len'' r of+ (r', r'') -> ((l |> c) <> r', r'')+ | otherwise -> (l |> before, after <| r)+ | otherwise -> case charSplitAtPosition len'' r of+ (before, after) -> (node l c before, after)+ where+ ll = charLengthAsPosition l+ lc = Char.lengthAsPosition c+ llc = ll <> lc+ len' = charSubOnRope l len ll+ len'' = charSubOnLines c len' lc++-- | Combination of 'splitAtLine' and subsequent 'utf16SplitAt'.+-- Time is linear in 'Utf16.posColumn' and logarithmic in 'Utf16.posLine'.+--+-- >>> :set -XOverloadedStrings+-- >>> utf16SplitAtPosition (Position 1 0) "f\n𐀀я"+-- Just ("f\n","𐀀я")+-- >>> utf16SplitAtPosition (Position 1 1) "f\n𐀀я"+-- Nothing+-- >>> utf16SplitAtPosition (Position 1 2) "f\n𐀀я"+-- Just ("f\n𐀀","я")+-- >>> utf16SplitAtPosition (Position 0 2) "f\n𐀀я"+-- Just ("f\n","𐀀я")+-- >>> utf16SplitAtPosition (Position 0 3) "f\n𐀀я"+-- Nothing+-- >>> utf16SplitAtPosition (Position 0 4) "f\n𐀀я"+-- Just ("f\n𐀀","я")+--+utf16SplitAtPosition :: HasCallStack => Utf16.Position -> Rope -> Maybe (Rope, Rope)+utf16SplitAtPosition (Utf16.Position 0 0) = Just . (mempty,)+utf16SplitAtPosition !len = \case+ Empty -> Just (Empty, Empty)+ Node l c r _ _ _ _+ | len <= ll -> case utf16SplitAtPosition len l of+ Nothing -> Nothing+ Just (before, after)+ | null after -> case utf16SplitAtPosition len' (c <| r) of+ Nothing -> Nothing+ Just (r', r'') -> Just (l <> r', r'')+ | otherwise -> Just (before, node after c r)+ | len <= llc -> case Utf16.splitAtPosition len' c of+ Nothing -> Nothing+ Just (before, after)+ | Utf16.null after -> case utf16SplitAtPosition len'' r of+ Nothing -> Nothing+ Just (r', r'') -> Just ((l |> c) <> r', r'')+ | otherwise -> Just (l |> before, after <| r)+ | otherwise -> case utf16SplitAtPosition len'' r of+ Nothing -> Nothing+ Just (before, after) -> Just (node l c before, after)+ where+ ll = utf16LengthAsPosition l+ lc = Utf16.lengthAsPosition c+ llc = ll <> lc+ len' = utf16SubOnRope l len ll+ len'' = utf16SubOnLines c len' lc
test/CharLines.hs view
@@ -11,14 +11,15 @@ import Data.Bool ((||), not, (&&)) import Data.Function (($)) import qualified Data.List as L-import Data.Monoid (mempty)+import Data.Monoid (mempty, mconcat) import Data.Ord (min, (>=), (<))-import Data.Semigroup ((<>))+import Data.Semigroup ((<>), stimes, stimesMonoid) import qualified Data.Text as T import Data.Text.Lines import Data.Tuple (snd)+import Data.Word (Word) import Test.Tasty (testGroup, TestTree)-import Test.Tasty.QuickCheck (testProperty, (===), applyFun, (.||.), (.&&.), (==>))+import Test.Tasty.QuickCheck (Small(..), testProperty, (===), applyFun, (.||.), (.&&.), (==>)) import Utils () @@ -36,6 +37,12 @@ \x -> mempty <> x === (x :: TextLines) , testProperty "TextLines <> mempty" $ \x -> x <> mempty === (x :: TextLines)++ , testProperty "mconcat" $+ \xs -> L.foldr (<>) mempty xs === mconcat (xs :: [TextLines])++ , testProperty "stimes" $+ \(Small n) xs -> stimesMonoid n xs === stimes (n :: Word) (xs :: TextLines) , testProperty "Position associativity" $ \x y z -> posLine x < maxBound - posLine y && posLine y < maxBound - posLine z ==>
test/Main.hs view
@@ -9,6 +9,7 @@ import qualified CharLines import qualified CharRope+import qualified MixedRope import qualified Utf16Lines import qualified Utf16Rope @@ -23,4 +24,5 @@ , CharRope.testSuite , Utf16Lines.testSuite , Utf16Rope.testSuite+ , MixedRope.testSuite ]
+ test/MixedRope.hs view
@@ -0,0 +1,86 @@+-- |+-- Copyright: (c) 2021-2022 Andrew Lelechenko+-- Licence: BSD3+-- Maintainer: Andrew Lelechenko <andrew.lelechenko@gmail.com>++module MixedRope+ ( testSuite+ ) where++import Prelude ((+), (-))+import Data.Bool (Bool(..), (&&))+import Data.Function (($))+import Data.Maybe (Maybe(..), isJust)+import Data.Semigroup ((<>))+import qualified Data.Text.Lines as Char+import qualified Data.Text.Utf16.Lines as Utf16+import qualified Data.Text.Utf16.Rope.Mixed as Mixed+import Test.Tasty (testGroup, TestTree)+import Test.Tasty.QuickCheck (testProperty, (===), property, (.&&.), counterexample)++import Utils ()++testSuite :: TestTree+testSuite = testGroup "Utf16 Mixed"+ [ testProperty "char null" $+ \x -> Mixed.null x === Char.null (Mixed.toTextLines x)++ , testProperty "charLength" $+ \x -> Mixed.charLength x === Char.length (Mixed.toTextLines x)+ , testProperty "utf16Length" $+ \x -> Mixed.utf16Length x === Utf16.length (Mixed.toTextLines x)++ , testProperty "lengthInLines" $+ \x -> Mixed.lengthInLines x === Char.lengthInLines (Mixed.toTextLines x)++ , testProperty "lines" $+ \x -> Mixed.lines x === Char.lines (Mixed.toTextLines x)++ , testProperty "splitAtLine" $+ \i x -> let (y, z) = Mixed.splitAtLine i x in+ (Mixed.toTextLines y, Mixed.toTextLines z) === Char.splitAtLine i (Mixed.toTextLines x)++ , testProperty "charSplitAt 1" $+ \i x -> case Mixed.charSplitAt i x of+ (y, z) -> x === y <> z+ , testProperty "charSplitAt 2" $+ \i x -> case (Mixed.charSplitAt i x, Char.splitAt i (Char.fromText $ Mixed.toText x)) of+ ((y, z), (y', z')) -> Char.fromText (Mixed.toText y) === y' .&&. Char.fromText (Mixed.toText z) === z'++ , testProperty "utf16SplitAt 1" $+ \i x -> case Mixed.utf16SplitAt i x of+ Nothing -> property True+ Just (y, z) -> x === y <> z+ , testProperty "utf16SplitAt 2" $+ \i x -> case (Mixed.utf16SplitAt i x, Utf16.splitAt i (Utf16.fromText $ Mixed.toText x)) of+ (Nothing, Nothing) -> property True+ (Nothing, Just{}) -> counterexample "can split TextLines, but not Mixed" False+ (Just{}, Nothing) -> counterexample "can split Mixed, but not TextLines" False+ (Just (y, z), Just (y', z')) -> Utf16.fromText (Mixed.toText y) === y' .&&. Utf16.fromText (Mixed.toText z) === z'+ , testProperty "splitAt 3" $+ \i x -> case Mixed.utf16SplitAt i x of+ Just{} -> True+ Nothing -> isJust (Mixed.utf16SplitAt (i - 1) x) && isJust (Mixed.utf16SplitAt (i + 1) x)++ , testProperty "charSplitAtPosition 1" $+ \i x -> case Mixed.charSplitAtPosition i x of+ (y, z) -> x === y <> z+ , testProperty "charSplitAtPosition 2" $+ \i x -> case (Mixed.charSplitAtPosition i x, Char.splitAtPosition i (Char.fromText $ Mixed.toText x)) of+ ((y, z), (y', z')) -> Char.fromText (Mixed.toText y) === y' .&&. Char.fromText (Mixed.toText z) === z'++ , testProperty "utf16SplitAtPosition 1" $+ \i x -> case Mixed.utf16SplitAtPosition i x of+ Nothing -> property True+ Just (y, z) -> x === y <> z+ , testProperty "utf16SplitAtPosition 2" $+ \i x -> case (Mixed.utf16SplitAtPosition i x, Utf16.splitAtPosition i (Utf16.fromText $ Mixed.toText x)) of+ (Nothing, Nothing) -> property True+ (Nothing, Just{}) -> counterexample "can split TextLines, but not Mixed" False+ (Just{}, Nothing) -> counterexample "can split Mixed, but not TextLines" False+ (Just (y, z), Just (y', z')) -> Utf16.fromText (Mixed.toText y) === y' .&&. Utf16.fromText (Mixed.toText z) === z'+ , testProperty "utf16SplitAtPosition 3" $+ \i x -> case Mixed.utf16SplitAtPosition i x of+ Just{} -> True+ Nothing -> isJust (Mixed.utf16SplitAtPosition (i <> Utf16.Position 0 1) x)+ ]
test/Utf16Lines.hs view
@@ -12,14 +12,15 @@ import Data.Function (($)) import qualified Data.List as L import Data.Maybe (Maybe(..), isJust)-import Data.Monoid (mempty)+import Data.Monoid (mempty, mconcat) import Data.Ord (min, (>=), (<))-import Data.Semigroup ((<>))+import Data.Semigroup ((<>), stimes, stimesMonoid) import qualified Data.Text as T import Data.Text.Utf16.Lines import Data.Tuple (snd)+import Data.Word (Word) import Test.Tasty (testGroup, TestTree)-import Test.Tasty.QuickCheck (testProperty, (===), property, (.||.), (.&&.), (==>))+import Test.Tasty.QuickCheck (Small(..), testProperty, (===), property, (.||.), (.&&.), (==>)) import Utils @@ -37,6 +38,12 @@ \x -> mempty <> x === (x :: TextLines) , testProperty "TextLines <> mempty" $ \x -> x <> mempty === (x :: TextLines)++ , testProperty "mconcat" $+ \xs -> L.foldr (<>) mempty xs === mconcat (xs :: [TextLines])++ , testProperty "stimes" $+ \(Small n) xs -> stimesMonoid n xs === stimes (n :: Word) (xs :: TextLines) , testProperty "Position associativity" $ \x y z -> posLine x < maxBound - posLine y && posLine y < maxBound - posLine z ==>
test/Utils.hs view
@@ -21,6 +21,7 @@ import qualified Data.Text.Rope as CharRope import qualified Data.Text.Utf16.Lines as Utf16 import qualified Data.Text.Utf16.Rope as Utf16Rope+import qualified Data.Text.Utf16.Rope.Mixed as MixedRope import Data.Word (Word) import Test.Tasty.QuickCheck (Gen, Arbitrary (arbitrary), arbitrary, shrink, frequency, arbitraryASCIIChar, arbitraryUnicodeChar, listOf, oneof) import Data.Monoid (mconcat, mappend)@@ -56,10 +57,6 @@ arbitrary = Char.fromText <$> arbitrary shrink = L.map Char.fromText . shrink . Char.toText -instance Arbitrary Utf16.TextLines where- arbitrary = Utf16.fromText <$> arbitrary- shrink = L.map Utf16.fromText . shrink . Utf16.toText- instance Arbitrary Char.Position where arbitrary = oneof [ Char.Position <$> arbitrary <*> arbitrary@@ -99,3 +96,13 @@ | Utf16Rope.null rp = [] | otherwise = L.concatMap (\i -> maybe [] (\(x, y) -> [x, y]) (Utf16Rope.splitAt i rp)) [1..Utf16Rope.length rp - 1]++instance Arbitrary MixedRope.Rope where+ arbitrary = frequency+ [ (9, mconcat . L.map MixedRope.fromText <$> arbitrary)+ , (1, mappend <$> arbitrary <*> arbitrary)+ ]+ shrink rp+ | MixedRope.null rp = []+ | otherwise = L.concatMap (\i -> (\(x, y) -> [x, y]) (MixedRope.charSplitAt i rp))+ [1..MixedRope.charLength rp - 1]
text-rope.cabal view
@@ -1,5 +1,5 @@ name: text-rope-version: 0.1+version: 0.2 cabal-version: >=1.10 build-type: Simple license: BSD3@@ -18,7 +18,7 @@ data-files: bench/bench.txt -tested-with: GHC == 9.2.1, GHC==9.0.2, GHC==8.10.7, GHC==8.8.4, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2+tested-with: GHC == 9.2.2, GHC==9.0.2, GHC==8.10.7, GHC==8.8.4, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2 source-repository head type: git@@ -35,6 +35,7 @@ Data.Text.Rope Data.Text.Utf16.Lines Data.Text.Utf16.Rope+ Data.Text.Utf16.Rope.Mixed other-modules: Data.Text.Lines.Internal build-depends:@@ -44,9 +45,11 @@ vector >=0.11 default-language: Haskell2010 hs-source-dirs: src- ghc-options: -O2 -Wall -Wcompat+ ghc-options: -O2 -Wall -Wcompat -fexpose-all-unfoldings if flag(debug) cpp-options: -DDEBUG+ if impl(ghc >= 9.2.2)+ ghc-options: -fcheck-prim-bounds c-sources: cbits/utils.c @@ -56,6 +59,7 @@ other-modules: CharLines CharRope+ MixedRope Utf16Lines Utf16Rope Utils