packages feed

yi-rope 0.9 → 0.10

raw patch · 3 files changed

+70/−121 lines, 3 filesdep −charsetdetect-aedep −data-defaultdep −text-icudep ~bytestringdep ~fingertreedep ~text

Dependencies removed: charsetdetect-ae, data-default, text-icu

Dependency ranges changed: bytestring, fingertree, text

Files

src/Yi/Rope.hs view
@@ -1,9 +1,11 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-}-{-# OPTIONS_HADDOCK show-extensions #-}+{-# language BangPatterns #-}+{-# language DeriveDataTypeable #-}+{-# language LambdaCase #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings #-}+{-# language ScopedTypeVariables #-}+{-# language ViewPatterns #-}+{-# options_haddock show-extensions #-}  -- | -- Module      :  Yi.Rope@@ -52,8 +54,7 @@    Yi.Rope.replicate, Yi.Rope.replicateChar,     -- * IO-   ConverterName, unCn, Yi.Rope.readFile, Yi.Rope.writeFile,-   Yi.Rope.writeFileUsingText, Yi.Rope.writeFileWithConverter,+   Yi.Rope.readFile, Yi.Rope.writeFile,     -- * Escape latches to underlying content. Note that these are safe    -- to use but it does not mean they should.@@ -61,25 +62,22 @@    ) where --import           Codec.Text.Detect (detectEncodingName) import           Control.DeepSeq+import           Control.Exception (try) import           Data.Binary-import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import           Data.Char (isSpace)-import           Data.Default import qualified Data.FingerTree as T import           Data.FingerTree hiding (null, empty, reverse, split)-import           Data.Function (fix) import qualified Data.List as L (foldl') import           Data.Maybe import           Data.Monoid import           Data.String (IsString(..)) import qualified Data.Text as TX-import qualified Data.Text.Encoding as TXE-import           Data.Text.ICU.Convert-import qualified Data.Text.IO as TF (writeFile)+import qualified Data.Text.Encoding.Error as TXEE+import qualified Data.Text.Lazy as TXL+import qualified Data.Text.Lazy.Encoding as TXLE+import qualified Data.Text.IO as TXIO (writeFile) import           Data.Typeable import           Prelude hiding (drop) @@ -124,7 +122,7 @@ instance Measured Size YiChunk where   measure (Chunk l t) = Indices l (countNl t) --- | A 'YiString' is a 'FingerTree' with cached column and line counts+-- | A 'YiString' is a 'FingerTree' with cached char and line counts -- over chunks of 'TX.Text'. newtype YiString = YiString { fromRope :: FingerTree Size YiChunk }                  deriving (Show, Typeable)@@ -163,9 +161,6 @@ instance Ord YiString where   compare x y = toText x `compare` toText y -instance Default YiString where-  def = mempty- (-|) :: YiChunk -> FingerTree Size YiChunk -> FingerTree Size YiChunk b -| t | chunkSize b == 0 = t        | otherwise        = b <| t@@ -240,6 +235,9 @@ fromText :: TX.Text -> YiString fromText = fromText' defaultChunkSize +fromLazyText :: TXL.Text -> YiString+fromLazyText = YiString . T.fromList . fmap (mkChunk TX.length) . TXL.toChunks+ -- | Consider whether you really need to use this! toText :: YiString -> TX.Text toText = TX.concat . go . fromRope@@ -365,12 +363,12 @@  -- | Takes the first n given characters. take :: Int -> YiString -> YiString-take 1 = maybe def Yi.Rope.singleton . Yi.Rope.head+take 1 = maybe mempty Yi.Rope.singleton . Yi.Rope.head take n = fst . Yi.Rope.splitAt n  -- | Drops the first n characters. drop :: Int -> YiString -> YiString-drop 1 = fromMaybe def . Yi.Rope.tail+drop 1 = fromMaybe mempty . Yi.Rope.tail drop n = snd . Yi.Rope.splitAt n  -- | The usual 'Prelude.dropWhile' optimised for 'YiString's.@@ -565,11 +563,26 @@  -- | This is like 'lines'' but it does *not* preserve newlines. ----- Implementation note: GHC does a pretty good job of optimizing--- this naive version. Hand coding a loop should be unnecessary--- here.+-- Specifically, we just strip the newlines from the result of+-- 'lines''.+--+-- This behaves slightly differently than the old split: the number of+-- resulting strings here is equal to the number of newline characters+-- in the underlying string. This is much more consistent than the old+-- behaviour which blindly used @ByteString@s split and stitched the+-- result back together which was inconsistent with the rest of the+-- interface which worked with number of newlines. lines :: YiString -> [YiString]-lines = fmap fromText . TX.lines . toText+lines = Prelude.map dropNl . lines'+  where+    dropNl (YiString t)  = case viewr t of+      EmptyR -> Yi.Rope.empty+      ts :> ch@(Chunk l tx) ->+        YiString $ ts |- if TX.null tx+                         then ch+                         else case TX.last tx of+                           '\n' -> Chunk (l - 1) (TX.init tx)+                           _ -> ch  -- | Splits the 'YiString' into a list of 'YiString' each containing a -- line.@@ -584,15 +597,13 @@ -- -- > 'toText' . 'concat' . 'lines'' ≡ 'toText' --+-- but the underlying structure might change: notably, chunks will+-- most likely change sizes. lines' :: YiString -> [YiString]-lines' = splitByKeepingDelim '\n'--splitByKeepingDelim :: Char -> YiString -> [YiString]-splitByKeepingDelim x = fmap fromText . fix go x . toText-  where-    go :: (Char -> TX.Text -> [TX.Text]) -> Char -> TX.Text -> [TX.Text]-    go _ c (TX.span (/=c) -> (_, TX.null -> True)) = []-    go f c (TX.span (/=c) -> (a,b)) = a `TX.snoc` c : f c (TX.tail b)+lines' t = let (YiString f, YiString s) = splitAtLine' 0 t+           in if T.null s+              then if T.null f then [] else [YiString f]+              else YiString f : lines' (YiString s)  -- | Joins up lines by a newline character. It does not leave a -- newline after the last line. If you want a more classical@@ -629,60 +640,11 @@   put = put . toString   get = Yi.Rope.fromString <$> get --- | 'ConverterName' is used to convey information about the--- underlying 'Converter' used within the buffer to encode and decode--- text. It is mostly here due to the lack of 'Binary' instance for--- 'Converter' itself.-newtype ConverterName = CN String deriving (Show, Eq, Ord, Read, Typeable)---- | Returns the underlying string.-unCn :: ConverterName -> String-unCn (CN s) = s---- | Simply 'put's/'get's the underlying 'String'.-instance Binary ConverterName where-  put (CN s) = put s-  get = CN <$> get---- | Writes the given 'YiString' to the given file, according to the--- 'Converter' specified by 'ConverterName'. You can obtain a--- 'ConverterName' through 'readFile'. If you have a 'Converter', use--- 'writeFileWithConverter' instead.------ If you don't care about the encoding used such as when creating a--- brand new file, you can use 'writeFileUsingText'.------ It's up to the user to handle exceptions.------ Returns an error message if conversion failed, otherwise Nothing--- on success.-writeFile :: FilePath -> YiString -> ConverterName -> IO (Maybe TX.Text)-writeFile f s (CN cn) = open cn (Just True) >>= writeFileWithConverter f s---- | As 'writeFile' but using the provided 'Converter' rather than--- creating one from a 'ConverterName'.------ It's up to the user to handle exceptions.-writeFileWithConverter :: FilePath -> YiString -> Converter -> IO (Maybe TX.Text)-writeFileWithConverter f s c = do-    let bytes = fromUnicode c $ toText s-        errorMsg = "Could not encode text with specified encoding"-    enc <- detectEncoding errorMsg $ BSL.fromChunks [bytes]-    case enc of-        Left err -> return $ Just err-        Right (_, (CN cn)) -> do-            if cn == getName c-                then BS.writeFile f bytes >> return Nothing-                else return . Just $ errorMsg---- | Write a 'YiString' into the given file. This function uses--- 'TF.writeFile' to do the writing: if you have special needs for--- preserving encoding/decoding, use 'writeFile' instead.+-- | Write a 'YiString' into the given file. -- -- It's up to the user to handle exceptions.-writeFileUsingText :: FilePath -> YiString -> IO ()-writeFileUsingText f = TF.writeFile f . toText-+writeFile :: FilePath -> YiString -> IO ()+writeFile f = TXIO.writeFile f . toText  -- | Reads file into the rope, also returning the 'ConverterName' that -- was used for decoding. You should resupply this to 'writeFile' if@@ -693,34 +655,22 @@ -- -- It is up to the user to handle exceptions not directly related to -- character decoding.-readFile :: FilePath -> IO (Either TX.Text (YiString, ConverterName))-readFile fp = BSL.readFile fp >>= detectEncoding err-    where err = "Could not guess the encoding of " <> TX.pack fp---- | Detects the encoding of a sequence of bytes.------ Presumably the calculating the 'YiString' is lazy so it is fine--- to use this to only get the converter name.------ Also allows specification of the error to return if the encoding--- of the bytes cannot be detected. The error returns won't necessarily--- be this error - it is used only if no encoding name is detected at all.-detectEncoding :: TX.Text -> BSL.ByteString-               -> IO (Either TX.Text (YiString, ConverterName))-detectEncoding err cs =-  case detectEncodingName cs of-   Nothing -> return $ case TXE.decodeUtf8' $ BSL.toStrict cs of-      -- The detection failed but stay optimistic and try as UTF8 anyway.-     Left _ -> Left err-     Right tx -> Right (fromText tx, CN "UTF-8")-   Just enc -> do-     let ke = if enc == "ASCII" then Just "UTF-8" else listToMaybe $ aliases enc-     case ke of-      Nothing -> return . Left . TX.pack $ "Don't know how to decode as " <> enc-      Just s -> do-        c <- open s (Just True)-        let st = BSL.toStrict cs-        return $ Right (fromText $ toUnicode c st, CN $ getName c)+readFile :: FilePath -> IO (Either TX.Text YiString)+readFile fp = BSL.readFile fp >>= go decoders+  where+  go [] _ = pure (Left err)+  go (d : ds) bytes =+      try (pure (d bytes)) >>= \case+          Left (_ :: TXEE.UnicodeException) -> go ds bytes+          Right text -> pure (Right (fromLazyText text))+  err = "Could not guess the encoding of " <> TX.pack fp+  decoders =+      [ TXLE.decodeUtf8+      , TXLE.decodeUtf16LE+      , TXLE.decodeUtf16BE+      , TXLE.decodeUtf32LE+      , TXLE.decodeUtf32BE+      ]  -- | Filters the characters from the underlying string. --
test/Yi/RopeSpec.hs view
@@ -37,6 +37,8 @@     prop "R.length ~ T.length" $ R.length `isLike` T.length     prop "R.null ~ T.null" $ R.null `isLike` T.null     prop "R.countNewLines ~ T.count \\n" $ R.countNewLines `isLike` T.count "\n"+    prop "R.concat . R.lines' = id" $ (R.toText . R.concat . R.lines') `isLike` id+    prop "R.lines ~ T.lines" $ (fmap R.toText . R.lines) `isLike` T.lines     prop "R.empty ~ T.empty" $ R.toText R.empty `shouldBe` T.empty     prop "fst . R.splitAt ~ fst . T.splitAt" $ \i ->       fst . R.splitAt i `isLikeT` fst . T.splitAt i
yi-rope.cabal view
@@ -1,5 +1,5 @@ name:                yi-rope-version:             0.9+version:             0.10 synopsis:            A rope data structure used by Yi description:         A rope data structure used by Yi license:             GPL-2@@ -19,13 +19,10 @@   build-depends:       base >=4.8 && <5     , binary-    , bytestring-    , charsetdetect-ae >= 1.0.1-    , data-default+    , bytestring >= 0.10     , deepseq-    , fingertree-    , text-    , text-icu+    , fingertree >= 0.1.1+    , text >= 1.2    hs-source-dirs:      src   default-language:    Haskell2010