diff --git a/Data/Text.hs b/Data/Text.hs
--- a/Data/Text.hs
+++ b/Data/Text.hs
@@ -24,6 +24,9 @@
 
 module Data.Text
     (
+    -- * Strict vs lazy types
+    -- $strict
+
     -- * Fusion
     -- $fusion
 
@@ -142,8 +145,8 @@
     , isInfixOf
 
     -- ** View patterns
-    , prefixed
-    , suffixed
+    , stripPrefix
+    , stripSuffix
 
     -- * Searching
     , filter
@@ -198,14 +201,58 @@
 import Data.Text.Unsafe (Iter(..), iter, iter_, lengthWord16, reverseIter,
                          unsafeHead, unsafeTail)
 import Data.Text.UnsafeChar (unsafeChr)
+import qualified Data.Text.Util as U
 import qualified Data.Text.Encoding.Utf16 as U16
 import Data.Text.Search (indices)
+#if defined(__HADDOCK__)
+import Data.ByteString (ByteString)
+import qualified Data.Text.Lazy as L
+import Data.Int (Int64)
+#endif
 
+-- $strict
+--
+-- This package provides both strict and lazy 'Text' types.  The
+-- strict type is provided by the 'Data.Text' package, while the lazy
+-- type is provided by the 'Data.Text.Lazy' package.  Internally, the
+-- lazy @Text@ type consists of a list of strict chunks.
+--
+-- The strict 'Text' type requires that an entire string fit into
+-- memory at once.  The lazy @Text@ type is capable of streaming
+-- strings that are larger than memory using a small memory footprint.
+-- In many cases, the overhead of chunked streaming makes the lazy
+-- @Text@ type slower than its strict counterpart, but this is not
+-- always the case.  Sometimes, the time complexity of a function in
+-- one module may be different from the other, due to their differing
+-- internal structures.
+--
+-- Each module provides an almost identical API, with the main
+-- difference being that the strict module uses 'Int' values for
+-- lengths and counts, while the lazy module uses 'Int64' lengths.
+
 -- $fusion
 --
 -- Most of the functions in this module are subject to /fusion/,
 -- meaning that a pipeline of such functions will usually allocate at
 -- most one 'Text' value.
+--
+-- As an example, consider the following pipeline:
+--
+-- > import Data.Text as T
+-- > import Data.Text.Encoding as E
+-- >
+-- > countChars :: ByteString -> Int
+-- > countChars = T.length . T.toUpper . E.decodeUtf8
+--
+-- From the type signatures involved, this looks like it should
+-- allocate one 'ByteString' value, and two 'Text' values. However,
+-- when a module is compiled with optimisation enabled under GHC, the
+-- two intermediate 'Text' values will be optimised away, and the
+-- function will be compiled down to a single loop over the source
+-- 'ByteString'.
+--
+-- Functions that can be fused by the compiler are marked with the
+-- phrase \"Subject to fusion\".
 
 instance Eq Text where
     t1 == t2 = stream t1 == stream t2
@@ -299,7 +346,7 @@
       x = do
         arr <- A.unsafeNew len
         A.copyI arr 0 arr1 off1 len1
-        A.copyI arr len1 arr2 off2 (len1+len2)
+        A.copyI arr len1 arr2 off2 len
         return arr
 {-# INLINE append #-}
 
@@ -473,7 +520,7 @@
 -- 'Text's and concatenates the list after interspersing the first
 -- argument between each element of the list.
 intercalate :: Text -> [Text] -> Text
-intercalate t = concat . (L.intersperse t)
+intercalate t = concat . (U.intersperse t)
 {-# INLINE intercalate #-}
 
 -- | /O(n)/ The 'intersperse' function takes a character and places it
@@ -488,6 +535,9 @@
 {-# INLINE reverse #-}
 
 -- | /O(m+n)/ Replace every occurrence of one substring with another.
+--
+-- In (unlikely) bad cases, this function's time complexity degrades
+-- towards /O(n*m)/.
 replace :: Text                 -- ^ Text to search for
         -> Text                 -- ^ Replacement text
         -> Text                 -- ^ Input text
@@ -610,12 +660,12 @@
 -- (typically the left-identity of the operator), and a 'Text',
 -- reduces the 'Text' using the binary operator, from left to right.
 -- Subject to fusion.
-foldl :: (b -> Char -> b) -> b -> Text -> b
+foldl :: (a -> Char -> a) -> a -> Text -> a
 foldl f z t = S.foldl f z (stream t)
 {-# INLINE foldl #-}
 
 -- | /O(n)/ A strict version of 'foldl'.  Subject to fusion.
-foldl' :: (b -> Char -> b) -> b -> Text -> b
+foldl' :: (a -> Char -> a) -> a -> Text -> a
 foldl' f z t = S.foldl' f z (stream t)
 {-# INLINE foldl' #-}
 
@@ -634,7 +684,7 @@
 -- (typically the right-identity of the operator), and a 'Text',
 -- reduces the 'Text' using the binary operator, from right to left.
 -- Subject to fusion.
-foldr :: (Char -> b -> b) -> b -> Text -> b
+foldr :: (Char -> a -> a) -> a -> Text -> a
 foldr f z t = S.foldr f z (stream t)
 {-# INLINE foldr #-}
 
@@ -731,26 +781,21 @@
            | otherwise = scanr f (last t) (init t)
 {-# INLINE scanr1 #-}
 
--- | /O(n)/ Like a combination of 'map' and 'foldl'. Applies a
+-- | /O(n)/ Like a combination of 'map' and 'foldl''. Applies a
 -- function to each element of a 'Text', passing an accumulating
 -- parameter from left to right, and returns a final 'Text'.
 mapAccumL :: (a -> Char -> (a,Char)) -> a -> Text -> (a, Text)
-mapAccumL f s t = case uncons t of
-                    Nothing -> (s, empty)
-                    Just (x, xs) -> (s'', cons y ys)
-                        where (s', y ) = f s x
-                              (s'',ys) = mapAccumL f s' xs
+mapAccumL f z0 = S.mapAccumL f z0 . stream
+{-# INLINE mapAccumL #-}
 
 -- | The 'mapAccumR' function behaves like a combination of 'map' and
--- 'foldr'; it applies a function to each element of a 'Text', passing
--- an accumulating parameter from right to left, and returning a final
--- value of this accumulator together with the new 'Text'.
+-- a strict 'foldr'; it applies a function to each element of a
+-- 'Text', passing an accumulating parameter from right to left, and
+-- returning a final value of this accumulator together with the new
+-- 'Text'.
 mapAccumR :: (a -> Char -> (a,Char)) -> a -> Text -> (a, Text)
-mapAccumR f s t = case uncons t of
-                    Nothing -> (s, empty)
-                    Just (x, xs) ->  (s'', cons y ys)
-                        where (s'',y ) = f s' x
-                              (s', ys) = mapAccumR f s xs
+mapAccumR f z0 = second reverse . S.mapAccumL f z0 . reverseStream
+{-# INLINE mapAccumR #-}
 
 -- -----------------------------------------------------------------------------
 -- ** Generating and unfolding 'Text's
@@ -1354,8 +1399,8 @@
 --
 -- Examples:
 --
--- > prefixed "foo" "foobar" == Just "bar"
--- > prefixed "foo" "quux"   == Nothing
+-- > stripPrefix "foo" "foobar" == Just "bar"
+-- > stripPrefix "foo" "quux"   == Nothing
 --
 -- This is particularly useful with the @ViewPatterns@ extension to
 -- GHC, as follows:
@@ -1364,10 +1409,10 @@
 -- > import Data.Text as T
 -- >
 -- > fnordLength :: Text -> Int
--- > fnordLength (prefixed "fnord" -> Just suf) = T.length suf
+-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
 -- > fnordLength _                              = -1
-prefixed :: Text -> Text -> Maybe Text
-prefixed p@(Text _arr _off plen) t@(Text arr off len)
+stripPrefix :: Text -> Text -> Maybe Text
+stripPrefix p@(Text _arr _off plen) t@(Text arr off len)
     | p `isPrefixOf` t = Just $! textP arr (off+plen) (len-plen)
     | otherwise        = Nothing
 
@@ -1376,8 +1421,8 @@
 --
 -- Examples:
 --
--- > suffixed "bar" "foobar" == Just "foo"
--- > suffixed "foo" "quux"   == Nothing
+-- > stripSuffix "bar" "foobar" == Just "foo"
+-- > stripSuffix "foo" "quux"   == Nothing
 --
 -- This is particularly useful with the @ViewPatterns@ extension to
 -- GHC, as follows:
@@ -1386,10 +1431,10 @@
 -- > import Data.Text as T
 -- >
 -- > quuxLength :: Text -> Int
--- > quuxLength (suffixed "quux" -> Just pre) = T.length pre
+-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
 -- > quuxLength _                             = -1
-suffixed :: Text -> Text -> Maybe Text
-suffixed p@(Text _arr _off plen) t@(Text arr off len)
+stripSuffix :: Text -> Text -> Maybe Text
+stripSuffix p@(Text _arr _off plen) t@(Text arr off len)
     | p `isSuffixOf` t = Just $! textP arr off (len-plen)
     | otherwise        = Nothing
 
diff --git a/Data/Text/Array.hs b/Data/Text/Array.hs
--- a/Data/Text/Array.hs
+++ b/Data/Text/Array.hs
@@ -102,22 +102,16 @@
 
 -- | Create an uninitialized mutable array.
 unsafeNew :: forall s. Int -> ST s (MArray s)
-unsafeNew n =
-#if defined(ASSERTS)
-    assert (n >= 0) .
-#endif
-    ST $ \s1# ->
-    case bytesInArray n of
-      len@(I# len#) ->
-#if defined(ASSERTS)
-         if len < 0 then error (show ("unsafeNew",len)) else
-#endif
-         case newByteArray# len# s1# of
-           (# s2#, marr# #) -> (# s2#, MArray marr#
+unsafeNew n
+  | len < 0 = error $ "Data.Text.Array.unsafeNew: invalid length " ++ show n
+  | otherwise = ST $ \s1# ->
+       case newByteArray# len# s1# of
+         (# s2#, marr# #) -> (# s2#, MArray marr#
 #if defined(ASSERTS)
-                                  n
+                                n
 #endif
-                                  #)
+                                #)
+  where !len@(I# len#) = bytesInArray n
 {-# INLINE unsafeNew #-}
 
 -- | Freeze a mutable array. Do not mutate the 'MArray' afterwards!
diff --git a/Data/Text/Encoding/Error.hs b/Data/Text/Encoding/Error.hs
--- a/Data/Text/Encoding/Error.hs
+++ b/Data/Text/Encoding/Error.hs
@@ -62,7 +62,11 @@
 -- use the description provided to construct a more helpful error
 -- report.
 type OnError a b = String -> Maybe a -> Maybe b
+
+-- | A handler for a decoding error.
 type OnDecodeError = OnError Word8 Char
+
+-- | A handler for an encoding error.
 type OnEncodeError = OnError Char Word8
 
 -- | An exception type for representing Unicode encoding errors.
@@ -91,16 +95,16 @@
 instance Exception UnicodeException
 
 -- | Throw a 'UnicodeException' if decoding fails.
-strictDecode :: OnError Word8 Char
+strictDecode :: OnDecodeError
 strictDecode desc c = throw (DecodeError desc c)
 
 -- | Replace an invalid input byte with the Unicode replacement
 -- character U+FFFD.
-lenientDecode :: OnError Word8 Char
+lenientDecode :: OnDecodeError
 lenientDecode _ _ = Just '\xfffd'
 
 -- | Throw a 'UnicodeException' if encoding fails.
-strictEncode :: OnError Char Word8
+strictEncode :: OnEncodeError
 strictEncode desc c = throw (EncodeError desc c)
 
 -- | Ignore an invalid input, substituting nothing in the output.
diff --git a/Data/Text/Fusion.hs b/Data/Text/Fusion.hs
--- a/Data/Text/Fusion.hs
+++ b/Data/Text/Fusion.hs
@@ -34,6 +34,9 @@
     -- ** Scans
     , reverseScanr
 
+    -- ** Accumulating maps
+    , mapAccumL
+
     -- ** Generation and unfolding
     , unfoldrN
 
@@ -199,3 +202,30 @@
 countChar :: Char -> Stream Char -> Int
 countChar = S.countCharI
 {-# INLINE [0] countChar #-}
+
+-- | /O(n)/ Like a combination of 'map' and 'foldl''. Applies a
+-- function to each element of a 'Text', passing an accumulating
+-- parameter from left to right, and returns a final 'Text'.
+mapAccumL :: (a -> Char -> (a,Char)) -> a -> Stream Char -> (a, Text)
+mapAccumL f z0 (Stream next0 s0 len) = (nz,I.textP na 0 nl)
+  where
+    (na,(nz,nl)) = A.run2 (A.unsafeNew mlen >>= \arr -> outer arr mlen z0 s0 0)
+      where mlen = upperBound 4 len
+    outer arr top = loop
+      where
+        loop !z !s !i =
+            case next0 s of
+              Done          -> return (arr, (z,i))
+              Skip s'       -> loop z s' i
+              Yield x s'
+                | j >= top  -> {-# SCC "mapAccumL/resize" #-} do
+                               let top' = (top + 1) `shiftL` 1
+                               arr' <- A.unsafeNew top'
+                               A.copyM arr' 0 arr 0 top
+                               outer arr' top' z s i
+                | otherwise -> do let (z',c) = f z x
+                                  d <- unsafeWrite arr i c
+                                  loop z' s' (i+d)
+                where j | ord x < 0x10000 = i
+                        | otherwise       = i + 1
+{-# INLINE [0] mapAccumL #-}
diff --git a/Data/Text/Fusion/Internal.hs b/Data/Text/Fusion/Internal.hs
--- a/Data/Text/Fusion/Internal.hs
+++ b/Data/Text/Fusion/Internal.hs
@@ -43,7 +43,7 @@
 
 infixl 2 :*:
 data PairS a b = !a :*: !b
-                 deriving (Eq, Ord, Show)
+                 -- deriving (Eq, Ord, Show)
 
 -- | Allow a function over a stream to switch between two states.
 data Switch = S1 | S2
@@ -52,10 +52,12 @@
               | Skip !s
               | Yield !a !s
 
+{-
 instance (Show a) => Show (Step s a)
     where show Done        = "Done"
           show (Skip _)    = "Skip"
           show (Yield x _) = "Yield " ++ show x
+-}
 
 instance (Eq a) => Eq (Stream a) where
     (==) = eq
diff --git a/Data/Text/Fusion/Size.hs b/Data/Text/Fusion/Size.hs
--- a/Data/Text/Fusion/Size.hs
+++ b/Data/Text/Fusion/Size.hs
@@ -22,9 +22,7 @@
     , unknownSize
     , smaller
     , larger
-    , toMax
     , upperBound
-    , lowerBound
     , isEmpty
     ) where
 
@@ -92,7 +90,7 @@
 mulSize (Exact m) (Max   n) = Max   (m*n)
 mulSize (Max   m) (Exact n) = Max   (m*n)
 mulSize (Max   m) (Max   n) = Max   (m*n)
-mulSize _          _       = Unknown
+mulSize _          _        = Unknown
 {-# INLINE mulSize #-}
 
 -- | Minimum of two size hints.
@@ -118,19 +116,6 @@
 larger   (Max   m)   (Max   n)             = Max   (m `max` n)
 larger _             _                     = Unknown
 {-# INLINE larger #-}
-
--- | Convert a size hint to an upper bound.
-toMax :: Size -> Size
-toMax   (Exact n) = Max n
-toMax a@(Max   _) = a
-toMax   Unknown   = Unknown
-{-# INLINE toMax #-}
-
--- | Compute the minimum size from a size hint.
-lowerBound :: Size -> Int
-lowerBound (Exact n) = n
-lowerBound _         = 0
-{-# INLINE lowerBound #-}
 
 -- | Compute the maximum size from a size hint, if possible.
 upperBound :: Int -> Size -> Int
diff --git a/Data/Text/IO.hs b/Data/Text/IO.hs
--- a/Data/Text/IO.hs
+++ b/Data/Text/IO.hs
@@ -111,7 +111,6 @@
     ts <- readChunks
     (hh', _) <- hClose_help hh
     return (hh'{haType=ClosedHandle}, T.concat ts)
-#endif
   
 -- | Use a more efficient buffer size if we're reading in
 -- block-buffered mode with the default buffer size.  When we can
@@ -129,6 +128,7 @@
            else throw e
       when (d > 0) . hSetBuffering h . BlockBuffering . Just . fromIntegral $ d
     _ -> return ()
+#endif
 
 -- | Read a single line from a handle.
 hGetLine :: Handle -> IO Text
@@ -289,8 +289,8 @@
 -- Beginning with GHC 6.12, text I\/O is performed using the system or
 -- handle's current locale and line ending conventions.
 --
--- Under GHC 6.10 and earlier, the system I\/O libraries /do not
--- support/ locale-sensitive I\/O or line ending conversion.  On these
+-- Under GHC 6.10 and earlier, the system I\/O libraries do not
+-- support locale-sensitive I\/O or line ending conversion.  On these
 -- versions of GHC, functions in this library all use UTF-8.  What
 -- does this mean in practice?
 --
diff --git a/Data/Text/IO/Internal.hs b/Data/Text/IO/Internal.hs
--- a/Data/Text/IO/Internal.hs
+++ b/Data/Text/IO/Internal.hs
@@ -44,7 +44,8 @@
     go hh@Handle__{..} = readIORef haCharBuffer >>= fmap f . hGetLineLoop hh []
 
 hGetLineLoop :: Handle__ -> [Text] -> CharBuffer -> IO [Text]
-hGetLineLoop hh@Handle__{..} ts buf@Buffer{ bufL=r0, bufR=w, bufRaw=raw0 } = do
+hGetLineLoop hh@Handle__{..} = go where
+ go ts buf@Buffer{ bufL=r0, bufR=w, bufRaw=raw0 } = do
   let findEOL raw r | r == w    = return (False, w)
                     | otherwise = do
         (c,r') <- readCharBuf raw r
@@ -76,7 +77,7 @@
               if null str
                 then ioe_EOF
                 else return str
-         Just new_buf -> hGetLineLoop hh (t:ts) new_buf
+         Just new_buf -> go (t:ts) new_buf
 
 -- This function is lifted almost verbatim from GHC.IO.Handle.Text.
 maybeFillReadBuffer :: Handle__ -> CharBuffer -> IO (Maybe CharBuffer)
diff --git a/Data/Text/Lazy.hs b/Data/Text/Lazy.hs
--- a/Data/Text/Lazy.hs
+++ b/Data/Text/Lazy.hs
@@ -27,7 +27,7 @@
 -- This module is intended to be imported @qualified@, to avoid name
 -- clashes with "Prelude" functions.  eg.
 --
--- > import qualified Data.Text.Lazy as B
+-- > import qualified Data.Text.Lazy as L
 
 module Data.Text.Lazy
     (
@@ -150,8 +150,8 @@
     , isInfixOf
 
     -- ** View patterns
-    , prefixed
-    , suffixed
+    , stripPrefix
+    , stripSuffix
 
     -- * Searching
     , filter
@@ -201,6 +201,7 @@
 import Data.Text.Lazy.Fusion (stream, unstream)
 import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldlChunks, foldrChunks)
 import Data.Text.Internal (textP)
+import qualified Data.Text.Util as U
 import Data.Text.Lazy.Search (indices)
 
 instance Eq Text where
@@ -447,7 +448,7 @@
 -- 'Text's and concatenates the list after interspersing the first
 -- argument between each element of the list.
 intercalate :: Text -> [Text] -> Text
-intercalate t = concat . (L.intersperse t)
+intercalate t = concat . (U.intersperse t)
 {-# INLINE intercalate #-}
 
 -- | /O(n)/ The 'intersperse' function takes a character and places it
@@ -517,6 +518,9 @@
         rev a (Chunk t ts) = rev (Chunk (T.reverse t) a) ts
 
 -- | /O(m+n)/ Replace every occurrence of one substring with another.
+--
+-- In (unlikely) bad cases, this function's time complexity degrades
+-- towards /O(n*m)/.
 replace :: Text                 -- ^ Text to search for
         -> Text                 -- ^ Replacement text
         -> Text                 -- ^ Input text
@@ -575,13 +579,13 @@
 -- (typically the left-identity of the operator), and a 'Text',
 -- reduces the 'Text' using the binary operator, from left to right.
 -- Subject to array fusion.
-foldl :: (b -> Char -> b) -> b -> Text -> b
+foldl :: (a -> Char -> a) -> a -> Text -> a
 foldl f z t = S.foldl f z (stream t)
 {-# INLINE foldl #-}
 
 -- | /O(n)/ A strict version of 'foldl'.
 -- Subject to array fusion.
-foldl' :: (b -> Char -> b) -> b -> Text -> b
+foldl' :: (a -> Char -> a) -> a -> Text -> a
 foldl' f z t = S.foldl' f z (stream t)
 {-# INLINE foldl' #-}
 
@@ -602,7 +606,7 @@
 -- (typically the right-identity of the operator), and a 'Text',
 -- reduces the 'Text' using the binary operator, from right to left.
 -- Subject to array fusion.
-foldr :: (Char -> b -> b) -> b -> Text -> b
+foldr :: (Char -> a -> a) -> a -> Text -> a
 foldr f z t = S.foldr f z (stream t)
 {-# INLINE foldr #-}
 
@@ -688,26 +692,31 @@
 scanr1 f t | null t    = empty
            | otherwise = scanr f (last t) (init t)
 
--- | /O(n)/ Like a combination of 'map' and 'foldl'. Applies a
+-- | /O(n)/ Like a combination of 'map' and 'foldl''. Applies a
 -- function to each element of a 'Text', passing an accumulating
 -- parameter from left to right, and returns a final 'Text'.
 mapAccumL :: (a -> Char -> (a,Char)) -> a -> Text -> (a, Text)
-mapAccumL f s t = case uncons t of
-                    Nothing -> (s, empty)
-                    Just (x, xs) -> (s'', cons y ys)
-                        where (s', y ) = f s x
-                              (s'',ys) = mapAccumL f s' xs
+mapAccumL f = go
+  where
+    go z (Chunk c cs)    = (z'', Chunk c' cs')
+        where (z',  c')  = T.mapAccumL f z c
+              (z'', cs') = go z' cs
+    go z Empty           = (z, Empty)
+{-# INLINE mapAccumL #-}
 
 -- | The 'mapAccumR' function behaves like a combination of 'map' and
--- 'foldr'; it applies a function to each element of a 'Text', passing
--- an accumulating parameter from right to left, and returning a final
--- value of this accumulator together with the new 'Text'.
+-- a strict 'foldr'; it applies a function to each element of a
+-- 'Text', passing an accumulating parameter from right to left, and
+-- returning a final value of this accumulator together with the new
+-- 'Text'.
 mapAccumR :: (a -> Char -> (a,Char)) -> a -> Text -> (a, Text)
-mapAccumR f s t = case uncons t of
-                    Nothing -> (s, empty)
-                    Just (x, xs) ->  (s'', cons y ys)
-                        where (s'',y ) = f s' x
-                              (s', ys) = mapAccumR f s xs
+mapAccumR f = go
+  where
+    go z (Chunk c cs)   = (z'', Chunk c' cs')
+        where (z'', c') = T.mapAccumR f z' c
+              (z', cs') = go z cs
+    go z Empty          = (z, Empty)
+{-# INLINE mapAccumR #-}
 
 -- | /O(n*m)/ 'replicate' @n@ @t@ is a 'Text' consisting of the input
 -- @t@ repeated @n@ times.
@@ -1209,8 +1218,8 @@
 --
 -- Examples:
 --
--- > prefixed "foo" "foobar" == Just "bar"
--- > prefixed "foo" "quux"   == Nothing
+-- > stripPrefix "foo" "foobar" == Just "bar"
+-- > stripPrefix "foo" "quux"   == Nothing
 --
 -- This is particularly useful with the @ViewPatterns@ extension to
 -- GHC, as follows:
@@ -1219,11 +1228,11 @@
 -- > import Data.Text as T
 -- >
 -- > fnordLength :: Text -> Int
--- > fnordLength (prefixed "fnord" -> Just suf) = T.length suf
+-- > fnordLength (stripPrefix "fnord" -> Just suf) = T.length suf
 -- > fnordLength _                              = -1
-prefixed :: Text -> Text -> Maybe Text
+stripPrefix :: Text -> Text -> Maybe Text
 -- Yes, this could be much more efficient.
-prefixed p t
+stripPrefix p t
     | p `isPrefixOf` t = Just (drop (length p) t)
     | otherwise        = Nothing
 
@@ -1232,8 +1241,8 @@
 --
 -- Examples:
 --
--- > suffixed "bar" "foobar" == Just "foo"
--- > suffixed "foo" "quux"   == Nothing
+-- > stripSuffix "bar" "foobar" == Just "foo"
+-- > stripSuffix "foo" "quux"   == Nothing
 --
 -- This is particularly useful with the @ViewPatterns@ extension to
 -- GHC, as follows:
@@ -1242,11 +1251,11 @@
 -- > import Data.Text as T
 -- >
 -- > quuxLength :: Text -> Int
--- > quuxLength (suffixed "quux" -> Just pre) = T.length pre
+-- > quuxLength (stripSuffix "quux" -> Just pre) = T.length pre
 -- > quuxLength _                             = -1
-suffixed :: Text -> Text -> Maybe Text
+stripSuffix :: Text -> Text -> Maybe Text
 -- Yes, this could be much more efficient.
-suffixed p t
+stripSuffix p t
     | p `isSuffixOf` t = Just (take (length t - length p) t)
     | otherwise        = Nothing
 
diff --git a/Data/Text/Lazy/Search.hs b/Data/Text/Lazy/Search.hs
--- a/Data/Text/Lazy/Search.hs
+++ b/Data/Text/Lazy/Search.hs
@@ -42,15 +42,17 @@
 indices needle@(Chunk n ns) _haystack@(Chunk k ks)
     | nlen <= 0  = []
     | nlen == 1  = indicesOne (nindex 0) 0 k ks
-    | otherwise  = scan 0 0 k ks
+    | otherwise  = advance k ks 0 0
   where
-    scan !g !i x@(T.Text _ _ l) xs
+    advance x@(T.Text _ _ l) xs = scan
+     where
+      scan !g !i
          | i >= m = case xs of
                       Empty           -> []
-                      Chunk y ys      -> scan g (i-m) y ys
+                      Chunk y ys      -> advance y ys g (i-m)
          | lackingHay (i + nlen) x xs  = []
-         | c == z && candidateMatch 0  = g : scan (g+nlen) (i+nlen) x xs
-         | otherwise                   = scan (g+delta) (i+delta) x xs
+         | c == z && candidateMatch 0  = g : scan (g+nlen) (i+nlen)
+         | otherwise                   = scan (g+delta) (i+delta)
        where
          m = fromIntegral l
          c = hindex (i + nlast)
diff --git a/Data/Text/Util.hs b/Data/Text/Util.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Util.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE CPP, DeriveDataTypeable #-}
+
+-- |
+-- Module      : Data.Text.Util
+-- Copyright   : 2010 Bryan O'Sullivan
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Useful functions.
+
+module Data.Text.Util
+    (
+      intersperse
+    ) where
+
+-- | A lazier version of Data.List.intersperse.  The other version
+-- causes space leaks!
+intersperse :: a -> [a] -> [a]
+intersperse _   []     = []
+intersperse sep (x:xs) = x : go xs
+  where
+    go []     = []
+    go (y:ys) = sep : y: go ys
+{-# INLINE intersperse #-}
diff --git a/tests/Makefile b/tests/Makefile
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -57,7 +57,7 @@
 
 coverage: coverage-html/hpc_index.html
 
-coverage-html/hpc_index.html: qc-hpc
+coverage-html/hpc_index.html: qc-hpc stdio-hpc
 	@rm -f qc-hpc.tix stdio-hpc.tix coverage.tix
 	./qc-hpc -a 100 +RTS -N
 	bash ./cover-stdio.sh
diff --git a/tests/Properties.hs b/tests/Properties.hs
--- a/tests/Properties.hs
+++ b/tests/Properties.hs
@@ -184,6 +184,8 @@
 tl_mappend s      = mappend s`eqP` (unpackS . mappend (TL.pack s))
 t_mconcat         = mconcat `eq` (unpackS . mconcat . L.map T.pack)
 tl_mconcat        = mconcat `eq` (unpackS . mconcat . L.map TL.pack)
+t_mempty          = mempty == (unpackS (mempty :: T.Text))
+tl_mempty         = mempty == (unpackS (mempty :: TL.Text))
 t_IsString        = fromString  `eqP` (T.unpack . fromString)
 tl_IsString       = fromString  `eqP` (TL.unpack . fromString)
 
@@ -581,18 +583,13 @@
 t_isInfixOf s     = L.isInfixOf s `eqP` T.isInfixOf (packS s)
 tl_isInfixOf s    = L.isInfixOf s `eqP` TL.isInfixOf (packS s)
 
-prefixed (p:ps) (t:ts)
-    | p == t = prefixed ps ts
-prefixed [] ts = Just ts
-prefixed _  _  = Nothing
-
-t_prefixed s      = (fmap packS . prefixed s) `eqP` T.prefixed (packS s)
-tl_prefixed s     = (fmap packS . prefixed s) `eqP` TL.prefixed (packS s)
+t_stripPrefix s      = (fmap packS . L.stripPrefix s) `eqP` T.stripPrefix (packS s)
+tl_stripPrefix s     = (fmap packS . L.stripPrefix s) `eqP` TL.stripPrefix (packS s)
 
-suffixed p t = reverse `fmap` prefixed (reverse p) (reverse t)
+stripSuffix p t = reverse `fmap` L.stripPrefix (reverse p) (reverse t)
 
-t_suffixed s      = (fmap packS . suffixed s) `eqP` T.suffixed (packS s)
-tl_suffixed s     = (fmap packS . suffixed s) `eqP` TL.suffixed (packS s)
+t_stripSuffix s      = (fmap packS . stripSuffix s) `eqP` T.stripSuffix (packS s)
+tl_stripSuffix s     = (fmap packS . stripSuffix s) `eqP` TL.stripSuffix (packS s)
 
 sf_elem p c       = (L.elem c . L.filter p) `eqP` (S.elem c . S.filter p)
 sf_filter q p     = (L.filter p . L.filter q) `eqP`
@@ -809,6 +806,8 @@
     testProperty "tl_mappend" tl_mappend,
     testProperty "t_mconcat" t_mconcat,
     testProperty "tl_mconcat" tl_mconcat,
+    testProperty "t_mempty" t_mempty,
+    testProperty "tl_mempty" tl_mempty,
     testProperty "t_IsString" t_IsString,
     testProperty "tl_IsString" tl_IsString
   ],
@@ -1082,10 +1081,10 @@
     testProperty "tl_isInfixOf" tl_isInfixOf,
 
     testGroup "view" [
-      testProperty "t_prefixed" t_prefixed,
-      testProperty "tl_prefixed" tl_prefixed,
-      testProperty "t_suffixed" t_suffixed,
-      testProperty "tl_suffixed" tl_suffixed
+      testProperty "t_stripPrefix" t_stripPrefix,
+      testProperty "tl_stripPrefix" tl_stripPrefix,
+      testProperty "t_stripSuffix" t_stripSuffix,
+      testProperty "tl_stripSuffix" tl_stripSuffix
     ]
   ],
 
diff --git a/tests/StdioCoverage.hs b/tests/StdioCoverage.hs
new file mode 100644
--- /dev/null
+++ b/tests/StdioCoverage.hs
@@ -0,0 +1,25 @@
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+import System.Environment (getArgs)
+import System.Exit (exitFailure)
+import System.IO (hPutStrLn, stderr)
+
+main = do
+  args <- getArgs
+  case args of
+    ["T.readFile", name] -> T.putStr =<< T.readFile name
+    ["T.writeFile", name, t] -> T.writeFile name (T.pack t)
+    ["T.appendFile", name, t] -> T.appendFile name (T.pack t)
+    ["T.interact"] -> T.interact id
+    ["T.getContents"] -> T.putStr =<< T.getContents
+    ["T.getLine"] -> T.putStrLn =<< T.getLine
+
+    ["TL.readFile", name] -> TL.putStr =<< TL.readFile name
+    ["TL.writeFile", name, t] -> TL.writeFile name (TL.pack t)
+    ["TL.appendFile", name, t] -> TL.appendFile name (TL.pack t)
+    ["TL.interact"] -> TL.interact id
+    ["TL.getContents"] -> TL.putStr =<< TL.getContents
+    ["TL.getLine"] -> TL.putStrLn =<< TL.getLine
+    _ -> hPutStrLn stderr "invalid directive!" >> exitFailure
diff --git a/tests/TestUtils.hs b/tests/TestUtils.hs
new file mode 100644
--- /dev/null
+++ b/tests/TestUtils.hs
@@ -0,0 +1,31 @@
+module TestUtils
+    (
+      withRedirect
+    , withTempFile
+    ) where
+
+import Control.Exception (bracket, bracket_)
+import Control.Monad (when)
+import GHC.IO.Handle.Internals (withHandle)
+import System.Directory (removeFile)
+import System.IO
+
+withTempFile :: (FilePath -> Handle -> IO a) -> IO a
+withTempFile = bracket (openTempFile "." "crashy.txt") cleanupTemp . uncurry
+  where
+    cleanupTemp (path,h) = do
+      open <- hIsOpen h
+      when open (hClose h)
+      removeFile path
+
+withRedirect :: Handle -> Handle -> IO a -> IO a
+withRedirect tmp h = bracket_ swap swap
+  where
+    whenM p a = p >>= (`when` a)
+    swap = do
+      whenM (hIsOpen tmp) $ whenM (hIsWritable tmp) $ hFlush tmp
+      whenM (hIsOpen h) $ whenM (hIsWritable h) $ hFlush h
+      withHandle "spam" tmp $ \tmph -> do
+        hh <- withHandle "spam" h $ \hh ->
+          return (tmph,hh)
+        return (hh,())
diff --git a/tests/benchmarks/Cut.hs b/tests/benchmarks/Cut.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/Cut.hs
@@ -0,0 +1,60 @@
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Data.Text.IO as T
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.Text.Lazy.IO as TL
+import qualified Data.Text.Lazy as TL
+import Data.Int (Int64)
+import Numeric (readDec)
+import System.Environment (getArgs)
+
+bytestring file s e = do
+  t <- B.readFile file
+  B.putStr (cut t)
+  where
+    cut = B.unlines . map (B.take (e - s) . B.drop s) . B.lines
+
+lazyBytestring file s e = do
+  t <- BL.readFile file
+  BL.putStr (cut (fromIntegral s) (fromIntegral e) t)
+  where
+    cut s e = BL.unlines . map (BL.take (e - s) . BL.drop s) . BL.lines
+
+lazyText file s e = do
+  t <- TL.readFile file
+  TL.putStr (cut (fromIntegral s) (fromIntegral e) t)
+  where
+    cut s e = TL.unlines . map (TL.take (e - s) . TL.drop s) . TL.lines
+
+text file s e = do
+  t <- T.readFile file
+  T.putStr (cut t)
+  where
+    cut = T.unlines . map (T.take (e - s) . T.drop s) . T.lines
+
+textBS file s e = do
+  bs <- B.readFile file
+  T.putStr . cut . T.decodeUtf8 $ bs
+  where
+    cut = T.unlines . map (T.take (e - s) . T.drop s) . T.lines
+
+lazyTextBS file s e = do
+  t <- BL.readFile file
+  TL.putStr (cut (fromIntegral s) (fromIntegral e) (TL.decodeUtf8 t))
+  where
+    cut s e = TL.unlines . map (TL.take (e - s) . TL.drop s) . TL.lines
+
+main = do
+  (name : ss : es : file : _) <- getArgs
+  let [(s',"")] = readDec ss
+      [(e,"")] = readDec es
+      s = s' - 1
+  case name of
+    "bs" -> bytestring file s e
+    "lbs" -> lazyBytestring file s e
+    "ltext" -> lazyText file s e
+    "text" -> text file s e
+    "ltextBS" -> lazyTextBS file s e
+    "textBS" -> textBS file s e
diff --git a/tests/benchmarks/FileIndices.hs b/tests/benchmarks/FileIndices.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/FileIndices.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE BangPatterns #-}
+
+import System.Environment (getArgs)
+import qualified Data.Text.Lazy.IO as T
+import qualified Data.Text.Lazy as T
+import qualified Data.Text.Lazy.Encoding as T
+import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.ByteString.Lazy.Search as B
+
+text :: FilePath -> String -> IO ()
+text file pat = T.readFile file >>= print . T.count (T.pack pat)
+
+textBS :: FilePath -> String -> IO ()
+textBS file pat = B.readFile file >>= print . T.count (T.pack pat) . T.decodeUtf8
+
+bytestring :: FilePath -> String -> IO ()
+bytestring file pat = B.readFile file >>= print . length . B.indices (BS.pack pat)
+
+main = do
+  (name : file : pat : _) <- getArgs
+  case name of
+    "bs" -> bytestring file pat
+    "text" -> text file pat
+    "textBS" -> textBS file pat
diff --git a/tests/benchmarks/FileRead.hs b/tests/benchmarks/FileRead.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/FileRead.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE BangPatterns #-}
+
+import System.Environment (getArgs)
+import qualified Data.Text.IO as T
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.Lazy.IO as TL
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy.Char8 as BL
+import System.IO
+
+string :: Handle -> IO ()
+string h = hGetContents h >>= print . length
+
+ltext :: Handle -> IO ()
+ltext h = do
+  t <- {-# SCC "TL.hGetContents" #-} TL.hGetContents h
+  print (TL.length t)
+
+ltextBS :: Handle -> IO ()
+ltextBS h = do
+  bs <- {-# SCC "B.hGetContents" #-} BL.hGetContents h
+  print . TL.length . TL.decodeUtf8 $ bs
+
+text :: Handle -> IO ()
+text h = do
+  t <- {-# SCC "T.hGetContents" #-} T.hGetContents h
+  print (T.length t)
+
+textBS :: Handle -> IO ()
+textBS h = do
+  bs <- {-# SCC "B.hGetContents" #-} B.hGetContents h
+  print . T.length . T.decodeUtf8 $ bs
+
+lbytestring :: Handle -> IO ()
+lbytestring h = do
+  bs <- {-# SCC "BL.hGetContents" #-} BL.hGetContents h
+  print (BL.length bs)
+
+bytestring :: Handle -> IO ()
+bytestring h = do
+  bs <- {-# SCC "B.hGetContents" #-} B.hGetContents h
+  print (B.length bs)
+
+main = do
+  (name : file : _) <- getArgs
+  h <- openFile file ReadMode
+  case name of
+    "bs" -> bytestring h
+    "lbs" -> lbytestring h
+    "ltext" -> ltext h
+    "ltextBS" -> ltextBS h
+    "string" -> string h
+    "text" -> text h
+    "textBS" -> textBS h
diff --git a/tests/benchmarks/FoldLines.hs b/tests/benchmarks/FoldLines.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/FoldLines.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE BangPatterns #-}
+
+import System.Environment
+import System.IO
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import qualified Data.ByteString as S
+
+-- Text
+foldLinesT :: (a -> T.Text -> a) -> a -> Handle -> IO a
+foldLinesT f z0 h = go z0
+  where
+    go !z = do
+        eof <- hIsEOF h
+        if eof
+            then return z
+            else do
+                l <- T.hGetLine h
+                let z' = f z l in go z'
+{-# INLINE foldLinesT #-}
+
+testT :: Handle -> IO Int
+testT = foldLinesT (\n _ -> n + 1) 0
+
+--ByteString
+foldLinesB :: (a -> S.ByteString -> a) -> a -> Handle -> IO a
+foldLinesB f z0 h = go z0
+  where
+    go !z = do
+        eof <- hIsEOF h
+        if eof
+            then return z
+            else do
+                l <- S.hGetLine h
+                let z' = f z l in go z'
+{-# INLINE foldLinesB #-}
+
+testB :: Handle -> IO Int
+testB = foldLinesB (\n _ -> n + 1) 0
+
+main = do
+  (name : file : _) <- getArgs
+  h <- openFile file ReadMode
+  hSetBuffering h (BlockBuffering (Just 16384))
+  case name of
+    "bs" -> testB h
+    "text" -> testT h
diff --git a/tests/benchmarks/HtmlCombinator.hs b/tests/benchmarks/HtmlCombinator.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/HtmlCombinator.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE BangPatterns, OverloadedStrings #-}
+import Data.Monoid (mappend, mconcat)
+import Prelude hiding (putStr)
+import Data.Text.Lazy.Builder (Builder, fromText, toLazyText)
+import Data.Text.Lazy.IO (putStr)
+
+import qualified Data.Text as T
+
+main :: IO ()
+main = do
+  putStr "Content-Type: text/html\n\n<table>"
+  putStr . toLazyText $ mconcat (replicate 20000 makeRow) 
+  putStr "</table>"
+
+makeRow :: Builder
+makeRow = mconcat (map makeCol [1..50])
+
+makeCol :: Int -> Builder
+makeCol 1 = fromText "<tr><td>1</td>"
+makeCol 50 = fromText "<td>50</td></tr>"
+makeCol i = fromText "<td>" `mappend` (textInt i `mappend` fromText "</td>")
+
+textInt :: Int -> Builder
+textInt = fromText . T.pack . show
diff --git a/tests/benchmarks/Makefile b/tests/benchmarks/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/Makefile
@@ -0,0 +1,18 @@
+CC := $(shell icu-config --cc)
+CFLAGS := -g $(shell icu-config --cflags)
+CPPFLAGS := $(shell icu-config --cppflags)
+LDFLAGS := $(CFLAGS) $(shell icu-config --ldflags --ldflags-icuio)
+ghc := ghc
+
+all := FileRead FileRead_prof Replace Replace_prof fileread_c
+
+all: $(all)
+
+%: %.hs
+	$(ghc) -O --make -o $@ $^
+
+%_prof: %.hs
+	$(ghc) -prof -auto-all -O --make -o $@ $^
+
+clean:
+	-rm -f *.hi *.o $(all)
diff --git a/tests/benchmarks/Replace.hs b/tests/benchmarks/Replace.hs
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/Replace.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE BangPatterns #-}
+module Main (main) where
+
+import System.Environment (getArgs)
+import qualified Data.Text.Lazy as LT
+import qualified Data.Text.Lazy.IO as LT
+import qualified Data.ByteString.Lazy.Search as LB
+import qualified Data.ByteString.Lazy.Char8 as LB
+import qualified Data.ByteString.Char8 as B
+
+lazyText file pat sub =
+  LT.readFile file >>= LT.putStr . LT.replace (LT.pack pat) (LT.pack sub)
+
+lazyBS file pat sub =
+  LB.readFile file >>= LB.putStr . LB.replace (B.pack pat) (LB.pack sub)
+
+main = do
+  (kind : file : pat : sub : _) <- getArgs
+  case kind of
+    "lazyText" -> lazyText file pat sub
+    "lazyTextNull" -> LT.readFile file >>= LT.putStr
+    "lazyBS" -> lazyBS file pat sub
+    "lazyBSNull" -> LB.readFile file >>= LB.putStr
diff --git a/tests/benchmarks/fileread.py b/tests/benchmarks/fileread.py
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/fileread.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import sys
+
+def string(name):
+    print len(open(name).read())
+
+def lazystring(name):
+    fp = open(name)
+    n = 0
+    d = True
+    bs = 128 * 1024
+    read = fp.read
+    while d:
+        d = len(read(bs))
+        n += d
+    print n
+
+def lazytext(name):
+    fp = open(name)
+    n = 0
+    d = True
+    bs = 128 * 1024
+    read = fp.read
+    while d:
+        s = read(bs)
+        d = len(s.decode('utf-8', 'replace'))
+        n += d
+    print n
+
+def text(name):
+    print len(open(name).read().decode('utf-8', 'replace'))
+
+if sys.argv[1] == 'bs':
+    string(sys.argv[2])
+if sys.argv[1] == 'lbs':
+    lazystring(sys.argv[2])
+elif sys.argv[1] == 'lazytext':
+    lazytext(sys.argv[2])
+elif sys.argv[1] == 'text':
+    text(sys.argv[2])
diff --git a/tests/benchmarks/fileread_c.c b/tests/benchmarks/fileread_c.c
new file mode 100644
--- /dev/null
+++ b/tests/benchmarks/fileread_c.c
@@ -0,0 +1,96 @@
+#include <unicode/ustdio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void lazystring(const char *name)
+{
+    FILE *ufp = fopen(name, "r");
+    const size_t bufsize = sizeof(char) * 32 * 1024;
+    char *str = malloc(bufsize);
+    long len = 0;
+    int32_t n;
+
+    do {
+	n = fread(str, sizeof(char), bufsize, ufp);
+	len += n;
+    } while (n > 0);
+
+    printf("%ld\n", len);
+}
+
+void lazytext(const char *name)
+{
+    UFILE *ufp = u_fopen(name, "r", NULL, "UTF-8");
+    const size_t bufsize = sizeof(UChar) * 32 * 1024;
+    UChar *str = malloc(bufsize);
+    long len = 0;
+    int32_t n;
+
+    do {
+	n = u_file_read(str, bufsize, ufp);
+	len += n;
+    } while (n > 0);
+
+    printf("%ld\n", len);
+}
+
+void text(const char *name)
+{
+    UFILE *ufp = u_fopen(name, "r", NULL, "UTF-8");
+    FILE *fp = u_fgetfile(ufp);
+    UChar *str;
+    long fsize;
+    int32_t n;
+
+    fseek(fp, 0, SEEK_END);
+    fsize = ftell(fp);
+    u_frewind(ufp);
+
+    str = malloc(sizeof(*str) * fsize);
+
+    n = u_file_read(str, fsize, ufp);
+
+    printf("%d\n", n);
+}
+
+void string(const char *name)
+{
+    FILE *fp = fopen(name, "r");
+    char *str;
+    long fsize;
+    int32_t n;
+
+    fseek(fp, 0, SEEK_END);
+    fsize = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+
+    str = malloc(sizeof(*str) * fsize);
+
+    n = fread(str, sizeof(char), fsize, fp);
+
+    printf("%d\n", n);
+}
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+	fprintf(stderr, "Usage: %s handler filename\n", argv[0]);
+	exit(1);
+    }
+
+    if (strcmp(argv[1], "lazystring") == 0)
+	lazystring(argv[2]);
+    else if (strcmp(argv[1], "lazytext") == 0)
+	lazytext(argv[2]);
+    else if (strcmp(argv[1], "string") == 0)
+	string(argv[2]);
+    else if (strcmp(argv[1], "text") == 0)
+	text(argv[2]);
+    else {
+	fprintf(stderr, "no matching handler\n");
+	return 1;
+    }
+
+    return 0;
+}
diff --git a/tests/cover-stdio.sh b/tests/cover-stdio.sh
new file mode 100644
--- /dev/null
+++ b/tests/cover-stdio.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+exe=./stdio-hpc
+
+rm -f $exe.tix
+
+f=$(mktemp stdio-f.XXXXXX)
+g=$(mktemp stdio-g.XXXXXX)
+
+for t in T TL; do
+    echo $t.readFile > $f
+    $exe $t.readFile $f > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.readFile 1>&2
+    fi
+
+    $exe $t.writeFile $f $t.writeFile
+    echo -n $t.writeFile > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.writeFile 1>&2
+    fi
+
+    echo -n quux > $f
+    $exe $t.appendFile $f $t.appendFile
+    echo -n quux$t.appendFile > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.appendFile 1>&2
+    fi
+
+    echo $t.interact | $exe $t.interact > $f
+    echo $t.interact > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.interact 1>&2
+    fi
+
+    echo $t.getContents | $exe $t.getContents > $f
+    echo $t.getContents > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.getContents 1>&2
+    fi
+
+    echo $t.getLine | $exe $t.getLine > $f
+    echo $t.getLine > $g
+    if ! diff -u $f $g; then
+	errs=$((errs+1))
+	echo FAIL: $t.getLine 1>&2
+    fi
+done
+
+rm -f $f $g
+
+exit $errs
diff --git a/text.cabal b/text.cabal
--- a/text.cabal
+++ b/text.cabal
@@ -1,7 +1,30 @@
 name:           text
-version:        0.8.0.0
-synopsis:       An efficient packed Unicode text type
-description:    An efficient packed Unicode text type.
+version:        0.8.1.0
+homepage:       http://code.haskell.org/text
+synopsis:       An efficient packed Unicode text type.
+description:    
+    .
+    An efficient packed, immutable Unicode text type (both strict and
+    lazy), with a powerful loop fusion optimization framework.
+    .
+    The 'Text' type represents Unicode character strings, in a time and
+    space-efficient manner. This package provides text processing
+    capabilities that are optimized for performance critical use, both
+    in terms of large data quantities and high speed.
+    .
+    The 'Text' type provides character-encoding, type-safe case
+    conversion via whole-string case conversion functions. It also
+    provides a range of functions for converting Text values to and from
+    'ByteStrings', using several standard encodings (see the 'text-icu'
+    package for a much larger variety of encoding functions).
+    .
+    Efficient locale-sensitive support for text IO is also supported.
+    .
+    This module is intended to be imported qualified, to avoid name
+    clashes with Prelude functions, e.g.
+    .
+    > import qualified Data.Text as T
+    .
 license:        BSD3
 license-file:   LICENSE
 author:         Tom Harper <rtomharper@googlemail.com>
@@ -11,22 +34,34 @@
 copyright:      2008-2009 Tom Harper, 2009-2010 Bryan O'Sullivan
 category:       Data, Text
 build-type:     Simple
-cabal-version:  >= 1.2.3
+cabal-version:  >= 1.6
 extra-source-files:
     README
     TODO
+    -- scripts/CaseFolding.txt
+    -- scripts/SpecialCasing.txt
     scripts/ApiCompare.hs
     scripts/Arsec.hs
     scripts/CaseFolding.hs
-    -- scripts/CaseFolding.txt
     scripts/CaseMapping.hs
     scripts/SpecialCasing.hs
-    -- scripts/SpecialCasing.txt
-    tests/Makefile
     tests/Benchmarks.hs
+    tests/Makefile
     tests/Properties.hs
     tests/QuickCheckUtils.hs
     tests/SlowFunctions.hs
+    tests/StdioCoverage.hs
+    tests/TestUtils.hs
+    tests/benchmarks/Cut.hs
+    tests/benchmarks/FileIndices.hs
+    tests/benchmarks/FileRead.hs
+    tests/benchmarks/FoldLines.hs
+    tests/benchmarks/HtmlCombinator.hs
+    tests/benchmarks/Makefile
+    tests/benchmarks/Replace.hs
+    tests/benchmarks/fileread.py
+    tests/benchmarks/fileread_c.c
+    tests/cover-stdio.sh
 
 flag developer
   description: operate in developer mode
@@ -65,6 +100,7 @@
     Data.Text.Unsafe
     Data.Text.UnsafeChar
     Data.Text.UnsafeShift
+    Data.Text.Util
 
   build-depends:
     base       < 5,
@@ -86,3 +122,7 @@
   if flag(developer)
     ghc-options: -Werror
     cpp-options: -DASSERTS
+
+source-repository head
+  type:     darcs
+  location: http://code.haskell.org/text/
