diff --git a/bench/MainBenchmarkSuite.hs b/bench/MainBenchmarkSuite.hs
--- a/bench/MainBenchmarkSuite.hs
+++ b/bench/MainBenchmarkSuite.hs
@@ -84,7 +84,7 @@
     mkTexts x t = (x, F.fromText' x t)
 
 allTexts :: [(Int -> String, [(Int, F.YiString)])]
-allTexts = [longTexts {-, wideTexts, shortTexts, tinyTexts -}]
+allTexts = [longTexts, wideTexts, shortTexts, tinyTexts]
 
 allChars :: [(Int -> String, [(Int, Char)])]
 allChars = map mkChar "λa"
@@ -160,6 +160,7 @@
   , onCharGroup "singleton" F.singleton
   , onTextGroup "countNewLines" F.countNewLines
   , onTextGroup "lines" F.lines
+  , onTextGroup "lines'" F.lines'
   , onSplitGroup "splitAt" F.splitAt
   , onSplitGroup "splitAtLine" F.splitAtLine
   , onTextGroup "toReverseString" F.toReverseString
diff --git a/src/Yi/Rope.hs b/src/Yi/Rope.hs
--- a/src/Yi/Rope.hs
+++ b/src/Yi/Rope.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns #-}
 {-# OPTIONS_HADDOCK show-extensions #-}
 
 -- |
@@ -70,6 +71,7 @@
 import           Data.Default
 import qualified Data.FingerTree as T
 import           Data.FingerTree hiding (null, empty, reverse, split)
+import           Data.Function (fix)
 import qualified Data.List as L (foldl')
 import           Data.Maybe
 import           Data.Monoid
@@ -501,24 +503,18 @@
     go acc (t':ts') = go (acc <> (c `cons` t')) ts'
 
 -- | Add a 'Char' in front of a 'YiString'.
---
--- We add the character to the front of the first chunk. This does
--- mean that a lot of 'cons' might result in an abnormally large first
--- chunk so if you have to do that, consider using 'append' instead..
 cons :: Char -> YiString -> YiString
 cons c (YiString t) = case viewl t of
   EmptyL -> Yi.Rope.singleton c
-  Chunk !l x :< ts -> YiString $ Chunk (l + 1) (c `TX.cons` x) <| ts
+  Chunk l x :< ts | l < defaultChunkSize -> YiString $ Chunk (l + 1) (c `TX.cons` x) <| ts
+  _ -> YiString $ Chunk 1 (TX.singleton c) <| t
 
 -- | Add a 'Char' in the back of a 'YiString'.
---
--- We add the character to the end of the last chunk. This does mean
--- that a lot of 'snoc' might result in an abnormally large last chunk
--- so if you have to do that, consider using 'append' instead..
 snoc :: YiString -> Char -> YiString
 snoc (YiString t) c = case viewr t of
   EmptyR -> Yi.Rope.singleton c
-  ts :> Chunk l x -> YiString $ ts |> Chunk (l + 1) (x `TX.snoc` c)
+  ts :> Chunk l x | l < defaultChunkSize -> YiString $ ts |> Chunk (l + 1) (x `TX.snoc` c)
+  _ -> YiString $ t |> Chunk 1 (TX.singleton c)
 
 -- | Single character 'YiString'. Consider whether it's worth creating
 -- this, maybe you can use 'cons' or 'snoc' instead?
@@ -569,26 +565,11 @@
 
 -- | This is like 'lines'' but it does *not* preserve newlines.
 --
--- Specifically, we just strip the newlines from the result of
--- 'lines''.
---
--- This behaves slightly differently than the old split: the number of
--- resulting strings here is equal to the number of newline characters
--- in the underlying string. This is much more consistent than the old
--- behaviour which blindly used @ByteString@s split and stitched the
--- result back together which was inconsistent with the rest of the
--- interface which worked with number of newlines.
+-- Implementation note: GHC does a pretty good job of optimizing
+-- this naive version. Hand coding a loop should be unnecessary
+-- here.
 lines :: YiString -> [YiString]
-lines = Prelude.map dropNl . lines'
-  where
-    dropNl (YiString t)  = case viewr t of
-      EmptyR -> Yi.Rope.empty
-      ts :> ch@(Chunk l tx) ->
-        YiString $ ts |- if TX.null tx
-                         then ch
-                         else case TX.last tx of
-                           '\n' -> Chunk (l - 1) (TX.init tx)
-                           _ -> ch
+lines = fmap fromText . TX.lines . toText
 
 -- | Splits the 'YiString' into a list of 'YiString' each containing a
 -- line.
@@ -603,13 +584,15 @@
 --
 -- > 'toText' . 'concat' . 'lines'' ≡ 'toText'
 --
--- but the underlying structure might change: notably, chunks will
--- most likely change sizes.
 lines' :: YiString -> [YiString]
-lines' t = let (YiString f, YiString s) = splitAtLine' 0 t
-           in if T.null s
-              then if T.null f then [] else [YiString f]
-              else YiString f : lines' (YiString s)
+lines' = splitByKeepingDelim '\n'
+
+splitByKeepingDelim :: Char -> YiString -> [YiString]
+splitByKeepingDelim x = fmap fromText . fix go x . toText
+  where
+    go :: (Char -> TX.Text -> [TX.Text]) -> Char -> TX.Text -> [TX.Text]
+    go _ c (TX.span (/=c) -> (_, TX.null -> True)) = []
+    go f c (TX.span (/=c) -> (a,b)) = a `TX.snoc` c : f c (TX.tail b)
 
 -- | Joins up lines by a newline character. It does not leave a
 -- newline after the last line. If you want a more classical
diff --git a/yi-rope.cabal b/yi-rope.cabal
--- a/yi-rope.cabal
+++ b/yi-rope.cabal
@@ -1,17 +1,17 @@
 name:                yi-rope
-version:             0.8
+version:             0.9
 synopsis:            A rope data structure used by Yi
 description:         A rope data structure used by Yi
 license:             GPL-2
 license-file:        LICENSE
-author:              Mateusz Kowalczyk
-maintainer:          fuuzetsu@fuuzetsu.co.uk
+author:              AUTHORS
+maintainer:          yi-devel@googlegroups.com
 category:            Yi
 build-type:          Simple
 cabal-version:       >=1.10
 
 library
-  ghc-options: -fpedantic-bottoms -fexpose-all-unfoldings -Wall -O2 -flate-dmd-anal
+  ghc-options: -fpedantic-bottoms -fexpose-all-unfoldings -ferror-spans -Wall -O2 -flate-dmd-anal
 
   exposed-modules:
     Yi.Rope
