diff --git a/Data/Text.hs b/Data/Text.hs
--- a/Data/Text.hs
+++ b/Data/Text.hs
@@ -54,13 +54,19 @@
     , intersperse
     , transpose
     , reverse
+    , replace
 
-    -- * Case conversion
+    -- ** Case conversion
     -- $case
     , toCaseFold
     , toLower
     , toUpper
 
+    -- ** Justification
+    , justifyLeft
+    , justifyRight
+    , center
+
     -- * Folds
     , foldl
     , foldl'
@@ -91,6 +97,7 @@
 
     -- ** Generation and unfolding
     , replicate
+    , replicateChar
     , unfoldr
     , unfoldrN
 
@@ -101,6 +108,11 @@
     , drop
     , takeWhile
     , dropWhile
+    , dropWhileEnd
+    , dropAround
+    , strip
+    , stripStart
+    , stripEnd
     , splitAt
     , span
     , break
@@ -110,9 +122,12 @@
     , tails
 
     -- ** Breaking into many substrings
+    -- $split
     , split
+    , splitTimes
+    , splitTimesEnd
     , splitWith
-    , breakSubstring
+    , chunksOf
 
     -- ** Breaking into lines and words
     , lines
@@ -135,6 +150,7 @@
     -- , findSubstring
     
     -- * Indexing
+    -- $index
     , index
     , findIndex
     , findIndices
@@ -154,7 +170,7 @@
                 Eq(..), Ord(..), (++),
                 Read(..), Show(..),
                 (&&), (||), (+), (-), (.), ($),
-                not, return, otherwise)
+                fromIntegral, div, not, return, otherwise)
 import Control.Exception (assert)
 import Data.Char (isSpace)
 import Control.Monad.ST (ST)
@@ -170,15 +186,15 @@
 
 import Data.Text.Internal (Text(..), empty, text, textP)
 import qualified Prelude as P
-import Data.Text.Unsafe (iter, iter_, unsafeHead, unsafeTail)
+import Data.Text.Unsafe (iter, iter_, reverseIter, unsafeHead, unsafeTail)
 import Data.Text.UnsafeChar (unsafeChr)
 import qualified Data.Text.Encoding.Utf16 as U16
 
 -- $fusion
 --
--- Most of the functions in this module are subject to /array fusion/,
--- meaning that a pipeline of functions will usually allocate at most
--- one 'Text' value.
+-- 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.
 
 instance Eq Text where
     t1 == t2 = stream t1 == stream t2
@@ -205,21 +221,18 @@
 -- -----------------------------------------------------------------------------
 -- * Conversion to/from 'Text'
 
