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 +64/−114
- test/Yi/RopeSpec.hs +2/−0
- yi-rope.cabal +4/−7
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