--- | /O(n)/ Convert a 'String' into a 'Text'.
---
--- This function is subject to array fusion.
+-- | /O(n)/ Convert a 'String' into a 'Text'.  Subject to fusion.
 pack :: String -> Text
 pack = unstream . S.streamList
 {-# INLINE [1] pack #-}
 
--- | /O(n)/ Convert a Text into a String.
--- Subject to array fusion.
+-- | /O(n)/ Convert a Text into a String.  Subject to fusion.
 unpack :: Text -> String
 unpack = S.unstreamList . stream
 {-# INLINE [1] unpack #-}
 
 -- | /O(1)/ Convert a character into a Text.
--- Subject to array fusion.
+-- Subject to fusion.
 singleton :: Char -> Text
 singleton = unstream . S.singleton
 {-# INLINE [1] singleton #-}
@@ -229,19 +242,19 @@
 
 -- | /O(n)/ Adds a character to the front of a 'Text'.  This function
 -- is more costly than its 'List' counterpart because it requires
--- copying a new array.  Subject to array fusion.
+-- copying a new array.  Subject to fusion.
 cons :: Char -> Text -> Text
 cons c t = unstream (S.cons c (stream t))
 {-# INLINE cons #-}
 
 -- | /O(n)/ Adds a character to the end of a 'Text'.  This copies the
--- entire array in the process.  Subject to array fusion.
+-- entire array in the process, unless fused.  Subject to fusion.
 snoc :: Text -> Char -> Text
 snoc t c = unstream (S.snoc (stream t) c)
 {-# INLINE snoc #-}
 
 -- | /O(n)/ Appends one 'Text' to the other by copying both of them
--- into a new 'Text'.  Subject to array fusion.
+-- into a new 'Text'.  Subject to fusion.
 append :: Text -> Text -> Text
 append (Text arr1 off1 len1) (Text arr2 off2 len2) = Text (A.run x) 0 len
     where
@@ -266,19 +279,19 @@
  #-}
 
 -- | /O(1)/ Returns the first character of a 'Text', which must be
--- non-empty.  Subject to array fusion.
+-- non-empty.  Subject to fusion.
 head :: Text -> Char
 head t = S.head (stream t)
 {-# INLINE head #-}
 
 -- | /O(1)/ Returns the first character and rest of a 'Text', or
--- 'Nothing' if empty. Subject to array fusion.
+-- 'Nothing' if empty. Subject to fusion.
 uncons :: Text -> Maybe (Char, Text)
 uncons t@(Text arr off len)
     | len <= 0  = Nothing
     | otherwise = Just (c, textP arr (off+d) (len-d))
     where (c,d) = iter t 0
-{-# INLINE uncons #-}
+{-# INLINE [1] uncons #-}
 
 -- | Lifted from Control.Arrow and specialized.
 second :: (b -> c) -> (a,b) -> (a,c)
@@ -292,7 +305,7 @@
   #-}
 
 -- | /O(1)/ Returns the last character of a 'Text', which must be
--- non-empty.  Subject to array fusion.
+-- non-empty.  Subject to fusion.
 last :: Text -> Char
 last (Text arr off len)
     | len <= 0                 = emptyError "last"
@@ -310,7 +323,7 @@
   #-}
 
 -- | /O(1)/ Returns all characters after the head of a 'Text', which
--- must be non-empty.  Subject to array fusion.
+-- must be non-empty.  Subject to fusion.
 tail :: Text -> Text
 tail t@(Text arr off len)
     | len <= 0  = emptyError "tail"
@@ -326,7 +339,7 @@
  #-}
 
 -- | /O(1)/ Returns all but the last character of a 'Text', which must
--- be non-empty.  Subject to array fusion.
+-- be non-empty.  Subject to fusion.
 init :: Text -> Text
 init (Text arr off len) | len <= 0                   = emptyError "init"
                         | n >= 0xDC00 && n <= 0xDFFF = textP arr off (len-2)
@@ -342,7 +355,7 @@
     unstream (S.init (stream t)) = init t
  #-}
 
--- | /O(1)/ Tests whether a 'Text' is empty or not.  Subject to array
+-- | /O(1)/ Tests whether a 'Text' is empty or not.  Subject to
 -- fusion.
 null :: Text -> Bool
 null (Text _arr _off len) = assert (len >= 0) $ len <= 0
@@ -356,15 +369,15 @@
  #-}
 
 -- | /O(n)/ Returns the number of characters in a 'Text'.
--- Subject to array fusion.
+-- Subject to fusion.
 length :: Text -> Int
 length t = S.length (stream t)
 {-# INLINE length #-}
 
 -- -----------------------------------------------------------------------------
 -- * Transformations
--- | /O(n)/ 'map' @f @xs is the 'Text' obtained by applying @f@ to
--- each element of @xs@.  Subject to array fusion.
+-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
+-- each element of @t@.  Subject to fusion.
 map :: (Char -> Char) -> Text -> Text
 map f t = unstream (S.map f (stream t))
 {-# INLINE [1] map #-}
@@ -377,31 +390,41 @@
 {-# INLINE intercalate #-}
 
 -- | /O(n)/ The 'intersperse' function takes a character and places it
--- between the characters of a 'Text'.  Subject to array fusion.
+-- between the characters of a 'Text'.  Subject to fusion.
 intersperse     :: Char -> Text -> Text
 intersperse c t = unstream (S.intersperse c (stream t))
 {-# INLINE intersperse #-}
 
--- | /O(n)/ Reverse the characters of a string. Subject to array fusion.
+-- | /O(n)/ Reverse the characters of a string. Subject to fusion.
 reverse :: Text -> Text
 reverse t = S.reverse (stream t)
 {-# INLINE reverse #-}
 
+-- | /O(m)*O(n)/ Replace every occurrence of one substring with another.
+replace :: Text                 -- ^ Text to search for
+        -> Text                 -- ^ Replacement text
+        -> Text                 -- ^ Input text
+        -> Text
+replace s d = intercalate d . split s
+{-# INLINE replace #-}
+
 -- ----------------------------------------------------------------------------
 -- ** Case conversions (folds)
 
 -- $case
 --
--- With Unicode text, it is incorrect to use combinators like @map
--- toUpper@ to case convert each character of a string individually.
--- Instead, use the whole-string case conversion functions from this
--- module.  For correctness in different writing systems, these
--- functions may map one input character to two or three output
--- characters.
+-- When case converting 'Text' values, do not use combinators like
+-- @map toUpper@ to case convert each character of a string
+-- individually, as this gives incorrect results according to the
+-- rules of some writing systems.  The whole-string case conversion
+-- functions from this module, such as @toUpper@, obey the correct
+-- case conversion rules.  As a result, these functions may map one
+-- input character to two or three output characters. For examples,
+-- see the documentation of each function.
 
 -- | /O(n)/ Convert a string to folded case.  This function is mainly
--- useful for performing caseless (or case insensitive) string
--- comparisons.
+-- useful for performing caseless (also known as case insensitive)
+-- string comparisons.
 --
 -- A string @x@ is a caseless match for a string @y@ if and only if:
 --
@@ -409,31 +432,77 @@
 --
 -- The result string may be longer than the input string, and may
 -- differ from applying 'toLower' to the input string.  For instance,
--- the Armenian small ligature men now (U+FB13) is case folded to the
--- bigram men now (U+0574 U+0576), while the micro sign (U+00B5) is
--- case folded to the Greek small letter letter mu (U+03BC) instead of
--- itself.
+-- the Armenian small ligature \"&#xfb13;\" (men now, U+FB13) is case
+-- folded to the sequence \"&#x574;\" (men, U+0574) followed by
+-- \"&#x576;\" (now, U+0576), while the Greek \"&#xb5;\" (micro sign,
+-- U+00B5) is case folded to \"&#x3bc;\" (small letter mu, U+03BC)
+-- instead of itself.
 toCaseFold :: Text -> Text
 toCaseFold t = unstream (S.toCaseFold (stream t))
 {-# INLINE [0] toCaseFold #-}
 
 -- | /O(n)/ Convert a string to lower case, using simple case
 -- conversion.  The result string may be longer than the input string.
--- For instance, the Latin capital letter I with dot above (U+0130)
--- maps to the sequence Latin small letter i (U+0069) followed by
--- combining dot above (U+0307).
+-- For instance, \"&#x130;\" (Latin capital letter I with dot above,
+-- U+0130) maps to the sequence \"i\" (Latin small letter i, U+0069) followed
+-- by \" &#x307;\" (combining dot above, U+0307).
 toLower :: Text -> Text
 toLower t = unstream (S.toLower (stream t))
 {-# INLINE toLower #-}
 
 -- | /O(n)/ Convert a string to upper case, using simple case
 -- conversion.  The result string may be longer than the input string.
--- For instance, the German eszett (U+00DF) maps to the two-letter
--- sequence SS.
+-- For instance, the German \"&#xdf;\" (eszett, U+00DF) maps to the
+-- two-letter sequence \"SS\".
 toUpper :: Text -> Text
 toUpper t = unstream (S.toUpper (stream t))
 {-# INLINE toUpper #-}
 
+-- | /O(n)/ Left-justify a string to the given length, using the
+-- specified fill character on the right. Subject to fusion. Examples:
+--
+-- > justifyLeft 7 'x' "foo"    == "fooxxxx"
+-- > justifyLeft 3 'x' "foobar" == "foobar"
+justifyLeft :: Int -> Char -> Text -> Text
+justifyLeft k c t
+    | len >= k  = t
+    | otherwise = t `append` replicateChar (k-len) c
+  where len = length t
+{-# INLINE [1] justifyLeft #-}
+
+{-# RULES
+"TEXT justifyLeft -> fused" [~1] forall k c t.
+    justifyLeft k c t = unstream (S.justifyLeftI k c (stream t))
+"TEXT justifyLeft -> unfused" [1] forall k c t.
+    unstream (S.justifyLeftI k c (stream t)) = justifyLeft k c t
+  #-}
+
+-- | /O(n)/ Right-justify a string to the given length, using the
+-- specified fill character on the left. Examples:
+--
+-- > justifyRight 7 'x' "bar"    == "xxxxbar"
+-- > justifyRight 3 'x' "foobar" == "foobar"
+justifyRight :: Int -> Char -> Text -> Text
+justifyRight k c t
+    | len >= k  = t
+    | otherwise = replicateChar (k-len) c `append` t
+  where len = length t
+{-# INLINE justifyRight #-}
+
+-- | /O(n)/ Center a string to the given length, using the
+-- specified fill character on either side. Examples:
+--
+-- > center 8 'x' "HS" = "xxxHSxxx"
+center :: Int -> Char -> Text -> Text
+center k c t
+    | len >= k  = t
+    | otherwise = replicateChar l c `append` t `append` replicateChar r c
+  where len = length t
+        d   = k - len
+        r   = d `div` 2
+        l   = d - r
+{-# INLINE center #-}
+
 -- | /O(n)/ The 'transpose' function transposes the rows and columns
 -- of its 'Text' argument.  Note that this function uses 'pack',
 -- 'unpack', and the list version of transpose, and is thus not very
@@ -447,26 +516,23 @@
 -- | /O(n)/ 'foldl', applied to a binary operator, a starting value
 -- (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.
+-- Subject to fusion.
 foldl :: (b -> Char -> b) -> b -> Text -> b
 foldl f z t = S.foldl f z (stream t)
 {-# INLINE foldl #-}
 
--- | /O(n)/ A strict version of 'foldl'.
--- Subject to array fusion.
+-- | /O(n)/ A strict version of 'foldl'.  Subject to fusion.
 foldl' :: (b -> Char -> b) -> b -> Text -> b
 foldl' f z t = S.foldl' f z (stream t)
 {-# INLINE foldl' #-}
 
 -- | /O(n)/ A variant of 'foldl' that has no starting value argument,
--- and thus must be applied to a non-empty 'Text'.  Subject to array
--- fusion.
+-- and thus must be applied to a non-empty 'Text'.  Subject to fusion.
 foldl1 :: (Char -> Char -> Char) -> Text -> Char
 foldl1 f t = S.foldl1 f (stream t)
 {-# INLINE foldl1 #-}
 
--- | /O(n)/ A strict version of 'foldl1'.
--- Subject to array fusion.
+-- | /O(n)/ A strict version of 'foldl1'.  Subject to fusion.
 foldl1' :: (Char -> Char -> Char) -> Text -> Char
 foldl1' f t = S.foldl1' f (stream t)
 {-# INLINE foldl1' #-}
@@ -474,13 +540,13 @@
 -- | /O(n)/ 'foldr', applied to a binary operator, a starting value
 -- (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.
+-- Subject to fusion.
 foldr :: (Char -> b -> b) -> b -> Text -> b
 foldr f z t = S.foldr f z (stream t)
 {-# INLINE foldr #-}
 
--- | /O(n)/ A variant of 'foldr' that has no starting value argument, and
--- thust must be applied to a non-empty 'Text'.  Subject to array
+-- | /O(n)/ A variant of 'foldr' that has no starting value argument,
+-- and thust must be applied to a non-empty 'Text'.  Subject to
 -- fusion.
 foldr1 :: (Char -> Char -> Char) -> Text -> Char
 foldr1 f t = S.foldr1 f (stream t)
@@ -489,13 +555,13 @@
 -- -----------------------------------------------------------------------------
 -- ** Special folds
 
--- | /O(n)/ Concatenate a list of 'Text's. Subject to array fusion.
+-- | /O(n)/ Concatenate a list of 'Text's. Subject to fusion.
 concat :: [Text] -> Text
 concat ts = unstream (S.concat (L.map stream ts))
 {-# INLINE concat #-}
 
 -- | /O(n)/ Map a function over a 'Text' that results in a 'Text', and
--- concatenate the results.  This function is subject to array fusion.
+-- concatenate the results.  Subject to fusion.
 --
 -- Note: if in 'concatMap' @f@ @t@, @f@ is defined in terms of fusible
 -- functions, it will also be fusible.
@@ -504,25 +570,25 @@
 {-# INLINE concatMap #-}
 
 -- | /O(n)/ 'any' @p@ @t@ determines whether any character in the
--- 'Text' @t@ satisifes the predicate @p@. Subject to array fusion.
+-- 'Text' @t@ satisifes the predicate @p@. Subject to fusion.
 any :: (Char -> Bool) -> Text -> Bool
 any p t = S.any p (stream t)
 {-# INLINE any #-}
 
 -- | /O(n)/ 'all' @p@ @t@ determines whether all characters in the
--- 'Text' @t@ satisify the predicate @p@. Subject to array fusion.
+-- 'Text' @t@ satisify the predicate @p@. Subject to fusion.
 all :: (Char -> Bool) -> Text -> Bool
 all p t = S.all p (stream t)
 {-# INLINE all #-}
 
 -- | /O(n)/ 'maximum' returns the maximum value from a 'Text', which
--- must be non-empty. Subject to array fusion.
+-- must be non-empty. Subject to fusion.
 maximum :: Text -> Char
 maximum t = S.maximum (stream t)
 {-# INLINE maximum #-}
 
 -- | /O(n)/ 'minimum' returns the minimum value from a 'Text', which
--- must be non-empty. Subject to array fusion.
+-- must be non-empty. Subject to fusion.
 minimum :: Text -> Char
 minimum t = S.minimum (stream t)
 {-# INLINE minimum #-}
@@ -531,8 +597,7 @@
 -- * Building 'Text's
 
 -- | /O(n)/ 'scanl' is similar to 'foldl', but returns a list of
--- successive reduced values from the left. This function is subject
--- to array fusion.
+-- successive reduced values from the left. Subject to fusion.
 --
 -- > scanl f z [x1, x2, ...] == [z, z `f` x1, (z `f` x1) `f` x2, ...]
 --
@@ -544,7 +609,7 @@
 {-# INLINE scanl #-}
 
 -- | /O(n)/ 'scanl1' is a variant of 'scanl' that has no starting
--- value argument.  This function is subject to array fusion.
+-- value argument.  Subject to fusion.
 --
 -- > scanl1 f [x1, x2, ...] == [x1, x1 `f` x2, ...]
 scanl1 :: (Char -> Char -> Char) -> Text -> Text
@@ -560,7 +625,7 @@
 {-# INLINE scanr #-}
 
 -- | /O(n)/ 'scanr1' is a variant of 'scanr' that has no starting
--- value argument.  This function is subject to array fusion.
+-- value argument.  Subject to fusion.
 scanr1 :: (Char -> Char -> Char) -> Text -> Text
 scanr1 f t | null t    = empty
            | otherwise = scanr f (last t) (init t)
@@ -590,11 +655,21 @@
 -- -----------------------------------------------------------------------------
 -- ** Generating and unfolding 'Text's
 
--- | /O(n)/ 'replicate' @n@ @c@ is a 'Text' of length @n@ with @c@ the
+-- | /O(n*m)/ 'replicate' @n@ @t@ is a 'Text' consisting of the input
+-- @t@ repeated @n@ times. Subject to fusion.
+replicate :: Int -> Text -> Text
+replicate n t = unstream (S.replicateI (fromIntegral n) (S.stream t))
+{-# INLINE [1] replicate #-}
+
+{-# RULES
+"TEXT replicate/singleton -> replicateChar" [~1] forall n c.
+    replicate n (singleton c) = replicateChar n c
+  #-}
+
+-- | /O(n)/ 'replicateChar' @n@ @c@ is a 'Text' of length @n@ with @c@ the
 -- value of every element. Subject to fusion.
-replicate :: Int -> Char -> Text
-replicate n c = unstream (S.replicate n c)
-{-# INLINE replicate #-}
+replicateChar :: Int -> Char -> Text
+replicateChar n c = unstream (S.replicateCharI n c)
 
 -- | /O(n)/, where @n@ is the length of the result. The 'unfoldr'
 -- function is analogous to the List 'L.unfoldr'. 'unfoldr' builds a
@@ -663,9 +738,9 @@
     unstream (S.drop n (stream t)) = drop n t
   #-}
 
--- | /O(n)/ 'takeWhile', applied to a predicate @p@ and a 'Text', returns
--- the longest prefix (possibly empty) of elements that satisfy @p@.
--- This function is subject to array fusion.
+-- | /O(n)/ 'takeWhile', applied to a predicate @p@ and a 'Text',
+-- returns the longest prefix (possibly empty) of elements that
+-- satisfy @p@.  Subject to fusion.
 takeWhile :: (Char -> Bool) -> Text -> Text
 takeWhile p t@(Text arr off len) = loop 0
   where loop !i | i >= len    = t
@@ -681,8 +756,8 @@
     unstream (S.takeWhile p (stream t)) = takeWhile p t
   #-}
 
--- | /O(n)/ 'dropWhile' @p@ @xs@ returns the suffix remaining after
--- 'takeWhile' @p@ @xs@. This function is subject to array fusion.
+-- | /O(n)/ 'dropWhile' @p@ @t@ returns the suffix remaining after
+-- 'takeWhile' @p@ @t@. Subject to fusion.
 dropWhile :: (Char -> Bool) -> Text -> Text
 dropWhile p t@(Text arr off len) = loop 0 0
   where loop !i !l | l >= len  = empty
@@ -698,6 +773,56 @@
     unstream (S.dropWhile p (stream t)) = dropWhile p t
   #-}
 
+-- | /O(n)/ 'dropWhileEnd' @p@ @t@ returns the prefix remaining after
+-- dropping characters that fail the predicate @p@ from the end of
+-- @t@.  Subject to fusion.
+-- Examples:
+--
+-- > dropWhileEnd (=='.') "foo..." == "foo"
+dropWhileEnd :: (Char -> Bool) -> Text -> Text
+dropWhileEnd p t@(Text arr off len) = loop (len-1) len
+  where loop !i !l | l <= 0    = empty
+                   | p c       = loop (i+d) (l+d)
+                   | otherwise = Text arr off l
+            where (c,d)        = reverseIter t i
+{-# INLINE [1] dropWhileEnd #-}
+
+{-# RULES
+"TEXT dropWhileEnd -> fused" [~1] forall p t.
+    dropWhileEnd p t = S.reverse (S.dropWhile p (S.reverseStream t))
+"TEXT dropWhileEnd -> unfused" [1] forall p t.
+    S.reverse (S.dropWhile p (S.reverseStream t)) = dropWhileEnd p t
+  #-}
+
+-- | /O(n)/ 'dropAround' @p@ @t@ returns the substring remaining after
+-- dropping characters that fail the predicate @p@ from both the
+-- beginning and end of @t@.  Subject to fusion.
+dropAround :: (Char -> Bool) -> Text -> Text
+dropAround p = dropWhile p . dropWhileEnd p
+{-# INLINE [1] dropAround #-}
+
+-- | /O(n)/ Remove leading white space from a string.  Equivalent to:
+--
+-- > dropWhile isSpace
+stripStart :: Text -> Text
+stripStart = dropWhile isSpace
+{-# INLINE [1] stripStart #-}
+
+-- | /O(n)/ Remove trailing white space from a string.  Equivalent to:
+--
+-- > dropWhileEnd isSpace
+stripEnd :: Text -> Text
+stripEnd = dropWhileEnd isSpace
+{-# INLINE [1] stripEnd #-}
+
+-- | /O(n)/ Remove leading and trailing white space from a string.
+-- Equivalent to:
+--
+-- > dropAround isSpace
+strip :: Text -> Text
+strip = dropAround isSpace
+{-# INLINE [1] strip #-}
+
 -- | /O(n)/ 'splitAt' @n t@ returns a pair whose first element is a
 -- prefix of @t@ of length @n@, and whose second is the remainder of
 -- the string. It is equivalent to @('take' n t, 'drop' n t)@.
@@ -767,40 +892,128 @@
 tails t | null t    = [empty]
         | otherwise = t : tails (unsafeTail t)
 
--- | /O(n)/ Break a 'Text' into pieces separated by the 'Char'
--- argument, consuming the delimiter. I.e.
+-- $split
 --
--- > split '\n' "a\nb\nd\ne" == ["a","b","d","e"]
--- > split 'a'  "aXaXaXa"    == ["","X","X","X",""]
--- > split 'x'  "x"          == ["",""]
+-- Splitting functions in this library do not perform character-wise
+-- copies to create substrings; they just construct new 'Text's that
+-- are slices of the original.
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces separated by the first
+-- 'Text' argument, consuming the delimiter. An empty delimiter is
+-- invalid, and will cause an error to be raised.
+--
+-- Examples:
+--
+-- > split "\r\n" "a\r\nb\r\nd\r\ne" == ["a","b","d","e"]
+-- > split "aaa"  "aaaXaaaXaaaXaaa"  == ["","X","X","X",""]
+-- > split "x"    "x"                == ["",""]
 -- 
 -- and
 --
--- > intercalate (singleton c) . split c == id
--- > split == splitWith . (==)
--- 
--- As for all splitting functions in this library, this function does
--- not copy the substrings, it just constructs new 'Text's that are
--- slices of the original.
-split :: Char -> Text -> [Text]
-split c = splitWith (==c)
-{-# INLINE split #-}
+-- > intercalate s . split s         == id
+-- > split (singleton c)             == splitWith (==c)
+split :: Text                   -- ^ Text to split on
+      -> Text                   -- ^ Input text
+      -> [Text]
+split pat src0
+    | l == 0    = emptyError "split"
+    | l == 1    = splitWith (== (unsafeHead pat)) src0
+    | otherwise = go src0
+  where
+    l      = length pat
+    go src = search 0 src
+      where
+        search !n !s
+            | null s             = [src]      -- not found
+            | pat `isPrefixOf` s = take n src : go (drop l s)
+            | otherwise          = search (n+1) (unsafeTail s)
+{-# INLINE [1] split #-}
 
+{-# RULES
+"TEXT split/singleton -> splitWith/==" [~1] forall c t.
+    split (singleton c) t = splitWith (==c) t
+  #-}
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times,
+-- treating the first 'Text' argument as the delimiter to break on,
+-- and consuming the delimiter.  The last element of the list contains
+-- the remaining text after the number of times to split has been
+-- reached.  A value of zero or less for @k@ causes no splitting to
+-- occur. An empty delimiter is invalid, and will cause an error to be
+-- raised.
+--
+-- Examples:
+--
+-- > splitTimes 0   "//"  "a//b//c"   == ["a//b//c"]
+-- > splitTimes 2   ":"   "a:b:c:d:e" == ["a","b","c:d:e"]
+-- > splitTimes 100 "???" "a????b"    == ["a","?b"]
+--
+-- and
+--
+-- > intercalate s . splitTimes k s   == id
+splitTimes :: Int               -- ^ Maximum number of times to split
+           -> Text              -- ^ Text to split on
+           -> Text              -- ^ Input text
+           -> [Text]
+splitTimes k pat src0
+    | k <= 0    = [src0]
+    | l == 0    = emptyError "splitTimes"
+    | otherwise = go k src0
+  where
+    l         = length pat
+    go !i src = search 0 src
+      where
+        search !n !s
+            | i == 0 || null s   = [src]      -- not found or limit reached
+            | pat `isPrefixOf` s = take n src : go (i-1) (drop l s)
+            | otherwise          = search (n+1) (unsafeTail s)
+{-# INLINE splitTimes #-}
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times, like
+-- 'splitTimes', but start from the end of the input and work towards
+-- the start.
+--
+-- Examples:
+--
+-- > splitTimes 2    "::" "a::b::c::d::e" == ["a","b","c::d::e"]
+-- > splitTimesEnd 2 "::" "a::b::c::d::e" == ["a::b::c","d","e"]
+splitTimesEnd :: Int               -- ^ Maximum number of times to split
+              -> Text              -- ^ Text to split on
+              -> Text              -- ^ Input text
+              -> [Text]
+splitTimesEnd k pat src =
+    L.reverse . L.map reverse $ splitTimes k (reverse pat) (reverse src)
+{-# INLINE splitTimesEnd #-}
+
 -- | /O(n)/ Splits a 'Text' into components delimited by separators,
 -- where the predicate returns True for a separator element.  The
 -- resulting components do not contain the separators.  Two adjacent
 -- separators result in an empty component in the output.  eg.
 --
 -- > splitWith (=='a') "aabbaca" == ["","","bb","c",""]
--- > splitWith (=='a') []        == []
+-- > splitWith (=='a') ""        == [""]
 splitWith :: (Char -> Bool) -> Text -> [Text]
-splitWith _ (Text _off _arr 0) = []
+splitWith _ t@(Text _off _arr 0) = [t]
 splitWith p t = loop t
     where loop s | null s'   = [l]
                  | otherwise = l : loop (unsafeTail s')
               where (l, s') = break p s
 {-# INLINE splitWith #-}
 
+-- | /O(n)/ Splits a 'Text' into components of length @k@.  The last
+-- element may be shorter than the other chunks, depending on the
+-- length of the input. Examples:
+--
+-- > chunksOf 3 "foobarbaz"   == ["foo","bar","baz"]
+-- > chunksOf 4 "haskell.org" == ["hask","ell.","org"]
+chunksOf :: Int -> Text -> [Text]
+chunksOf k = go
+  where
+    go t = case splitAt k t of
+             (a,b) | null a    -> []
+                   | otherwise -> a : go b
+{-# INLINE chunksOf #-}
+
 -- ----------------------------------------------------------------------------
 -- * Searching
 
@@ -831,38 +1044,6 @@
 partition p t = (filter p t, filter (not . p) t)
 {-# INLINE partition #-}
 
--- | /O(n)/ Break a string on a substring, returning a pair of the
--- part of the string prior to the match, and the rest of the string.
---
--- The following relationship holds:
---
--- > break (==c) l == breakSubstring (singleton c) l
---
--- For example, to tokenise a string, dropping delimiters:
---
--- > tokenise x y = h : if null t then [] else tokenise x (drop (length x) t)
--- >     where (h,t) = breakSubstring x y
---
--- To skip to the first occurence of a string:
---
--- > snd (breakSubstring x y)
---
--- To take the parts of a string before a delimiter:
---
--- > fst (breakSubstring x y)
---
-breakSubstring :: Text -- ^ String to search for
-               -> Text -- ^ String to search in
-               -> (Text,Text) -- ^ Head and tail of string broken at substring
-
-breakSubstring pat src = search 0 src
-  where
-    search !n !s
-        | null s             = (src,empty)      -- not found
-        | pat `isPrefixOf` s = (take n src,s)
-        | otherwise          = search (n+1) (unsafeTail s)
-{-# INLINE breakSubstring #-}
-
 -- | /O(n)/ 'filter', applied to a predicate and a 'Text',
 -- returns a 'Text' containing those characters that satisfy the
 -- predicate.
@@ -874,6 +1055,23 @@
 -------------------------------------------------------------------------------
 -- ** Indexing 'Text's
 
+-- $index
+--
+-- If you think of a 'Text' value as an array of 'Char' values (which
+-- it is not), you run the risk of writing inefficient code.
+--
+-- An idiom that is common in some languages is to find the numeric
+-- offset of a character or substring, then use that number to split
+-- or trim the searched string.  With a 'Text' value, this approach
+-- would require two /O(n)/ operations: one to perform the search, and
+-- one to operate from wherever the search ended.
+--
+-- For example, suppose you have a string that you want to split on
+-- the substring @\"::\"@, such as @\"foo::bar::quux\"@. Instead of
+-- searching for the index of @\"::\"@ and taking the substrings
+-- before and after that index, you would instead use @splitTimes 1
+-- "::"@.
+
 -- | /O(n)/ 'Text' index (subscript) operator, starting from 0.
 index :: Text -> Int -> Char
 index t n = S.index (stream t) n
@@ -881,39 +1079,59 @@
 
 -- | /O(n)/ The 'findIndex' function takes a predicate and a 'Text'
 -- and returns the index of the first element in the 'Text' satisfying
--- the predicate. This function is subject to fusion.
+-- the predicate. Subject to fusion.
 findIndex :: (Char -> Bool) -> Text -> Maybe Int
 findIndex p t = S.findIndex p (stream t)
 {-# INLINE findIndex #-}
 
 -- | The 'findIndices' function extends 'findIndex', by returning the
 -- indices of all elements satisfying the predicate, in ascending
--- order. This function is subject to fusion.
+-- order. Subject to fusion.
 findIndices :: (Char -> Bool) -> Text -> [Int]
 findIndices p t = S.findIndices p (stream t)
 {-# INLINE findIndices #-}
 
 -- | /O(n)/ The 'elemIndex' function returns the index of the first
 -- element in the given 'Text' which is equal to the query element, or
--- 'Nothing' if there is no such element. This function is subject to
--- fusion.
+-- 'Nothing' if there is no such element. Subject to fusion.
 elemIndex :: Char -> Text -> Maybe Int
 elemIndex c t = S.elemIndex c (stream t)
 {-# INLINE elemIndex #-}
 
 -- | /O(n)/ The 'elemIndices' function returns the index of every
 -- element in the given 'Text' which is equal to the query
--- element. This function is subject to fusion.
+-- element. Subject to fusion.
 elemIndices :: Char -> Text -> [Int]
 elemIndices c t = S.elemIndices c (stream t)
 {-# INLINE elemIndices #-}
 
--- | /O(n)/ The 'count' function returns the number of times the query
--- element appears in the given 'Text'. This function is subject to
--- fusion.
-count :: Char -> Text -> Int
-count c t = S.count c (stream t)
-{-# INLINE count #-}
+-- | /O(n*m)/ The 'count' function returns the number of times the
+-- query string appears in the given 'Text'. An empty query string is
+-- invalid, and will cause an error to be raised.
+count :: Text -> Text -> Int
+count pat src0
+    | l == 0    = emptyError "count"
+    | l == 1    = countChar (unsafeHead pat) src0
+    | otherwise = go 0 src0
+  where
+    l = length pat
+    go !n src = search src
+      where
+        search s | null s             = n
+                 | pat `isPrefixOf` s = go (n+1) (drop l s)
+                 | otherwise          = search (unsafeTail s)
+{-# INLINE [1] count #-}
+
+{-# RULES
+"TEXT count/singleton -> countChar" [~1] forall c t.
+    count (singleton c) t = countChar c t
+  #-}
+
+-- | /O(n)/ The 'countChar' function returns the number of times the
+-- query element appears in the given 'Text'. Subject to fusion.
+countChar :: Char -> Text -> Int
+countChar c t = S.countChar c (stream t)
+{-# INLINE countChar #-}
 
 -------------------------------------------------------------------------------
 -- * Zipping
diff --git a/Data/Text/Array.hs b/Data/Text/Array.hs
--- a/Data/Text/Array.hs
+++ b/Data/Text/Array.hs
@@ -166,9 +166,6 @@
 
 #if defined(__GLASGOW_HASKELL__)
 
-iNT_SCALE :: Int# -> Int#
-iNT_SCALE n# = scale# *# n# where I# scale# = SIZEOF_INT
-
 wORD16_SCALE :: Int# -> Int#
 wORD16_SCALE n# = scale# *# n# where I# scale# = SIZEOF_WORD16
 
@@ -227,38 +224,6 @@
   marr <- unsafeNew len
   sequence_ [unsafeWrite marr i initVal | i <- [0..len-1]]
   return marr
-
-instance Elt Int where
-#if defined(__GLASGOW_HASKELL__)
-
-    bytesInArray (I# i#) _ = I# (iNT_SCALE i#)
-    {-# INLINE bytesInArray #-}
-
-    unsafeIndex (Array len ba#) i@(I# i#) =
-      CHECK_BOUNDS("unsafeIndex",len,i)
-        case indexIntArray# ba# i# of r# -> (I# r#)
-    {-# INLINE unsafeIndex #-}
-
-    unsafeRead (MArray len mba#) i@(I# i#) = ST $ \s# ->
-      CHECK_BOUNDS("unsafeRead",len,i)
-      case readIntArray# mba# i# s# of
-        (# s2#, r# #) -> (# s2#, I# r# #)
-    {-# INLINE unsafeRead #-}
-
-    unsafeWrite (MArray len marr#) i@(I# i#) (I# e#) = ST $ \s1# ->
-      CHECK_BOUNDS("unsafeWrite",len,i)
-      case writeIntArray# marr# i# e# s1# of
-        s2# -> (# s2#, () #)
-    {-# INLINE unsafeWrite #-}
-
-#elif defined(__HUGS__)
-
-    bytesInArray n w = sizeOf w * n
-    unsafeIndex = unsafeIndexArray
-    unsafeRead = unsafeReadMArray
-    unsafeWrite = unsafeWriteMArray
-
-#endif
 
 instance Elt Word16 where
 #if defined(__GLASGOW_HASKELL__)
diff --git a/Data/Text/Fusion.hs b/Data/Text/Fusion.hs
--- a/Data/Text/Fusion.hs
+++ b/Data/Text/Fusion.hs
@@ -44,7 +44,7 @@
     , findIndexOrEnd
     , elemIndex
     , elemIndices
-    , count
+    , countChar
     ) where
 
 import Prelude (Bool(..), Char, Eq(..), Maybe(..), Monad(..), Int,
@@ -229,6 +229,6 @@
 
 -- | /O(n)/ The 'count' function returns the number of times the query
 -- element appears in the given stream.
-count :: Char -> Stream Char -> Int
-count = S.countI
-{-# INLINE [0] count #-}
+countChar :: Char -> Stream Char -> Int
+countChar = S.countCharI
+{-# INLINE [0] countChar #-}
diff --git a/Data/Text/Fusion/Common.hs b/Data/Text/Fusion/Common.hs
--- a/Data/Text/Fusion/Common.hs
+++ b/Data/Text/Fusion/Common.hs
@@ -41,6 +41,9 @@
     , toLower
     , toUpper
 
+    -- ** Justification
+    , justifyLeftI
+
     -- * Folds
     , foldl
     , foldl'
@@ -62,10 +65,11 @@
     , scanl
 
     -- ** Accumulating maps
-    , mapAccumL
+    -- , mapAccumL
 
     -- ** Generation and unfolding
-    , replicate
+    , replicateCharI
+    , replicateI
     , unfoldr
     , unfoldrNI
 
@@ -90,7 +94,7 @@
     , findIndicesI
     , elemIndexI
     , elemIndicesI
-    , countI
+    , countCharI
 
     -- * Zipping and unzipping
     , zipWith
@@ -101,6 +105,7 @@
                 fromIntegral, otherwise)
 import qualified Data.List as L
 import qualified Prelude as P
+import Data.Int (Int64)
 import Data.Text.Fusion.Internal
 import Data.Text.Fusion.CaseMapping (foldMapping, lowerMapping, upperMapping)
 
@@ -358,6 +363,23 @@
 toLower = caseConvert lowerMapping
 {-# INLINE [0] toLower #-}
 
+justifyLeftI :: Integral a => a -> Char -> Stream Char -> Stream Char
+justifyLeftI k c (Stream next0 s0 len) = Stream next (s0 :!: S1 :!: 0) newLen
+  where
+    j = fromIntegral k
+    newLen | j > len   = j
+           | otherwise = len
+    next (s :!: S1 :!: n) =
+        case next0 s of
+          Done       -> next (s :!: S2 :!: n)
+          Skip s'    -> Skip (s' :!: S1 :!: n)
+          Yield x s' -> Yield x (s' :!: S1 :!: n+1)
+    next (s :!: S2 :!: n)
+        | n < k       = Yield c (s :!: S2 :!: n+1)
+        | otherwise   = Done
+    {-# INLINE next #-}
+{-# INLINE [0] justifyLeftI #-}
+
 -- ----------------------------------------------------------------------------
 -- * Reducing Streams (folds)
 
@@ -533,6 +555,7 @@
 -- -----------------------------------------------------------------------------
 -- ** Accumulating maps
 
+{-
 -- | /O(n)/ Like a combination of 'map' and 'foldl'. Applies a
 -- function to each element of a stream, passing an accumulating
 -- parameter from left to right, and returns a final stream.
@@ -550,20 +573,33 @@
                        Skip s'    -> Skip (s' :!: z)
                        Done       -> Done
 {-# INLINE [0] mapAccumL #-}
+-}
 
 -- -----------------------------------------------------------------------------
 -- ** Generating and unfolding streams
 
-replicate :: Int -> Char -> Stream Char
-replicate n c
+replicateCharI :: Integral a => a -> Char -> Stream Char
+replicateCharI n c
     | n < 0     = empty
-    | otherwise = Stream next 0 n -- HINT maybe too low
+    | otherwise = Stream next 0 (fromIntegral n) -- HINT maybe too low
   where
     {-# INLINE next #-}
     next i | i >= n    = Done
            | otherwise = Yield c (i + 1)
-{-# INLINE [0] replicate #-}
+{-# INLINE [0] replicateCharI #-}
 
+replicateI :: Int64 -> Stream Char -> Stream Char
+replicateI n (Stream next0 s0 len) =
+    Stream next (0 :!: s0) (max 0 (fromIntegral n * len))
+  where
+    next (k :!: s)
+        | k >= n = Done
+        | otherwise = case next0 s of
+                        Done       -> Skip    (k+1 :!: s0)
+                        Skip s'    -> Skip    (k :!: s')
+                        Yield x s' -> Yield x (k :!: s')
+{-# INLINE [0] replicateI #-}
+
 -- | /O(n)/, where @n@ is the length of the result. The unfoldr function
 -- is analogous to the List 'unfoldr'. unfoldr builds a stream
 -- from a seed value. The function takes the element and returns
@@ -804,17 +840,17 @@
                  | otherwise -> loop (i+1) s'
 {-# INLINE [0] elemIndicesI #-}
 
--- | /O(n)/ The 'count' function returns the number of times the query
--- element appears in the given stream.
-countI :: Integral a => Char -> Stream Char -> a
-countI a (Stream next s0 _len) = loop 0 s0
+-- | /O(n)/ The 'countCharI' function returns the number of times the
+-- query element appears in the given stream.
+countCharI :: Integral a => Char -> Stream Char -> a
+countCharI a (Stream next s0 _len) = loop 0 s0
   where
     loop !i !s = case next s of
       Done                   -> i
       Skip    s'             -> loop i s'
       Yield x s' | a == x    -> loop (i+1) s'
                  | otherwise -> loop i s'
-{-# INLINE [0] countI #-}
+{-# INLINE [0] countCharI #-}
 
 streamError :: String -> String -> a
 streamError func msg = P.error $ "Data.Text.Fusion.Common." ++ func ++ ": " ++ msg
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
@@ -30,31 +30,29 @@
 -- | Specialised, strict Maybe-like type.
 data M a = N
          | J {-# UNPACK #-} !a
-           deriving (Eq, Ord, Show)
 
 type M8 = M Word8
 
 -- Restreaming state.
 data S s = S {-# UNPACK #-} !s
     {-# UNPACK #-} !M8 {-# UNPACK #-} !M8 {-# UNPACK #-} !M8
-           deriving (Eq, Ord, Show)
 
 infixl 2 :!:
 data PairS a b = !a :!: !b
-               deriving (Eq, Ord, Read, Show)
 
 -- | Allow a function over a stream to switch between two states.
 data Switch = S1 | S2
-            deriving (Eq, Ord, Show)
 
 data Step s a = Done
               | 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/Internal.hs b/Data/Text/Internal.hs
--- a/Data/Text/Internal.hs
+++ b/Data/Text/Internal.hs
@@ -45,7 +45,7 @@
 text arr off len =
     assert (len >= 0) .
     assert (off >= 0) .
-    assert (alen == 0 || off < alen) .
+    assert (alen == 0 || len == 0 || off < alen) .
     assert (len == 0 || c < 0xDC00 || c > 0xDFFF) $
     Text arr off len
   where c    = A.unsafeIndex arr off
diff --git a/Data/Text/Lazy.hs b/Data/Text/Lazy.hs
--- a/Data/Text/Lazy.hs
+++ b/Data/Text/Lazy.hs
@@ -1,4 +1,5 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE BangPatterns #-}
 -- |
 -- Module      : Data.Text.Lazy
 -- Copyright   : (c) Bryan O'Sullivan 2009
@@ -57,6 +58,7 @@
     , intersperse
     , transpose
     , reverse
+    , replace
 
     -- ** Case conversion
     -- $case
@@ -64,6 +66,11 @@
     , toLower
     , toUpper
 
+    -- ** Justification
+    , justifyLeft
+    , justifyRight
+    , center
+
     -- * Folds
     , foldl
     , foldl'
@@ -94,6 +101,7 @@
 
     -- ** Generation and unfolding
     , replicate
+    , replicateChar
     , unfoldr
     , unfoldrN
 
@@ -104,6 +112,11 @@
     , drop
     , takeWhile
     , dropWhile
+    , dropWhileEnd
+    , dropAround
+    , strip
+    , stripStart
+    , stripEnd
     , splitAt
     , span
     , break
@@ -113,8 +126,12 @@
     , tails
 
     -- ** Breaking into many substrings
+    -- $split
     , split
+    , splitTimes
+    , splitTimesEnd
     , splitWith
+    , chunksOf
     -- , breakSubstring
 
     -- ** Breaking into lines and words
@@ -154,12 +171,13 @@
 
 import Prelude (Char, Bool(..), Int, Maybe(..), String,
                 Eq(..), Ord(..), Read(..), Show(..),
-                (&&), (+), (-), (.), ($), (++),
-                flip, fromIntegral, not, otherwise)
+                (&&), (||), (+), (-), (.), ($), (++),
+                div, flip, fromIntegral, not, otherwise)
 import qualified Prelude as P
 import Data.Int (Int64)
 import qualified Data.List as L
 import Data.Char (isSpace)
+import Data.Monoid (Monoid(..))
 import Data.String (IsString(..))
 import qualified Data.Text as T
 import qualified Data.Text.Fusion.Common as S
@@ -182,6 +200,11 @@
 instance Read Text where
     readsPrec p str = [(pack x,y) | (x,y) <- readsPrec p str]
 
+instance Monoid Text where
+    mempty  = empty
+    mappend = append
+    mconcat = concat
+
 instance IsString Text where
     fromString = pack
 
@@ -339,8 +362,8 @@
     S.length (stream t) = length t
  #-}
 
--- | /O(n)/ 'map' @f @xs is the 'Text' obtained by applying @f@ to
--- each element of @xs@.  Subject to array fusion.
+-- | /O(n)/ 'map' @f@ @t@ is the 'Text' obtained by applying @f@ to
+-- each element of @t@.  Subject to array fusion.
 map :: (Char -> Char) -> Text -> Text
 map f t = unstream (S.map f (stream t))
 {-# INLINE [1] map #-}
@@ -358,6 +381,51 @@
 intersperse c t = unstream (S.intersperse c (stream t))
 {-# INLINE intersperse #-}
 
+-- | /O(n)/ Left-justify a string to the given length, using the
+-- specified fill character on the right. Subject to fusion. Examples:
+--
+-- > justifyLeft 7 'x' "foo"    == "fooxxxx"
+-- > justifyLeft 3 'x' "foobar" == "foobar"
+justifyLeft :: Int64 -> Char -> Text -> Text
+justifyLeft k c t
+    | len >= k  = t
+    | otherwise = t `append` replicateChar (k-len) c
+  where len = length t
+{-# INLINE [1] justifyLeft #-}
+
+{-# RULES
+"LAZY TEXT justifyLeft -> fused" [~1] forall k c t.
+    justifyLeft k c t = unstream (S.justifyLeftI k c (stream t))
+"LAZY TEXT justifyLeft -> unfused" [1] forall k c t.
+    unstream (S.justifyLeftI k c (stream t)) = justifyLeft k c t
+  #-}
+
+-- | /O(n)/ Right-justify a string to the given length, using the
+-- specified fill character on the left. Examples:
+--
+-- > justifyRight 7 'x' "bar"    == "xxxxbar"
+-- > justifyRight 3 'x' "foobar" == "foobar"
+justifyRight :: Int64 -> Char -> Text -> Text
+justifyRight k c t
+    | len >= k  = t
+    | otherwise = replicateChar (k-len) c `append` t
+  where len = length t
+{-# INLINE justifyRight #-}
+
+-- | /O(n)/ Center a string to the given length, using the
+-- specified fill character on either side. Examples:
+--
+-- > center 8 'x' "HS" = "xxxHSxxx"
+center :: Int64 -> Char -> Text -> Text
+center k c t
+    | len >= k  = t
+    | otherwise = replicateChar l c `append` t `append` replicateChar r c
+  where len = length t
+        d   = k - len
+        r   = d `div` 2
+        l   = d - r
+{-# INLINE center #-}
+
 -- | /O(n)/ The 'transpose' function transposes the rows and columns
 -- of its 'Text' argument.  Note that this function uses 'pack',
 -- 'unpack', and the list version of transpose, and is thus not very
@@ -373,6 +441,14 @@
   where rev a Empty        = a
         rev a (Chunk t ts) = rev (Chunk (T.reverse t) a) ts
 
+-- | /O(m)*O(n)/ Replace every occurrence of one substring with another.
+replace :: Text                 -- ^ Text to search for
+        -> Text                 -- ^ Replacement text
+        -> Text                 -- ^ Input text
+        -> Text
+replace s d = intercalate d . split s
+{-# INLINE replace #-}
+
 -- ----------------------------------------------------------------------------
 -- ** Case conversions (folds)
 
@@ -556,12 +632,22 @@
                         where (s'',y ) = f s' x
                               (s', ys) = mapAccumR f s xs
 
--- | /O(n)/ 'replicate' @n@ @c@ is a 'Text' of length @n@ with @c@ the
--- value of every element.
-replicate :: Int -> Char -> Text
-replicate n c = unstream (S.replicate n c)
+-- | /O(n*m)/ 'replicate' @n@ @t@ is a 'Text' consisting of the input
+-- @t@ repeated @n@ times. Subject to fusion.
+replicate :: Int64 -> Text -> Text
+replicate n t = unstream (S.replicateI n (S.stream t))
 {-# INLINE replicate #-}
 
+-- | /O(n)/ 'replicateChar' @n@ @c@ is a 'Text' of length @n@ with @c@ the
+-- value of every element. Subject to fusion.
+replicateChar :: Int64 -> Char -> Text
+replicateChar n c = unstream (S.replicateCharI n c)
+
+{-# RULES
+"LAZY TEXT replicate/singleton -> replicateChar" [~1] forall n c.
+    replicate n (singleton c) = replicateChar n c
+  #-}
+
 -- | /O(n)/, where @n@ is the length of the result. The 'unfoldr'
 -- function is analogous to the List 'L.unfoldr'. 'unfoldr' builds a
 -- 'Text' from a seed value. The function takes the element and
@@ -605,7 +691,7 @@
 -- | /O(n)/ 'drop' @n@, applied to a 'Text', returns the suffix of the
 -- 'Text' of length @n@, or the empty 'Text' if @n@ is greater than the
 -- length of the 'Text'. Subject to fusion.
-drop :: Int -> Text -> Text
+drop :: Int64 -> Text -> Text
 drop i t0
     | i <= 0 = t0
     | otherwise = drop' i t0
@@ -644,8 +730,8 @@
     unstream (S.takeWhile p (stream t)) = takeWhile p t
   #-}
 
--- | /O(n)/ 'dropWhile' @p@ @xs@ returns the suffix remaining after
--- 'takeWhile' @p@ @xs@. This function is subject to array fusion.
+-- | /O(n)/ 'dropWhile' @p@ @t@ returns the suffix remaining after
+-- 'takeWhile' @p@ @t@. This function is subject to array fusion.
 dropWhile :: (Char -> Bool) -> Text -> Text
 dropWhile p t0 = dropWhile' t0
   where dropWhile' Empty        = Empty
@@ -661,7 +747,53 @@
 "LAZY TEXT dropWhile -> unfused" [1] forall p t.
     unstream (S.dropWhile p (stream t)) = dropWhile p t
   #-}
+-- | /O(n)/ 'dropWhileEnd' @p@ @t@ returns the prefix remaining after
+-- dropping characters that fail the predicate @p@ from the end of
+-- @t@.
+-- Examples:
+--
+-- > dropWhileEnd (=='.') "foo..." == "foo"
+dropWhileEnd :: (Char -> Bool) -> Text -> Text
+dropWhileEnd p = go
+  where go Empty = Empty
+        go (Chunk t Empty) = if T.null t'
+                             then Empty
+                             else Chunk t' Empty
+            where t' = T.dropWhileEnd p t
+        go (Chunk t ts) = case go ts of
+                            Empty -> go (Chunk t Empty)
+                            ts' -> Chunk t ts'
+{-# INLINE dropWhileEnd #-}
 
+-- | /O(n)/ 'dropAround' @p@ @t@ returns the substring remaining after
+-- dropping characters that fail the predicate @p@ from both the
+-- beginning and end of @t@.  Subject to fusion.
+dropAround :: (Char -> Bool) -> Text -> Text
+dropAround p = dropWhile p . dropWhileEnd p
+{-# INLINE [1] dropAround #-}
+
+-- | /O(n)/ Remove leading white space from a string.  Equivalent to:
+--
+-- > dropWhile isSpace
+stripStart :: Text -> Text
+stripStart = dropWhile isSpace
+{-# INLINE [1] stripStart #-}
+
+-- | /O(n)/ Remove trailing white space from a string.  Equivalent to:
+--
+-- > dropWhileEnd isSpace
+stripEnd :: Text -> Text
+stripEnd = dropWhileEnd isSpace
+{-# INLINE [1] stripEnd #-}
+
+-- | /O(n)/ Remove leading and trailing white space from a string.
+-- Equivalent to:
+--
+-- > dropAround isSpace
+strip :: Text -> Text
+strip = dropAround isSpace
+{-# INLINE [1] strip #-}
+
 -- | /O(n)/ 'splitAt' @n t@ returns a pair whose first element is a
 -- prefix of @t@ of length @n@, and whose second is the remainder of
 -- the string. It is equivalent to @('take' n t, 'drop' n t)@.
@@ -734,34 +866,108 @@
   | T.length t == 1 = ts : tails ts'
   | otherwise       = ts : tails (Chunk (T.unsafeTail t) ts')
 
--- | /O(n)/ Break a 'Text' into pieces separated by the byte
--- argument, consuming the delimiter. I.e.
+-- $split
 --
--- > split '\n' "a\nb\nd\ne" == ["a","b","d","e"]
--- > split 'a'  "aXaXaXa"    == ["","X","X","X",""]
--- > split 'x'  "x"          == ["",""]
+-- Splitting functions in this library do not perform character-wise
+-- copies to create substrings; they just construct new 'Text's that
+-- are slices of the original.
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces separated by the first
+-- 'Text' argument, consuming the delimiter.  An empty delimiter is
+-- invalid, and will cause an error to be raised.
+--
+-- Examples:
+--
+-- > split "\r\n" "a\r\nb\r\nd\r\ne" == ["a","b","d","e"]
+-- > split "aaa"  "aaaXaaaXaaaXaaa"  == ["","X","X","X",""]
+-- > split "x"    "x"                == ["",""]
 -- 
 -- and
 --
--- > intercalate [c] . split c == id
--- > split == splitWith . (==)
--- 
--- As for all splitting functions in this library, this function does
--- not copy the substrings, it just constructs new 'Text's that are
--- slices of the original.
-split :: Char -> Text -> [Text]
-split c = splitWith (==c)
-{-# INLINE split #-}
+-- > intercalate s . split s         == id
+-- > split (singleton c)             == splitWith (==c)
+split :: Text                   -- ^ Text to split on
+      -> Text                   -- ^ Input text
+      -> [Text]
+split pat src0
+    | l == 0    = emptyError "split"
+    | l == 1    = splitWith (== (head pat)) src0
+    | otherwise = go src0
+  where
+    l      = length pat
+    go src = search 0 src
+      where
+        search !n !s
+            | null s             = [src]      -- not found
+            | pat `isPrefixOf` s = take n src : go (drop l s)
+            | otherwise          = search (n+1) (tail s)
+{-# INLINE [1] split #-}
 
+{-# RULES
+"LAZY TEXT split/singleton -> splitWith/==" [~1] forall c t.
+    split (singleton c) t = splitWith (==c) t
+  #-}
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times,
+-- treating the first 'Text' argument as the delimiter to break on,
+-- and consuming the delimiter.  The last element of the list contains
+-- the remaining text after the number of times to split has been
+-- reached.  A value of zero or less for @k@ causes no splitting to
+-- occur.  An empty delimiter is invalid, and will cause an error to
+-- be raised.
+--
+-- Examples:
+--
+-- > splitTimes 0   "//"  "a//b//c"   == ["a//b//c"]
+-- > splitTimes 2   ":"   "a:b:c:d:e" == ["a","b","c:d:e"]
+-- > splitTimes 100 "???" "a????b"    == ["a","?b"]
+--
+-- and
+--
+-- > intercalate s . splitTimes k s   == id
+splitTimes :: Int64             -- ^ Maximum number of times to split
+           -> Text              -- ^ Text to split on
+           -> Text              -- ^ Input text
+           -> [Text]
+splitTimes k pat src0
+    | k <= 0    = [src0]
+    | l == 0    = emptyError "splitTimes"
+    | otherwise = go k src0
+  where
+    l         = length pat
+    go !i src = search 0 src
+      where
+        search !n !s
+            | i == 0 || null s   = [src]      -- not found or limit reached
+            | pat `isPrefixOf` s = take n src : go (i-1) (drop l s)
+            | otherwise          = search (n+1) (tail s)
+{-# INLINE splitTimes #-}
+
+-- | /O(m)*O(n)/ Break a 'Text' into pieces at most @k@ times, like
+-- 'splitTimes', but start from the end of the input and work towards
+-- the start.
+--
+-- Examples:
+--
+-- > splitTimes 2    "::" "a::b::c::d::e" == ["a","b","c::d::e"]
+-- > splitTimesEnd 2 "::" "a::b::c::d::e" == ["a::b::c","d","e"]
+splitTimesEnd :: Int64             -- ^ Maximum number of times to split
+              -> Text              -- ^ Text to split on
+              -> Text              -- ^ Input text
+              -> [Text]
+splitTimesEnd k pat src =
+    L.reverse . L.map reverse $ splitTimes k (reverse pat) (reverse src)
+{-# INLINE splitTimesEnd #-}
+
 -- | /O(n)/ Splits a 'Text' into components delimited by separators,
 -- where the predicate returns True for a separator element.  The
 -- resulting components do not contain the separators.  Two adjacent
 -- separators result in an empty component in the output.  eg.
 --
 -- > splitWith (=='a') "aabbaca" == ["","","bb","c",""]
--- > splitWith (=='a') []        == []
+-- > splitWith (=='a') []        == [""]
 splitWith :: (Char -> Bool) -> Text -> [Text]
-splitWith _ Empty = []
+splitWith _ Empty = [Empty]
 splitWith p (Chunk t0 ts0) = comb [] (T.splitWith p t0) ts0
   where comb acc (s:[]) Empty        = revChunks (s:acc) : []
         comb acc (s:[]) (Chunk t ts) = comb (s:acc) (T.splitWith p t) ts
@@ -769,6 +975,20 @@
         comb _   []     _            = impossibleError "splitWith"
 {-# INLINE splitWith #-}
 
+-- | /O(n)/ Splits a 'Text' into components of length @k@.  The last
+-- element may be shorter than the other chunks, depending on the
+-- length of the input. Examples:
+--
+-- > chunksOf 3 "foobarbaz"   == ["foo","bar","baz"]
+-- > chunksOf 4 "haskell.org" == ["hask","ell.","org"]
+chunksOf :: Int64 -> Text -> [Text]
+chunksOf k = go
+  where
+    go t = case splitAt k t of
+             (a,b) | null a    -> []
+                   | otherwise -> a : go b
+{-# INLINE chunksOf #-}
+
 -- | /O(n)/ Breaks a 'Text' up into a list of 'Text's at
 -- newline 'Char's. The resulting strings do not contain newlines.
 lines :: Text -> [Text]
@@ -894,12 +1114,33 @@
 elemIndices c t = S.elemIndices c (stream t)
 {-# INLINE elemIndices #-}
 
--- | /O(n)/ The 'count' function returns the number of times the query
--- element appears in the given 'Text'. This function is subject to
--- fusion.
-count :: Char -> Text -> Int64
-count c t = S.count c (stream t)
-{-# INLINE count #-}
+-- | /O(n*m)/ The 'count' function returns the number of times the
+-- query string appears in the given 'Text'. An empty query string is
+-- invalid, and will cause an error to be raised.
+count :: Text -> Text -> Int64
+count pat src0
+    | l == 0    = emptyError "count"
+    | l == 1    = countChar (head pat) src0
+    | otherwise = go 0 src0
+  where
+    l = length pat
+    go !n src = search src
+      where
+        search s | null s             = n
+                 | pat `isPrefixOf` s = go (n+1) (drop l s)
+                 | otherwise          = search (tail s)
+{-# INLINE [1] count #-}
+
+{-# RULES
+"LAZY TEXT count/singleton -> countChar" [~1] forall c t.
+    count (singleton c) t = countChar c t
+  #-}
+
+-- | /O(n)/ The 'countChar' function returns the number of times the
+-- query element appears in the given 'Text'. This function is subject
+-- to fusion.
+countChar :: Char -> Text -> Int64
+countChar c t = S.countChar c (stream t)
 
 -- | /O(n)/ 'zip' takes two 'Text's and returns a list of
 -- corresponding pairs of bytes. If one input 'Text' is short,
diff --git a/Data/Text/Lazy/Fusion.hs b/Data/Text/Lazy/Fusion.hs
--- a/Data/Text/Lazy/Fusion.hs
+++ b/Data/Text/Lazy/Fusion.hs
@@ -22,7 +22,7 @@
     , findIndices
     , elemIndex
     , elemIndices
-    , count
+    , countChar
     ) where
 
 import Prelude hiding (length)
@@ -134,6 +134,6 @@
 
 -- | /O(n)/ The 'count' function returns the number of times the query
 -- element appears in the given stream.
-count :: Char -> Stream Char -> Int64
-count = S.countI
-{-# INLINE [0] count #-}
+countChar :: Char -> Stream Char -> Int64
+countChar = S.countCharI
+{-# INLINE [0] countChar #-}
diff --git a/Data/Text/UnsafeChar.hs b/Data/Text/UnsafeChar.hs
--- a/Data/Text/UnsafeChar.hs
+++ b/Data/Text/UnsafeChar.hs
@@ -19,7 +19,7 @@
     , unsafeChr8
     , unsafeChr32
     , unsafeWrite
-    , unsafeWriteRev
+    -- , unsafeWriteRev
     ) where
 
 import Control.Exception (assert)
@@ -60,6 +60,7 @@
           hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
 {-# INLINE unsafeWrite #-}
 
+{-
 unsafeWriteRev :: A.MArray s Word16 -> Int -> Char -> ST s Int
 unsafeWriteRev marr i c
     | n < 0x10000 = do
@@ -76,3 +77,4 @@
           lo = fromIntegral $ (m `shiftR` 10) + 0xD800
           hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
 {-# INLINE unsafeWriteRev #-}
+-}
diff --git a/Data/Text/UnsafeShift.hs b/Data/Text/UnsafeShift.hs
--- a/Data/Text/UnsafeShift.hs
+++ b/Data/Text/UnsafeShift.hs
@@ -17,7 +17,7 @@
       UnsafeShift(..)
     ) where
 
-import qualified Data.Bits as Bits
+-- import qualified Data.Bits as Bits
 import GHC.Base
 import GHC.Word
 
@@ -33,24 +33,26 @@
 
 instance UnsafeShift Word16 where
     {-# INLINE shiftL #-}
-    shiftL (W16# x#) (I# i#) = W16# (x# `uncheckedShiftL#` i#)
+    shiftL (W16# x#) (I# i#) = W16# (narrow16Word# (x# `uncheckedShiftL#` i#))
 
     {-# INLINE shiftR #-}
     shiftR (W16# x#) (I# i#) = W16# (x# `uncheckedShiftRL#` i#)
 
 instance UnsafeShift Word32 where
     {-# INLINE shiftL #-}
-    shiftL (W32# x#) (I# i#) = W32# (x# `uncheckedShiftL#` i#)
+    shiftL (W32# x#) (I# i#) = W32# (narrow32Word# (x# `uncheckedShiftL#` i#))
 
     {-# INLINE shiftR #-}
     shiftR (W32# x#) (I# i#) = W32# (x# `uncheckedShiftRL#` i#)
 
+{-
 instance UnsafeShift Word64 where
     {-# INLINE shiftL #-}
     shiftL (W64# x#) (I# i#) = W64# (x# `uncheckedShiftL64#` i#)
 
     {-# INLINE shiftR #-}
     shiftR (W64# x#) (I# i#) = W64# (x# `uncheckedShiftRL64#` i#)
+-}
 
 instance UnsafeShift Int where
     {-# INLINE shiftL #-}
@@ -59,9 +61,11 @@
     {-# INLINE shiftR #-}
     shiftR (I# x#) (I# i#) = I# (x# `iShiftRA#` i#)
 
+{-
 instance UnsafeShift Integer where
     {-# INLINE shiftL #-}
     shiftL = Bits.shiftL
 
     {-# INLINE shiftR #-}
     shiftR = Bits.shiftR
+-}
diff --git a/README b/README
--- a/README
+++ b/README
@@ -31,5 +31,7 @@
 
 The base code for this library was originally written by Tom Harper,
 based on the stream fusion framework developed by Roman Leshchinskiy,
-Duncan Coutts, and Don Stewart.  The core library was fleshed out,
-debugged, and tested by Bryan O'Sullivan.
+Duncan Coutts, and Don Stewart.
+
+The core library was fleshed out, debugged, and tested by Bryan
+O'Sullivan, and he is the current maintainer.
diff --git a/text.cabal b/text.cabal
--- a/text.cabal
+++ b/text.cabal
@@ -1,5 +1,5 @@
 name:           text
-version:        0.3
+version:        0.4
 synopsis:       An efficient packed Unicode text type
 description:    An efficient packed Unicode text type.
 license:        BSD3
@@ -19,27 +19,27 @@
     Data.Text
     Data.Text.Encoding
     Data.Text.Encoding.Error
-    Data.Text.Encoding.Fusion
     Data.Text.Foreign
-    Data.Text.Fusion
-    Data.Text.Fusion.Common
     Data.Text.Lazy
     Data.Text.Lazy.Encoding
-    Data.Text.Lazy.Encoding.Fusion
-    Data.Text.Lazy.Fusion
   other-modules:
     Data.Text.Array
-    Data.Text.Internal
+    Data.Text.Encoding.Fusion
     Data.Text.Encoding.Fusion.Common
-    Data.Text.Fusion.Internal
+    Data.Text.Encoding.Utf16
+    Data.Text.Encoding.Utf32
+    Data.Text.Encoding.Utf8
+    Data.Text.Fusion
     Data.Text.Fusion.CaseMapping
+    Data.Text.Fusion.Common
+    Data.Text.Fusion.Internal
+    Data.Text.Internal
+    Data.Text.Lazy.Encoding.Fusion
+    Data.Text.Lazy.Fusion
     Data.Text.Lazy.Internal
     Data.Text.Unsafe
     Data.Text.UnsafeChar
     Data.Text.UnsafeShift
-    Data.Text.Encoding.Utf8
-    Data.Text.Encoding.Utf16
-    Data.Text.Encoding.Utf32
 
   build-depends:
     base       < 5,
