diff --git a/demos/ComplexDemo.hs b/demos/ComplexDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/ComplexDemo.hs
@@ -0,0 +1,169 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-do-bind #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import System.Exit ( exitSuccess )
+import System.Locale
+import qualified Data.Text as T
+import Control.Monad
+import Control.Concurrent
+import Data.Time.Clock
+import Data.Time.Format
+import Graphics.Vty hiding (pad)
+import Graphics.Vty.Widgets.All
+
+-- Visual attributes.
+fg = white
+bg = black
+focAttr = black `on` yellow
+headerAttr = fgColor bright_green
+msgAttr = fgColor blue
+
+-- Multi-state checkbox value type
+data FrostingType = Chocolate
+                  | Vanilla
+                  | Lemon
+                    deriving (Eq, Show)
+
+main :: IO ()
+main = do
+  let msg = "- <TAB> switches input elements\n\n\
+            \- ordinary keystrokes edit\n\n\
+            \- <SPC> toggles radio buttons and checkboxes\n\n\
+            \- <ESC> quits"
+
+      columns = [ column (ColFixed 25) `pad` (padAll 1)
+                , column ColAuto `pad` (padAll 1)
+                ]
+
+  table <- newTable columns BorderFull >>=
+           withNormalAttribute (bgColor blue) >>=
+           withBorderAttribute (fgColor green)
+
+  tw <- (textWidget wrap msg) >>= withNormalAttribute msgAttr
+  mainBox <- vBox table tw >>= withBoxSpacing 1
+
+  r1 <- newCheckbox "Cake"
+  r2 <- newCheckbox "Death"
+  radioHeader <- plainText T.empty >>= withNormalAttribute headerAttr
+
+  rg <- newRadioGroup
+  addToRadioGroup rg r1
+  addToRadioGroup rg r2
+
+  r3 <- newMultiStateCheckbox "Frosting" [ (Chocolate, 'C')
+                                         , (Vanilla, 'V')
+                                         , (Lemon, 'L')
+                                         ]
+
+  edit1 <- editWidget >>= withFocusAttribute (white `on` red)
+  edit2 <- multiLineEditWidget
+  edit2box <- boxFixed 30 3 edit2
+  setEditLineLimit edit2 $ Just 3
+
+  edit1Header <- textWidget wrap T.empty >>= withNormalAttribute headerAttr
+  edit2Header <- textWidget wrap T.empty >>= withNormalAttribute headerAttr
+
+  lst <- newList (fgColor bright_green)
+
+  selector <- vLimit 3 lst
+  listHeader <- plainText T.empty
+
+  rs <- vBox r1 r2
+
+  cbHeader <- plainText T.empty
+  timeText <- plainText T.empty
+
+  prog <- newProgressBar (white `on` red) (red `on` white)
+  setProgressTextAlignment prog AlignCenter
+
+  addHeadingRow_ table headerAttr ["Column 1", "Column 2"]
+  addRow table $ radioHeader .|. rs
+  addRow table $ cbHeader .|. r3
+  addRow table $ edit1Header .|. edit1
+  addRow table $ edit2Header .|. edit2box
+  addRow table $ listHeader .|. customCell selector `pad` padNone
+  addRow table $ emptyCell .|. timeText
+  addRow table $ emptyCell .|. prog
+
+  rg `onRadioChange` \cb -> do
+      s <- getCheckboxLabel cb
+      setText radioHeader $ T.concat [s, ", please."]
+
+  r3 `onCheckboxChange` \v ->
+      setText cbHeader $ T.pack $ concat ["you chose: ", show v]
+
+  prog `onProgressChange` \val ->
+      setProgressText prog $ T.concat [ "Progress ("
+                                      , T.pack $ show val
+                                      , " %)"
+                                      ]
+
+  edit1 `onChange` (setText edit1Header)
+  edit2 `onChange` (setText edit2Header)
+
+  lst `onSelectionChange` \ev ->
+      case ev of
+        SelectionOn _ k _ -> setText listHeader $ T.pack $ "You selected: " ++ k
+        SelectionOff -> return ()
+
+  lst `onItemActivated` \(ActivateItemEvent _ s _) ->
+      setText listHeader $ T.pack $ "You activated: " ++ s
+
+  setEditText edit1 "Foo"
+  setEditText edit2 "Bar"
+  setCheckboxChecked r1
+
+  setCheckboxState r3 Chocolate
+  -- It would be nice if we didn't have to do this, but the
+  -- setCheckboxState call above will not notify any state-change
+  -- handlers because the state isn't actually changing (from its
+  -- original value of Chocolate, the first value in its state list).
+  setText cbHeader $ "you chose: Chocolate"
+
+  fgr <- newFocusGroup
+  fgr `onKeyPressed` \_ k _ -> do
+         case k of
+           KEsc -> exitSuccess
+           _ -> return False
+
+  let strs = [ "Cookies"
+             , "Cupcakes"
+             , "Twinkies"
+             , "M&Ms"
+             , "Fritos"
+             , "Cheetos"
+             ]
+
+  forM_ strs $ \s ->
+      (addToList lst s =<< (plainText $ T.pack s))
+
+  addToFocusGroup fgr r1
+  addToFocusGroup fgr r2
+  addToFocusGroup fgr r3
+  addToFocusGroup fgr edit1
+  addToFocusGroup fgr edit2
+  addToFocusGroup fgr lst
+
+  ui <- centered =<< hLimit 70 mainBox
+
+  forkIO $ forever $ do
+         schedule $ do
+           t <- getCurrentTime
+           setText timeText $ T.pack $
+                   formatTime defaultTimeLocale rfc822DateFormat t
+         threadDelay $ 1 * 1000 * 1000
+
+  forkIO $ forever $ do
+         let act i = do
+               threadDelay $ 1 * 1000 * 1000
+               schedule $ setProgress prog (i `mod` 101)
+               act $ i + 4
+         act 0
+
+  c <- newCollection
+  _ <- addToCollection c ui fgr
+
+  runUi c $ defaultContext { focusAttr = focAttr
+                           , normalAttr = fg `on` bg
+                           }
diff --git a/demos/DialogDemo.hs b/demos/DialogDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/DialogDemo.hs
@@ -0,0 +1,40 @@
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import qualified Data.Text as T
+import Graphics.Vty hiding (Button)
+import Graphics.Vty.Widgets.All
+
+main :: IO ()
+main = do
+  e <- editWidget
+  fg <- newFocusGroup
+  addToFocusGroup fg e
+
+  u <- plainText "Enter some text and press enter." <--> return e
+       >>= withBoxSpacing 1
+
+  pe <- padded u (padLeftRight 2)
+  (d, dFg) <- newDialog pe "<enter text>"
+  setNormalAttribute d (white `on` blue)
+
+  c <- centered =<< withPadding (padLeftRight 10) (dialogWidget d)
+
+  -- When the edit widget changes, set the dialog's title.
+  e `onChange` setDialogTitle d
+
+  -- When the user presses Enter in the edit widget, accept the
+  -- dialog.
+  e `onActivate` (const $ acceptDialog d)
+
+  -- Exit either way.
+  d `onDialogAccept` const shutdownUi
+  d `onDialogCancel` const shutdownUi
+
+  coll <- newCollection
+  _ <- addToCollection coll c =<< (mergeFocusGroups fg dFg)
+
+  runUi coll $ defaultContext { focusAttr = black `on` yellow }
+
+  (putStrLn . ("You entered: " ++) . T.unpack) =<< getEditText e
diff --git a/demos/DirBrowserDemo.hs b/demos/DirBrowserDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/DirBrowserDemo.hs
@@ -0,0 +1,17 @@
+module Main where
+
+import Graphics.Vty
+import Graphics.Vty.Widgets.All
+
+main :: IO ()
+main = do
+  (b, fg) <- newDirBrowser defaultBrowserSkin
+
+  c <- newCollection
+  _ <- addToCollection c (dirBrowserWidget b) fg
+
+  b `onBrowseAccept` error
+  b `onBrowseCancel` error
+
+  runUi c $ defaultContext { focusAttr = white `on` blue
+                           }
diff --git a/demos/EditDemo.hs b/demos/EditDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/EditDemo.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Graphics.Vty hiding (Button)
+import Graphics.Vty.Widgets.All
+
+main :: IO ()
+main = do
+  e1 <- multiLineEditWidget
+  e2 <- multiLineEditWidget
+  setEditLineLimit e2 $ Just 3
+  e3 <- editWidget
+
+  fg <- newFocusGroup
+  _ <- addToFocusGroup fg e1
+  _ <- addToFocusGroup fg e2
+  _ <- addToFocusGroup fg e3
+
+  be1 <- bordered =<< boxFixed 40 5 e1
+  be2 <- bordered =<< boxFixed 40 3 e2
+  be3 <- bordered =<< boxFixed 40 1 e3
+
+  c <- centered =<< ((plainText "Multi-Line Editor (unlimited lines):")
+                         <--> (return be1)
+                         <--> (plainText "Multi-Line Editor (3 lines):")
+                         <--> (return be2)
+                         <--> (plainText "Single-Line Editor:")
+                         <--> (return be3)
+                         <--> (plainText "- Esc to quit\n\n- TAB to switch editors") >>= withBoxSpacing 1
+                    )
+
+  coll <- newCollection
+  _ <- addToCollection coll c fg
+
+  fg `onKeyPressed` \_ k _ ->
+      case k of
+        KEsc -> shutdownUi >> return True
+        _ -> return False
+
+  runUi coll $ defaultContext { focusAttr = fgColor yellow }
diff --git a/demos/ListDemo.hs b/demos/ListDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/ListDemo.hs
@@ -0,0 +1,137 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-do-bind #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import System.Exit ( exitSuccess )
+import qualified Data.Text as T
+import Graphics.Vty
+import Graphics.Vty.Widgets.All
+
+data AppElements =
+    AppElements { theList :: Widget (List T.Text FormattedText)
+                , theBody :: Widget FormattedText
+                , theFooter :: Widget FormattedText
+                , theListLimit :: Widget (VLimit (List T.Text FormattedText))
+                , uis :: Collection
+                }
+
+titleAttr = bright_white `on` blue
+focAttr = black `on` green
+bodyAttr = white `on` black
+selAttr = black `on` yellow
+keyAttr = fgColor magenta
+
+message1 :: T.Text
+message1 = "This demonstration shows how list widgets behave. \n\
+           \See the keystrokes below to try the demo."
+
+message2 :: [(T.Text, Attr)]
+message2 = [ ("- Press ", def_attr), ("q", keyAttr), (" to quit\n", def_attr)
+           , ("- Press ", def_attr), ("+", keyAttr)
+           , (" / ", def_attr), ("a", keyAttr)
+           , (" to add a list item\n", def_attr)
+           , ("- Press ", def_attr), ("-", keyAttr)
+           , (" / ", def_attr), ("d", keyAttr)
+           , (" to remove the selected list item\n", def_attr)
+           , ("- Press ", def_attr)
+           , ("up", keyAttr), (" / ", def_attr)
+           , ("down", keyAttr), (" / ", def_attr)
+           , ("page up", keyAttr), (" / ", def_attr)
+           , ("page down", keyAttr)
+           , (" to navigate the list\n", def_attr)
+           ]
+
+buildUi appst = do
+  msg1 <- plainText message1
+  setTextFormatter msg1 wrap
+
+  msg2 <- plainTextWithAttrs message2
+  setTextFormatter msg2 wrap
+
+  mainUi <- (hBorder >>= withBorderAttribute titleAttr)
+         <--> (return $ theList appst)
+         <--> ((return $ theFooter appst)
+               <++> (hBorder >>= withBorderAttribute titleAttr))
+
+  centered =<< hLimit 55 =<<
+             ((return msg1)
+                  <--> ((vLimit 25 mainUi >>= bordered)
+                        <--> return msg2 >>= withBoxSpacing 1)
+                    >>= withBoxSpacing 1)
+
+-- Construct the application statea using the message map.
+mkAppElements :: IO AppElements
+mkAppElements = do
+  lw <- newTextList selAttr []
+  b <- textWidget wrap T.empty
+  ft <- plainText T.empty >>= withNormalAttribute titleAttr
+  ll <- vLimit 5 lw
+
+  c <- newCollection
+
+  return $ AppElements { theList = lw
+                       , theBody = b
+                       , theFooter = ft
+                       , theListLimit = ll
+                       , uis = c
+                       }
+
+updateBody :: AppElements -> Int -> IO ()
+updateBody st i = do
+  let msg = "This is the text for list entry " ++ (show $ i + 1)
+  setText (theBody st) $ T.pack msg
+
+updateFooterNums :: AppElements -> Widget (List a b) -> IO ()
+updateFooterNums st w = do
+  result <- getSelected w
+  sz <- getListSize w
+  let msg = case result of
+              Nothing -> "0/0"
+              Just (i, _) -> (show $ i + 1) ++ "/" ++ (show sz)
+  setText (theFooter st) $ T.pack msg
+
+main :: IO ()
+main = do
+  st <- mkAppElements
+
+  ui <- buildUi st
+  fg <- newFocusGroup
+
+  _ <- addToCollection (uis st) ui fg
+  _ <- addToFocusGroup fg (theList st)
+
+  (theList st) `onSelectionChange` \_ -> updateFooterNums st $ theList st
+  (theList st) `onItemAdded` \_ -> updateFooterNums st $ theList st
+  (theList st) `onItemRemoved` \_ -> updateFooterNums st $ theList st
+
+  let removeCurrentItem = do
+         result <- getSelected (theList st)
+         case result of
+           Nothing -> return ()
+           Just (i, _) -> removeFromList (theList st) i >> return ()
+      addNewItem =
+          (plainText "a list item") >>=
+                    addToList (theList st) "unused"
+
+  (theList st) `onKeyPressed` \_ k _ -> do
+         case k of
+           (KASCII 'q') -> exitSuccess
+           (KASCII '-') -> removeCurrentItem >> return True
+           (KASCII 'd') -> removeCurrentItem >> return True
+           (KASCII '+') -> addNewItem >> return True
+           (KASCII 'a') -> addNewItem >> return True
+           _ -> return False
+
+  -- We need to call these handlers manually because while they will
+  -- be called automatically as items are added to the list in the
+  -- future, the items currently in the list didn't call these because
+  -- they weren't registered at the time the items were added.  And
+  -- that was impossible because the list was created and populated
+  -- before we even got a reference to it, so we couldn't have set up
+  -- event handlers.
+  updateFooterNums st (theList st)
+
+  -- Enter the event loop.
+  runUi (uis st) $ defaultContext { normalAttr = bodyAttr
+                                  , focusAttr = focAttr
+                                  }
diff --git a/demos/PhoneInputDemo.hs b/demos/PhoneInputDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/PhoneInputDemo.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+-- This demo is discussed in the vty-ui user's manual.
+
+import Control.Monad
+import qualified Data.Text as T
+
+import Graphics.Vty hiding (pad)
+import Graphics.Vty.Widgets.All
+
+data PhoneNumber = PhoneNumber T.Text T.Text T.Text
+                   deriving (Show)
+
+-- This type isn't pretty, but we have to specify the type of the
+-- complete interface.  Initially you can let the compiler tell you
+-- what it is.
+type T = Box (Box
+              (Box (Box (HFixed Edit) FormattedText) (HFixed Edit))
+              FormattedText) (HFixed Edit)
+
+data PhoneInput =
+   PhoneInput { phoneInputWidget :: Widget T
+              , edit1 :: Widget Edit
+              , edit2 :: Widget Edit
+              , edit3 :: Widget Edit
+              , activateHandlers :: Handlers PhoneNumber
+              }
+
+newPhoneInput :: IO (PhoneInput, Widget FocusGroup)
+newPhoneInput = do
+   ahs <- newHandlers
+   e1 <- editWidget
+   e2 <- editWidget
+   e3 <- editWidget
+   ui <- (hFixed 4 e1) <++>
+         (plainText "-") <++>
+         (hFixed 4 e2) <++>
+         (plainText "-") <++>
+         (hFixed 5 e3)
+
+   let w = PhoneInput ui e1 e2 e3 ahs
+       doFireEvent = const $ do
+         num <- mkPhoneNumber
+         fireEvent w (return . activateHandlers) num
+
+       mkPhoneNumber = do
+         s1 <- getEditText e1
+         s2 <- getEditText e2
+         s3 <- getEditText e3
+         return $ PhoneNumber s1 s2 s3
+
+   e1 `onActivate` doFireEvent
+   e2 `onActivate` doFireEvent
+   e3 `onActivate` doFireEvent
+
+   e1 `onChange` \s -> when (T.length s == 3) $ focus e2
+   e2 `onChange` \s -> when (T.length s == 3) $ focus e3
+
+   fg <- newFocusGroup
+   mapM_ (addToFocusGroup fg) [e1, e2, e3]
+   return (w, fg)
+
+onPhoneInputActivate :: PhoneInput
+                     -> (PhoneNumber -> IO ()) -> IO ()
+onPhoneInputActivate input handler =
+    addHandler (return . activateHandlers) input handler
+
+main :: IO ()
+main = do
+  (p, fg) <- newPhoneInput
+  p `onPhoneInputActivate` (error . show)
+
+  ui <- padded (phoneInputWidget p) (padLeftRight 5 `pad` padTopBottom 2)
+
+  c <- newCollection
+  _ <- addToCollection c ui fg
+
+  runUi c $ defaultContext { focusAttr = white `on` blue
+                           }
diff --git a/demos/ProgressBarDemo.hs b/demos/ProgressBarDemo.hs
new file mode 100644
--- /dev/null
+++ b/demos/ProgressBarDemo.hs
@@ -0,0 +1,58 @@
+{-# OPTIONS_GHC -fno-warn-unused-do-bind -fno-warn-missing-signatures #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import qualified Data.Text as T
+import Control.Concurrent (forkIO, threadDelay)
+import Control.Monad (forever)
+
+import System.Exit ( exitSuccess )
+import Graphics.Vty
+import Graphics.Vty.Widgets.All
+
+-- Visual attributes.
+focAttr = black `on` green
+bodyAttr = bright_green `on` black
+completeAttr = white `on` red
+incompleteAttr = red `on` white
+
+setupProgessBar :: IO (Widget ProgressBar)
+setupProgessBar = do
+  pb <- newProgressBar completeAttr incompleteAttr
+  setProgressTextAlignment pb AlignCenter
+
+  pb `onProgressChange` \val ->
+      setProgressText pb $ T.pack $ "Progress (" ++ show val ++ " %)"
+
+  return pb
+
+setupProgressBarThread :: Widget ProgressBar -> IO ()
+setupProgressBarThread pb = do
+  forkIO $ forever $ do
+    let act i = do
+          threadDelay $ 1 * 1000 * 1000
+          schedule $ setProgress pb (i `mod` 101)
+          act $ i + 4
+    act 0
+  return ()
+
+setupDialog :: (Show a) => Widget a -> IO (Dialog, Widget FocusGroup)
+setupDialog ui = do
+  (dlg, fg) <- newDialog ui "Progress Bar Demo"
+  dlg `onDialogAccept` const exitSuccess
+  dlg `onDialogCancel` const exitSuccess
+  return (dlg, fg)
+
+main :: IO ()
+main = do
+  pb <- setupProgessBar
+  setupProgressBarThread pb
+
+  (dlg, dlgFg) <- setupDialog pb
+
+  c <- newCollection
+  _ <- addToCollection c (dialogWidget dlg) dlgFg
+
+  runUi c $ defaultContext { normalAttr = bodyAttr
+                           , focusAttr = focAttr
+                           }
diff --git a/doc/Makefile b/doc/Makefile
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -1,9 +1,9 @@
 
-vty-ui-users-manual.pdf: *.tex ch[1234]/*.tex
+vty-ui-users-manual.pdf: *.tex ch?/*.tex
 	# Run it twice so the TOC gets generated properly
-	pdflatex -shell-escape vty-ui-users-manual.tex
-	pdflatex -shell-escape vty-ui-users-manual.tex
+	pdflatex -halt-on-error -shell-escape vty-ui-users-manual.tex
+	pdflatex -halt-on-error -shell-escape vty-ui-users-manual.tex
 
 clean:
 	rm -f *~ *.dvi *.pdf *.log *.aux *.toc *.out
-	rm -f ch[1234]/*.aux ch[1234]/*~
+	rm -f ch?/*.aux ch?/*~
diff --git a/doc/ch1/api_notes.tex b/doc/ch1/api_notes.tex
--- a/doc/ch1/api_notes.tex
+++ b/doc/ch1/api_notes.tex
@@ -1,5 +1,7 @@
 \section{Conventions and API Notes}
 
+\subsection{Widget Types}
+
 When you create a widget in \vtyui, the result with almost always have
 a type like \fw{Widget a}.  The type variable \fw{a} represents the
 specific type of state the widget can carry, and therefore which
@@ -15,12 +17,20 @@
 \fw{Widget a} whenever you need to refer to a widget; this makes it
 possible to mutate widget state when events occur in your application.
 
+\subsection{Return Values}
+
 Regarding return values, even if a function is of type \fw{...\ -> IO
   a}, we say it is ``in the \fw{IO} monad'' and \textit{returns}
 \fw{a}.  We won't bother saying that a function \textit{returns \fw{IO
     a}}.
 
+\subsection{Library Modules}
+
 Lastly, we will refer to the many \vtyui\ library modules throughout
 this document.  We will almost always omit the
 \fw{Graphics.Vty.Widgets} module namespace prefix and will instead
-refer to the modules by their short names.
+refer to the modules by their short names.  In addition, many modules
+in this library use \fw{Data.Text} values to represent text strings.
+We assume that \fw{Data.Text} is imported under the qualified name
+\fw{T}.  We also assume the use of the \fw{OverloadedStrings} compiler
+language extension to avoid the repeated use of \fw{T.pack}.
diff --git a/doc/ch1/getting_started.tex b/doc/ch1/getting_started.tex
--- a/doc/ch1/getting_started.tex
+++ b/doc/ch1/getting_started.tex
@@ -8,8 +8,8 @@
  import Graphics.Vty.Widgets.All
 \end{haskellcode}
 
-The \fw{All} module exports everything exported by the library; if you
-prefer, you may import specific modules depending on your needs.
+The \fw{All} module exports almost everything exported by the library.
+If you prefer, you can always import specific modules.
 
 As a demonstration, we'll create a program which presents an editing
 widget in the middle of the screen.  You'll be able to provide some
@@ -17,6 +17,8 @@
 will print what you entered.  The code for this program is as follows:
 
 \begin{haskellcode}
+ import qualified Data.Text as T
+
  main :: IO ()
  main = do
    e <- editWidget
@@ -29,7 +31,7 @@
    addToCollection c ui fg
 
    e `onActivate` \this ->
-     getEditText this >>= (error . ("You entered: " ++))
+     getEditText this >>= (error . ("You entered: " ++) . T.unpack)
 
    runUi c defaultContext
 \end{haskellcode}
@@ -106,7 +108,7 @@
 
 \begin{haskellcode}
  e `onActivate` \this -> getEditText this >>=
-   (error . ("You entered: " ++))
+   (error . ("You entered: " ++) . T.unpack)
 \end{haskellcode}
 
 This binds an event handler to the ``activation'' of the \fw{Edit}
@@ -114,7 +116,10 @@
 and presses \fw{Enter}.  The handler for this event is an \fw{IO}
 action which takes the \fw{Edit} widget itself as its only parameter.
 The \fw{getEditText} function gets the current text of the \fw{Edit}
-widget, and we use \fw{error} to abort the program and print the text.
+widget, and we use \fw{error} to abort the program and print the
+text.\footnote{In general I wouldn't recommend the use of \fw{error}
+  to do this, but the \vtyui\ event loop will still cleanly shut down
+  and clean up Vty in the event of any exception.}
 
 \begin{haskellcode}
  runUi c defaultContext
diff --git a/doc/ch3/implementing_composite_widgets.tex b/doc/ch3/implementing_composite_widgets.tex
--- a/doc/ch3/implementing_composite_widgets.tex
+++ b/doc/ch3/implementing_composite_widgets.tex
@@ -41,7 +41,7 @@
 implementing \fw{WidgetImpl}'s interface.  First we provide the types:
 
 \begin{haskellcode}
- data PhoneNumber = PhoneNumber String String String
+ data PhoneNumber = PhoneNumber T.Text T.Text T.Text
                     deriving (Show)
 
  -- This type isn't pretty, but we have to specify the type
@@ -78,9 +78,6 @@
          (plainText "-") <++>
          (hFixed 5 e3)
 
-   e1 `onChange` \s -> when (length s == 3) $ focus e2
-   e2 `onChange` \s -> when (length s == 3) $ focus e3
-
    let w = PhoneInput ui e1 e2 e3 ahs
        doFireEvent = const $ do
          num <- mkPhoneNumber
@@ -95,6 +92,9 @@
    e1 `onActivate` doFireEvent
    e2 `onActivate` doFireEvent
    e3 `onActivate` doFireEvent
+
+   e1 `onChange` \s -> when (T.length s == 3) $ focus e2
+   e2 `onChange` \s -> when (T.length s == 3) $ focus e3
 
    fg <- newFocusGroup
    mapM_ (addToFocusGroup fg) [e1, e2, e3]
diff --git a/doc/ch3/implementing_event_handlers.tex b/doc/ch3/implementing_event_handlers.tex
--- a/doc/ch3/implementing_event_handlers.tex
+++ b/doc/ch3/implementing_event_handlers.tex
@@ -68,9 +68,9 @@
 
 We've introduced a new operator here, \fw{<\string~\string~}.  This
 operator takes any \fw{Widget a} and a function on its state type,
-\fw{a -> b}, and runs the function and returns the value, \fw{b},
-inside calling monad.  \fw{addHandler} needs a value of type
-\fw{Handlers TemperatureEvent}, and to get that we must use
+applies the function to the state, and returns the result.
+\fw{add\-Handler} needs a value of type \fw{Handlers
+  Temperature\-Event}, and to get that we must use
 \fw{<\string~\string~}.
 
 The \fw{addHandler} function takes a \fw{Handlers a} and a handler of
diff --git a/doc/ch3/new_widget_type.tex b/doc/ch3/new_widget_type.tex
--- a/doc/ch3/new_widget_type.tex
+++ b/doc/ch3/new_widget_type.tex
@@ -8,20 +8,20 @@
 As an example, consider a widget that displays a numeric counter.  The
 widget state will be the value of the counter.  We'll start with the
 following state type:\footnote{You might wonder why we don't just use
-  \fw{Int}; the reason is because that's too general.  Other widgets
-  might represent the temperature with an \fw{Int}, and then your
-  counter API functions -- taking a widget of type \fw{Widget Int} --
-  would work on their widgets, which is probably not what you want!}
+  \fw{Int}, i.e., \fw{Widget Int}; the reason is because that's too
+  general.  Other widgets might represent the temperature with an
+  \fw{Int}, and then your counter API functions -- taking a widget of
+  type \fw{Widget Int} -- would work on other types of widgets, which
+  is probably not what you want!}
 
 \begin{haskellcode}
  data Counter = Counter Int
 \end{haskellcode}
 
 The next step is to write a widget constructor function.  This
-function will return a value of type \fw{Widget Counter}, which
-indicates that it is a \fw{Widget} with state type \fw{Counter}.
-We'll allow the constructor to take the counter's initial value.
-Here's what the function will look like in full:
+function will return a value of type \fw{Widget Counter}.  The
+constructor will take the counter's initial value.  Here's the
+function in full:
 
 \begin{haskellcode}
  newCounter :: Int -> IO (Widget Counter)
@@ -48,15 +48,15 @@
 where all of the widget logic is actually implemented.  You implement
 this logic by overriding the fields of the \fw{WidgetImpl} type, such
 as \fw{render\_}.  We call \fw{newWidget}'s result \fw{wRef} because
-it is a reference to a widget object, and this helps distinguish it
-from the actual widget data in the next step.
+it is a reference to a widget, and this helps distinguish it from the
+actual widget data in the next step.
 
 The \fw{newWidget} function takes an initial state of the widget (of
 type \fw{a}) and a transformation function \fw{WidgetImpl a ->
   WidgetImpl a}, creates a new \fw{WidgetImpl}, sets its \fw{state} to
 the initial state provided, and transforms it with the transformation
 function.  We do this to specify the behavior of the widget beyond the
-defaults, which are specified in the \fw{newWidget} function.
+defaults provided by the \fw{newWidget} function.
 
 Here is the \fw{render\_} function which will actually construct a Vty
 \fw{Image} to be displayed in the terminal:
@@ -65,14 +65,15 @@
  render_ =
    \this size ctx -> do
      (Counter v) <- getState this
-     let s = show v
-         width = fromEnum $ region_width size - length s
-         truncated = take width s
-     return $ string (getNormalAttr ctx) truncated
+     let s = T.pack $ show v
+         width = (fromEnum $ region_width size) -
+                 (fromEnum $ textWidth s)
+         (truncated, _, _) = clip1d (Phys 0) (Phys width) s
+     return $ string (getNormalAttr ctx) $ T.unpack truncated
 \end{haskellcode}
 
 The type of \fw{render\_} is \fw{Widget a -> DisplayRegion ->
-  RenderContext -> IO Image}.  The types are as follows:
+  RenderContext -> IO Image}.  The arguments are as follows:
 
 \begin{itemize}
 \item \fw{Widget a} - the widget being rendered, i.e., the \fw{Widget
@@ -88,8 +89,8 @@
   widget sizes are used to compute widget positions, so sizes must be
   accurate.
 
-  A widget may render to an \fw{Image} \textit{smaller} than the
-  specified size; many do.
+  A widget must render to an \fw{Image} \textit{no larger} than the
+  specified size.
 \item \fw{RenderContext} - the rendering context passed to \fw{runUi}
   as explained in Section \ref{sec:event_loop}.  In the \fw{render\_}
   function, we use this to determine which screen attributes to use.
@@ -114,17 +115,22 @@
 the latest state value at the time \fw{render\_} is called.
 
 \begin{haskellcode}
- let s = show v
-     width = fromEnum $ region_width size - length s
-     truncated = take width s
+ let s = T.pack $ show v
+     width = (fromEnum $ region_width size) -
+             (fromEnum $ textWidth s)
+     (truncated, _, _) = clip1d (Phys 0) (Phys width) s
 \end{haskellcode}
 
 To ensure that the \fw{Image} we generate does not exceed \fw{size} as
 described above, we use the width of the region to limit how many
-characters we take from the string representation of the counter.
+characters we take from the string representation of the counter.  We
+also introduce a function to calculate the width of our counter
+string, \fw{textWidth}, and a function to clip the string to the
+desired width, \fw{clip1d}.  For more information on text clipping,
+see Section \ref{sec:textclip}.
 
 \begin{haskellcode}
- return $ string (getNormalAttr ctx) truncated
+ return $ string (getNormalAttr ctx) $ T.unpack truncated
 \end{haskellcode}
 
 The \fw{string} function is a Vty library function which takes an
diff --git a/doc/ch3/widgetimpl_api.tex b/doc/ch3/widgetimpl_api.tex
--- a/doc/ch3/widgetimpl_api.tex
+++ b/doc/ch3/widgetimpl_api.tex
@@ -7,6 +7,7 @@
 \begin{haskellcode}
  data WidgetImpl a = WidgetImpl {
        state :: a
+     , visibie :: Bool
      , render_ :: Widget a -> DisplayRegion -> RenderContext
                -> IO Image
      , growHorizontal_ :: a -> IO Bool
@@ -39,6 +40,11 @@
 \item \fw{focused} -- \fw{True} if this widget is focused.  As
   explained in Section \ref{sec:focus}, although one widget has the
   user's focus, internally many widgets may share it in a hierarchy.
+\item \fw{visible} -- \fw{True} if this widget is visible.  Visible
+  widgets will be rendered as usual, but invisible widgets
+  automatically render to empty images and do not grow horizontally or
+  vertically.  This field can be manipulated with \fw{setVisible} and
+  read with \fw{getVisible}.
 \item \fw{currentSize} -- the ``current'' size of the widget, i.e.,
   the size of the \fw{Image} \textit{after} the last time the widget
   was rendered.
diff --git a/doc/ch4/DirBrowser.tex b/doc/ch4/DirBrowser.tex
--- a/doc/ch4/DirBrowser.tex
+++ b/doc/ch4/DirBrowser.tex
@@ -146,7 +146,9 @@
 
 \begin{haskellcode}
  browser `onBrowseAccept` \path ->
-   reportBrowserError browser $ "not a valid document: " ++ path
+   reportBrowserError browser $ T.concat [ "not a valid document: "
+                                         , T.pack path
+                                         ]
 \end{haskellcode}
 
 \subsubsection{Growth Policy}
diff --git a/doc/ch4/Edit.tex b/doc/ch4/Edit.tex
--- a/doc/ch4/Edit.tex
+++ b/doc/ch4/Edit.tex
@@ -77,11 +77,23 @@
   \fw{setEditLineLimit \$ Just 0} is a no-op.
 \end{itemize}
 
+\subsubsection{Wide Character Support}
+
+Some characters, such as those from some Asian character sets, require
+two columns instead of one when displayed in a terminal.  The
+\fw{Edit} widget supports such characters with one caveat: when a wide
+character straddles the left or right viewing boundary of an \fw{Edit}
+widget, an indicator (\fw{\$}) will be displayed in its place to
+indicate that a wide character lies on the boundary and can be
+revealed by scrolling further in the appropriate direction.  Such
+indicators are only visual and do not affect the underlying text, so
+e.g. calls to \fw{getEditText} will return the text as expected.
+
 \subsubsection{Growth Policy}
 
 Single-line \fw{Edit} widgets -- those created by \fw{editWidget} --
 grow only horizontally and are always one row high.  Multi-line edit
-widgets -- those created by \fw{multiLineEditWidget} -- always grow in
-both dimensions.  To manage this behavior, you can use one of the
-``fixed'' family of widgets to control their sizes (see Section
+widgets -- those created by \fw{multi\-Line\-Edit\-Widget} -- always
+grow in both dimensions.  To manage this behavior, you can use one of
+the ``fixed'' family of widgets to control their sizes (see Section
 \ref{sec:fixed}).
diff --git a/doc/ch4/FormattedText.tex b/doc/ch4/FormattedText.tex
--- a/doc/ch4/FormattedText.tex
+++ b/doc/ch4/FormattedText.tex
@@ -20,8 +20,8 @@
 The contents of a text widget can be set in one of three ways:
 \begin{itemize}
 \item Initially, as a parameter to \fw{plainText} and \fw{textWidget}
-\item As a \fw{String} parameter to \fw{setText}
-\item As a list parameter of \fw{(String, Attr)} with
+\item As a \fw{Text} parameter to \fw{setText}
+\item As a list parameter of \fw{(Text, Attr)} with
   \fw{setTextWithAttrs}
 \end{itemize}
 
@@ -32,8 +32,8 @@
 defaults to the ``normal'' attribute of the widget (see Section
 \ref{sec:attributes} for more information on attributes).
 
-The \fw{setText} function merely takes a \fw{String}, tokenizes it,
-and assigns the default attribute to all tokens.
+The \fw{setText} function merely takes a \fw{Text} value, tokenizes
+it, and assigns the default attribute to all tokens.
 
 The \fw{setTextWithAttrs} function provides finer control over the
 initial attribute assignment to the text because it lets you specify
@@ -70,7 +70,7 @@
 \end{haskellcode}
 
 In addition, the formatter for a text widget can be set at any time
-with \fw{setTextFormatter}:
+with \fw{set\-Text\-Formatter}:
 
 \begin{haskellcode}
  setTextFormatter t someFormatter
diff --git a/doc/ch5/TextClip.tex b/doc/ch5/TextClip.tex
new file mode 100644
--- /dev/null
+++ b/doc/ch5/TextClip.tex
@@ -0,0 +1,47 @@
+\section{Text Clipping}
+\label{sec:textclip}
+
+Most widgets in \vtyui\ render some form of text.  When we render
+text, we have to reason about the amount of space -- columns -- that
+the text will consume in the terminal so that we can render coherent
+interfaces.  However, this is tricky because some characters use one
+column of space and others use
+two.\footnote{\url{http://www.unicode.org/reports/tr11/}} To account
+for this possibility, any code which deals with computing the space
+required for text must consider the width of \textit{each character}.
+
+In cases where we have to consider character width, the most common
+operation we're trying to perform is to \textit{clip} a text string to
+ensure that it fits within a given region.  The \fw{TextClip} module
+provides types and functions to do this in one and two dimensions:
+
+\begin{itemize}
+\item \fw{ClipRect} - the type of two-dimensional clipping regions.
+  Allows you to specify a top-left corner, a clipping width, and a
+  clipping height.
+\item \fw{clip1d} - performs one-dimensional clipping on a single line
+  of text.
+\item \fw{clip2d} - performs two-dimensional clipping on a list of
+  lines of text using a \fw{Clip\-Rect}.
+\end{itemize}
+
+The functions in the \fw{TextClip} module deal in physical values
+expressed using the \fw{Phys} type.  This type designates a
+\textit{physical width} as opposed to a logical one.  We use this
+distinction to gain compile-time clarity about which integer values
+refer to logical characters and which ones refer to terminal column
+counts.
+
+Both the \fw{clip1d} and \fw{clip2d} functions return text strings
+truncated so that their characters fit into the specified physical
+space.  They also return \fw{Bool} indicators which can be used to
+determine whether clipping occurred in the ``middle'' of wide
+characters.  The \fw{Edit} widget uses this feature to annotate
+truncated strings to indicate that a wide character can be found on
+either end of a truncated line of text.
+
+In simple widgets, we could technically ignore text clipping details
+if we know that we'll always be rendering strings which use
+single-column characters.  However, we should get in the habit of
+always using clipping functions in case we need to start showing
+multi-column characters.
diff --git a/doc/ch5/TextZipper.tex b/doc/ch5/TextZipper.tex
new file mode 100644
--- /dev/null
+++ b/doc/ch5/TextZipper.tex
@@ -0,0 +1,67 @@
+\section{The Text Zipper}
+
+The \fw{TextZipper} module provides a
+\textit{zipper}\footnote{\url{http://www.haskell.org/haskellwiki/Zipper}}
+data structure to manage the text editing process of multi-line text
+buffers.  A \fw{TextZipper} stores text and a \textit{cursor position}
+at which editing operations are applied.  The zipper implementation
+works for any string representation type that provides implementations
+of \fw{drop}, \fw{take}, \fw{length}, \fw{last}, \fw{init}, and
+\fw{null}.  The module provides a default implementation (used by the
+\fw{Edit} widget) based on the \fw{Data.Text.Text} type.  Its
+constructor, \fw{textZipper}, takes a list of \fw{Text} values and
+creates a zipper.
+
+\begin{haskellcode}
+ let z = textZipper []
+\end{haskellcode}
+
+To extract the text content of the zipper, use \fw{getText}:
+
+\begin{haskellcode}
+ let theLines = getText z
+\end{haskellcode}
+
+The module provides the following \textit{editing transformations}:
+
+\begin{itemize}
+\item \fw{moveCursor (row, col)} - Move the cursor to the specified
+  cursor position.  Invalid positions will be ignored.
+\item \fw{insertChar c} - Insert \fw{c} at the cursor position.
+\item \fw{breakLine} - Insert a line break at the cursor position.
+\item \fw{killToEOL} - Kill (delete) all text after the cursor
+  position up to the end of the current line.
+\item \fw{gotoEOL} - Move the cursor past the end of the current line.
+\item \fw{gotoBOL} - Move the cursor to just before the beginning of
+  the current line.
+\item \fw{deletePrevChar} - Delete the character preceding the cursor
+  position.  If the cursor is at the beginning of a line, the current
+  line will be appended onto the previous line.
+\item \fw{deleteChar} - Delete the character at the cursor position.
+  If the cursor is at the end of a line, the following line will be
+  appended onto the current line.
+\item \fw{moveRight} - Move the cursor one position to the right,
+  wrapping to the following line if necessary.
+\item \fw{moveLeft} - Move the cursor one position to the left,
+  wrapping to the preceding line if necessary.
+\item \fw{moveUp} - Move the cursor up by one row.
+\item \fw{moveDown} - Move the cursor down by one row.
+\end{itemize}
+
+These transformations can be composed in natural ways to create a
+sequence of editing transformations.  For example:
+
+\begin{haskellcode}
+ import Control.Arrow
+
+ doEdits :: TextZipper a -> TextZipper a
+ doEdits = foldl (>>>) id [ moveRight
+                          , insertChar 'x'
+                          , insertChar 'z'
+                          , moveLeft
+                          , insertChar 'y'
+                          , deletePrevChar
+                          ]
+ -- Later:
+ let edited = doEdits $ textZipper theLines
+\end{haskellcode}
diff --git a/doc/ch5/main.tex b/doc/ch5/main.tex
new file mode 100644
--- /dev/null
+++ b/doc/ch5/main.tex
@@ -0,0 +1,8 @@
+\chapter{Other Topics}
+\label{chap:other}
+
+This chapter contains supplementary material on various aspects of
+\vtyui.
+
+\input{ch5/TextClip}
+\input{ch5/TextZipper}
diff --git a/doc/macros.tex b/doc/macros.tex
--- a/doc/macros.tex
+++ b/doc/macros.tex
@@ -1,6 +1,6 @@
 % Custom macros.
 
-\newcommand{\vtyuiversion}{1.5}
+\newcommand{\vtyuiversion}{1.6}
 
 \newcommand{\fw}[1]{\texttt{#1}}
 \newcommand{\vtyui}{\fw{vty-ui}}
diff --git a/doc/vty-ui-users-manual.tex b/doc/vty-ui-users-manual.tex
--- a/doc/vty-ui-users-manual.tex
+++ b/doc/vty-ui-users-manual.tex
@@ -33,5 +33,6 @@
 \include{ch2/main}
 \include{ch3/main}
 \include{ch4/main}
+\include{ch5/main}
 
 \end{document}
diff --git a/src/ComplexDemo.hs b/src/ComplexDemo.hs
deleted file mode 100644
--- a/src/ComplexDemo.hs
+++ /dev/null
@@ -1,163 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-do-bind #-}
-module Main where
-
-import System.Exit ( exitSuccess )
-import System.Locale
-import Control.Monad
-import Control.Concurrent
-import Data.Time.Clock
-import Data.Time.Format
-import Graphics.Vty hiding (pad)
-import Graphics.Vty.Widgets.All
-
--- Visual attributes.
-fg = white
-bg = black
-focAttr = black `on` yellow
-headerAttr = fgColor bright_green
-msgAttr = fgColor blue
-
--- Multi-state checkbox value type
-data FrostingType = Chocolate
-                  | Vanilla
-                  | Lemon
-                    deriving (Eq, Show)
-
-main :: IO ()
-main = do
-  let msg = "- <TAB> switches input elements\n\n\
-            \- ordinary keystrokes edit\n\n\
-            \- <SPC> toggles radio buttons and checkboxes\n\n\
-            \- <ESC> quits"
-
-      columns = [ column (ColFixed 25) `pad` (padAll 1)
-                , column ColAuto `pad` (padAll 1)
-                ]
-
-  table <- newTable columns BorderFull >>=
-           withNormalAttribute (bgColor blue) >>=
-           withBorderAttribute (fgColor green)
-
-  tw <- (textWidget wrap msg) >>= withNormalAttribute msgAttr
-  mainBox <- vBox table tw >>= withBoxSpacing 1
-
-  r1 <- newCheckbox "Cake"
-  r2 <- newCheckbox "Death"
-  radioHeader <- plainText "" >>= withNormalAttribute headerAttr
-
-  rg <- newRadioGroup
-  addToRadioGroup rg r1
-  addToRadioGroup rg r2
-
-  r3 <- newMultiStateCheckbox "Frosting" [ (Chocolate, 'C')
-                                         , (Vanilla, 'V')
-                                         , (Lemon, 'L')
-                                         ]
-
-  edit1 <- editWidget >>= withFocusAttribute (white `on` red)
-  edit2 <- multiLineEditWidget
-  edit2box <- boxFixed 30 3 edit2
-  setEditLineLimit edit2 $ Just 3
-
-  edit1Header <- textWidget wrap [] >>= withNormalAttribute headerAttr
-  edit2Header <- textWidget wrap [] >>= withNormalAttribute headerAttr
-
-  lst <- newList (fgColor bright_green)
-
-  selector <- vLimit 3 lst
-  listHeader <- plainText ""
-
-  rs <- vBox r1 r2
-
-  cbHeader <- plainText ""
-  timeText <- plainText ""
-
-  prog <- newProgressBar (white `on` red) (red `on` white)
-  setProgressTextAlignment prog AlignCenter
-
-  addHeadingRow_ table headerAttr ["Column 1", "Column 2"]
-  addRow table $ radioHeader .|. rs
-  addRow table $ cbHeader .|. r3
-  addRow table $ edit1Header .|. edit1
-  addRow table $ edit2Header .|. edit2box
-  addRow table $ listHeader .|. customCell selector `pad` padNone
-  addRow table $ emptyCell .|. timeText
-  addRow table $ emptyCell .|. prog
-
-  rg `onRadioChange` \cb -> do
-      s <- getCheckboxLabel cb
-      setText radioHeader $ s ++ ", please."
-
-  r3 `onCheckboxChange` \v ->
-      setText cbHeader $ "you chose: " ++ show v
-
-  prog `onProgressChange` \val ->
-      setProgressText prog $ "Progress (" ++ show val ++ " %)"
-
-  edit1 `onChange` (setText edit1Header)
-  edit2 `onChange` (setText edit2Header)
-
-  lst `onSelectionChange` \ev ->
-      case ev of
-        SelectionOn _ k _ -> setText listHeader $ "You selected: " ++ k
-        SelectionOff -> return ()
-
-  lst `onItemActivated` \(ActivateItemEvent _ s _) ->
-      setText listHeader $ "You activated: " ++ s
-
-  setEditText edit1 "Foo"
-  setEditText edit2 "Bar"
-  setCheckboxChecked r1
-
-  setCheckboxState r3 Chocolate
-  -- It would be nice if we didn't have to do this, but the
-  -- setCheckboxState call above will not notify any state-change
-  -- handlers because the state isn't actually changing (from its
-  -- original value of Chocolate, the first value in its state list).
-  setText cbHeader $ "you chose: Chocolate"
-
-  fgr <- newFocusGroup
-  fgr `onKeyPressed` \_ k _ -> do
-         case k of
-           KEsc -> exitSuccess
-           _ -> return False
-
-  let strs = [ "Cookies"
-             , "Cupcakes"
-             , "Twinkies"
-             , "M&Ms"
-             , "Fritos"
-             , "Cheetos"
-             ]
-
-  forM_ strs $ \s ->
-      (addToList lst s =<< plainText s)
-
-  addToFocusGroup fgr r1
-  addToFocusGroup fgr r2
-  addToFocusGroup fgr r3
-  addToFocusGroup fgr edit1
-  addToFocusGroup fgr edit2
-  addToFocusGroup fgr lst
-
-  ui <- centered =<< hLimit 70 mainBox
-
-  forkIO $ forever $ do
-         schedule $ do
-           t <- getCurrentTime
-           setText timeText $ formatTime defaultTimeLocale rfc822DateFormat t
-         threadDelay $ 1 * 1000 * 1000
-
-  forkIO $ forever $ do
-         let act i = do
-               threadDelay $ 1 * 1000 * 1000
-               schedule $ setProgress prog (i `mod` 101)
-               act $ i + 4
-         act 0
-
-  c <- newCollection
-  _ <- addToCollection c ui fgr
-
-  runUi c $ defaultContext { focusAttr = focAttr
-                           , normalAttr = fg `on` bg
-                           }
diff --git a/src/DialogDemo.hs b/src/DialogDemo.hs
deleted file mode 100644
--- a/src/DialogDemo.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
-module Main where
-
-import Graphics.Vty hiding (Button)
-import Graphics.Vty.Widgets.All
-
-main :: IO ()
-main = do
-  e <- editWidget
-  fg <- newFocusGroup
-  addToFocusGroup fg e
-
-  u <- plainText "Enter some text and press enter." <--> return e
-       >>= withBoxSpacing 1
-
-  pe <- padded u (padLeftRight 2)
-  (d, dFg) <- newDialog pe "<enter text>"
-  setNormalAttribute d (white `on` blue)
-
-  c <- centered =<< withPadding (padLeftRight 10) (dialogWidget d)
-
-  -- When the edit widget changes, set the dialog's title.
-  e `onChange` setDialogTitle d
-
-  -- When the user presses Enter in the edit widget, accept the
-  -- dialog.
-  e `onActivate` (const $ acceptDialog d)
-
-  -- Exit either way.
-  d `onDialogAccept` const shutdownUi
-  d `onDialogCancel` const shutdownUi
-
-  coll <- newCollection
-  _ <- addToCollection coll c =<< (mergeFocusGroups fg dFg)
-
-  runUi coll $ defaultContext { focusAttr = black `on` yellow }
-
-  (putStrLn . ("You entered: " ++)) =<< getEditText e
diff --git a/src/DirBrowserDemo.hs b/src/DirBrowserDemo.hs
deleted file mode 100644
--- a/src/DirBrowserDemo.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Main where
-
-import Graphics.Vty
-import Graphics.Vty.Widgets.All
-
-main :: IO ()
-main = do
-  (b, fg) <- newDirBrowser defaultBrowserSkin
-
-  c <- newCollection
-  _ <- addToCollection c (dirBrowserWidget b) fg
-
-  b `onBrowseAccept` error
-  b `onBrowseCancel` error
-
-  runUi c $ defaultContext { focusAttr = white `on` blue
-                           }
diff --git a/src/EditDemo.hs b/src/EditDemo.hs
deleted file mode 100644
--- a/src/EditDemo.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module Main where
-
-import Graphics.Vty hiding (Button)
-import Graphics.Vty.Widgets.All
-
-main :: IO ()
-main = do
-  e1 <- multiLineEditWidget
-  e2 <- multiLineEditWidget
-  setEditLineLimit e2 $ Just 3
-  e3 <- editWidget
-
-  fg <- newFocusGroup
-  _ <- addToFocusGroup fg e1
-  _ <- addToFocusGroup fg e2
-  _ <- addToFocusGroup fg e3
-
-  be1 <- bordered =<< boxFixed 40 5 e1
-  be2 <- bordered =<< boxFixed 40 3 e2
-  be3 <- bordered =<< boxFixed 40 1 e3
-
-  c <- centered =<< ((plainText "Multi-Line Editor (unlimited lines):")
-                         <--> (return be1)
-                         <--> (plainText "Multi-Line Editor (3 lines):")
-                         <--> (return be2)
-                         <--> (plainText "Single-Line Editor:")
-                         <--> (return be3)
-                         <--> (plainText "- Esc to quit\n\n- TAB to switch editors") >>= withBoxSpacing 1
-                    )
-
-  coll <- newCollection
-  _ <- addToCollection coll c fg
-
-  fg `onKeyPressed` \_ k _ ->
-      case k of
-        KEsc -> shutdownUi >> return True
-        _ -> return False
-
-  runUi coll $ defaultContext { focusAttr = fgColor yellow }
diff --git a/src/Graphics/Vty/Widgets/All.hs b/src/Graphics/Vty/Widgets/All.hs
--- a/src/Graphics/Vty/Widgets/All.hs
+++ b/src/Graphics/Vty/Widgets/All.hs
@@ -6,6 +6,8 @@
     , module Graphics.Vty.Widgets.List
     , module Graphics.Vty.Widgets.Borders
     , module Graphics.Vty.Widgets.Text
+    , module Graphics.Vty.Widgets.TextClip
+    , module Graphics.Vty.Widgets.TextZipper
     , module Graphics.Vty.Widgets.Edit
     , module Graphics.Vty.Widgets.Util
     , module Graphics.Vty.Widgets.Table
@@ -32,6 +34,8 @@
 import Graphics.Vty.Widgets.List
 import Graphics.Vty.Widgets.Borders
 import Graphics.Vty.Widgets.Text
+import Graphics.Vty.Widgets.TextClip
+import Graphics.Vty.Widgets.TextZipper
 import Graphics.Vty.Widgets.Edit
 import Graphics.Vty.Widgets.Util
 import Graphics.Vty.Widgets.Table
diff --git a/src/Graphics/Vty/Widgets/Borders.hs b/src/Graphics/Vty/Widgets/Borders.hs
--- a/src/Graphics/Vty/Widgets/Borders.hs
+++ b/src/Graphics/Vty/Widgets/Borders.hs
@@ -20,6 +20,8 @@
     )
 where
 
+import qualified Data.Text as T
+
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
 import Graphics.Vty.Widgets.Box
@@ -32,7 +34,7 @@
 class HasBorderAttr a where
     setBorderAttribute :: a -> Attr -> IO ()
 
-data HBorder = HBorder Attr String
+data HBorder = HBorder Attr T.Text
                deriving (Show)
 
 instance HasBorderAttr (Widget HBorder) where
@@ -45,23 +47,23 @@
 
 -- | Adds a label to a horizontal border.  The label will be
 -- horizontally centered.
-withHBorderLabel :: String -> Widget HBorder -> IO (Widget HBorder)
+withHBorderLabel :: T.Text -> Widget HBorder -> IO (Widget HBorder)
 withHBorderLabel label w = setHBorderLabel w label >> return w
 
 -- | Adds a label to a horizontal border.  The label will be
 -- horizontally centered.
-setHBorderLabel :: Widget HBorder -> String -> IO ()
+setHBorderLabel :: Widget HBorder -> T.Text -> IO ()
 setHBorderLabel w label =
     updateWidgetState w $ \(HBorder a _) -> HBorder a label
 
 -- | Adds a label to the top border of a bordered widget.  The label
 -- will be horizontally centered.
-withBorderedLabel :: String -> Widget (Bordered a) -> IO (Widget (Bordered a))
+withBorderedLabel :: T.Text -> Widget (Bordered a) -> IO (Widget (Bordered a))
 withBorderedLabel label w = setBorderedLabel w label >> return w
 
 -- | Adds a label to the top border of a bordered widget.  The label
 -- will be horizontally centered.
-setBorderedLabel :: Widget (Bordered a) -> String -> IO ()
+setBorderedLabel :: Widget (Bordered a) -> T.Text -> IO ()
 setBorderedLabel w label =
     updateWidgetState w $ \(Bordered a ch _) -> Bordered a ch label
 
@@ -69,7 +71,7 @@
 -- attribute and character.
 hBorder :: IO (Widget HBorder)
 hBorder = do
-  let initSt = HBorder def_attr ""
+  let initSt = HBorder def_attr T.empty
   wRef <- newWidget initSt $ \w ->
       w { growHorizontal_ = const $ return True
         , render_ = renderHBorder
@@ -85,22 +87,30 @@
                          , attr
                          , normalAttr ctx
                          ]
-      noTitle = char_fill attr' (skinHorizontal $ skin ctx) (region_width s) 1
-  case null str of
-    True -> return noTitle
-    False -> do
-      let title = " " ++ str ++ " "
-      case (toEnum $ length title) > region_width s of
-        True -> return noTitle
-        False -> do
-                  let remaining = region_width s - (toEnum $ length title)
-                      side1 = remaining `div` 2
-                      side2 = if remaining `mod` 2 == 0 then side1 else side1 + 1
-                  return $ horiz_cat [ char_fill attr' (skinHorizontal $ skin ctx) side1 1
-                                     , string attr' title
-                                     , char_fill attr' (skinHorizontal $ skin ctx) side2 1
-                                     ]
+      ch = skinHorizontal $ skin ctx
+      noTitle = T.pack $ replicate (fromEnum $ region_width s) ch
 
+  wStr <- case T.null str of
+            True -> return noTitle
+            False -> do
+              let title = T.concat [ T.pack " "
+                                   , str
+                                   , T.pack " "
+                                   ]
+              case (textWidth title) > (Phys $ fromEnum $ region_width s) of
+                True -> return noTitle
+                False -> do
+                       let remaining = (Phys $ fromEnum $ region_width s) - (textWidth title)
+                           Phys side1 = remaining `div` Phys 2
+                           side2 = if remaining `mod` Phys 2 == Phys 0 then side1 else side1 + 1
+                       return $ T.concat [ T.pack $ replicate side1 ch
+                                         , title
+                                         , T.pack $ replicate side2 ch
+                                         ]
+
+  w <- plainTextWithAttrs [(wStr, attr')]
+  render w s ctx
+
 data VBorder = VBorder Attr
                deriving (Show)
 
@@ -125,7 +135,7 @@
         }
   return wRef
 
-data Bordered a = (Show a) => Bordered Attr (Widget a) String
+data Bordered a = (Show a) => Bordered Attr (Widget a) T.Text
 
 instance Show (Bordered a) where
     show (Bordered attr _ l) = concat [ "Bordered { attr = "
@@ -142,7 +152,7 @@
 -- |Wrap a widget in a bordering box.
 bordered :: (Show a) => Widget a -> IO (Widget (Bordered a))
 bordered child = do
-  let initSt = Bordered def_attr child ""
+  let initSt = Bordered def_attr child T.empty
   wRef <- newWidget initSt $ \w ->
       w { growVertical_ = const $ growVertical child
         , growHorizontal_ = const $ growHorizontal child
@@ -189,10 +199,14 @@
   let adjusted = DisplayRegion (image_width childImage + 2)
                  (image_height childImage)
 
-  tlCorner <- plainText [skinCornerTL sk] >>= withNormalAttribute attr'
-  trCorner <- plainText [skinCornerTR sk] >>= withNormalAttribute attr'
-  blCorner <- plainText [skinCornerBL sk] >>= withNormalAttribute attr'
-  brCorner <- plainText [skinCornerBR sk] >>= withNormalAttribute attr'
+  tlCorner <- plainText (T.singleton $ skinCornerTL sk)
+              >>= withNormalAttribute attr'
+  trCorner <- plainText (T.singleton $ skinCornerTR sk)
+              >>= withNormalAttribute attr'
+  blCorner <- plainText (T.singleton $ skinCornerBL sk)
+              >>= withNormalAttribute attr'
+  brCorner <- plainText (T.singleton $ skinCornerBR sk)
+              >>= withNormalAttribute attr'
 
   hb <- hBorder >>= withHBorderLabel label
   setBorderAttribute hb attr'
diff --git a/src/Graphics/Vty/Widgets/Button.hs b/src/Graphics/Vty/Widgets/Button.hs
--- a/src/Graphics/Vty/Widgets/Button.hs
+++ b/src/Graphics/Vty/Widgets/Button.hs
@@ -11,6 +11,7 @@
     )
 where
 
+import qualified Data.Text as T
 import Graphics.Vty.Widgets.Core
 import Graphics.Vty.Widgets.Text
 import Graphics.Vty.Widgets.Padding
@@ -34,7 +35,7 @@
 pressButton b = fireEvent b (return . buttonPressedHandlers) b
 
 -- |Set the text label on a button.
-setButtonText :: Button -> String -> IO ()
+setButtonText :: Button -> T.Text -> IO ()
 setButtonText b s = setText (buttonText b) s
 
 instance HasNormalAttr Button where
@@ -44,7 +45,7 @@
     setFocusAttribute b a = setFocusAttribute (buttonWidget b) a
 
 -- |Create a button.  Get its underlying widget with 'buttonWidget'.
-newButton :: String -> IO Button
+newButton :: T.Text -> IO Button
 newButton msg = do
   t <- plainText msg
   setTextAppearFocused t True
@@ -53,6 +54,8 @@
        withPadding (padLeftRight 3) >>=
        withNormalAttribute (white `on` black) >>=
        withFocusAttribute (blue `on` white)
+
+  updateWidget w $ \st -> st { getCursorPosition_ = const $ return Nothing }
 
   hs <- newHandlers
 
diff --git a/src/Graphics/Vty/Widgets/CheckBox.hs b/src/Graphics/Vty/Widgets/CheckBox.hs
--- a/src/Graphics/Vty/Widgets/CheckBox.hs
+++ b/src/Graphics/Vty/Widgets/CheckBox.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable #-}
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable,
+  OverloadedStrings #-}
 -- |This module provides ''check box'' widgets and ''radio button''
 -- widgets.  In addition, this module provides a generalized
 -- ''multi-state'' check box type which allows you to set multiple
@@ -40,11 +41,13 @@
 import Data.IORef
 import Data.List ( findIndex )
 import Data.Maybe
+import qualified Data.Text as T
 import Control.Monad
 import Control.Exception
 import Data.Typeable
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
+import Graphics.Vty.Widgets.Text
 import Graphics.Vty.Widgets.Events
 import Graphics.Vty.Widgets.Util
 
@@ -126,9 +129,10 @@
                            , rightBracketChar :: Char
                            , checkboxStates :: [(a, Char)]
                            , currentState :: a
-                           , checkboxLabel :: String
+                           , checkboxLabel :: T.Text
                            , checkboxChangeHandlers :: Handlers a
                            , checkboxFrozen :: Bool
+                           , innerTextWidget :: Widget FormattedText
                            }
 
 instance Show a => Show (CheckBox a) where
@@ -141,12 +145,12 @@
                      ]
 
 -- |Create a new checkbox with the specified text label.
-newCheckbox :: String -> IO (Widget (CheckBox Bool))
+newCheckbox :: T.Text -> IO (Widget (CheckBox Bool))
 newCheckbox label = newMultiStateCheckbox label [(False, ' '), (True, 'x')]
 
 -- |Create a new multi-state checkbox.
 newMultiStateCheckbox :: (Eq a) =>
-                         String -- ^The checkbox label.
+                         T.Text -- ^The checkbox label.
                       -> [(a, Char)] -- ^The list of valid states that
                                      -- the checkbox can be in, along
                                      -- with the visual representation
@@ -155,6 +159,7 @@
 newMultiStateCheckbox _ [] = throw EmptyCheckboxStates
 newMultiStateCheckbox label states = do
   cchs <- newHandlers
+  t <- plainText ""
   let initSt = CheckBox { checkboxLabel = label
                         , checkboxChangeHandlers = cchs
                         , leftBracketChar = '['
@@ -162,36 +167,40 @@
                         , checkboxStates = states
                         , currentState = fst $ states !! 0
                         , checkboxFrozen = False
+                        , innerTextWidget = t
                         }
 
   wRef <- newWidget initSt $ \w ->
       w { getCursorPosition_ =
             \this -> do
               pos <- getCurrentPosition this
-              return $ Just (pos `plusWidth` 1)
+              ch <- leftBracketChar <~~ this
+              return $ Just (pos `plusWidth` (toEnum $ fromEnum $ chWidth ch))
 
         , keyEventHandler = radioKeyEvent
         , render_ =
             \this sz ctx -> do
-              f <- focused <~ this
               st <- getState this
-
-              let attr = mergeAttrs [ overrideAttr ctx
-                                    , normalAttr ctx
-                                    ]
-
-                  fAttr = if f
-                          then focusAttr ctx
-                          else attr
+              tw <- innerTextWidget <~~ this
 
-                  v = currentState st
+              let v = currentState st
                   ch = fromJust $ lookup v (checkboxStates st)
 
-                  s = [leftBracketChar st, ch, rightBracketChar st, ' '] ++
-                      (checkboxLabel st)
+                  s = T.concat [ T.pack [ leftBracketChar st
+                                        , ch
+                                        , rightBracketChar st
+                                        , ' '
+                                        ]
+                               , checkboxLabel st
+                               ]
 
-              return $ string fAttr $ take (fromEnum $ region_width sz) s
+              setText tw s
+              render tw sz ctx
         }
+
+  wRef `relayFocusEvents` t
+  setTextAppearFocused t True
+
   return wRef
 
 modifyElem :: [a] -> Int -> (a -> a) -> [a]
@@ -223,7 +232,7 @@
                                      }
 
 -- |Get a checkbox's text label.
-getCheckboxLabel :: Widget (CheckBox a) -> IO String
+getCheckboxLabel :: Widget (CheckBox a) -> IO T.Text
 getCheckboxLabel = (checkboxLabel <~~)
 
 radioKeyEvent :: (Eq a) => Widget (CheckBox a) -> Key -> [Modifier] -> IO Bool
diff --git a/src/Graphics/Vty/Widgets/Core.hs b/src/Graphics/Vty/Widgets/Core.hs
--- a/src/Graphics/Vty/Widgets/Core.hs
+++ b/src/Graphics/Vty/Widgets/Core.hs
@@ -31,6 +31,8 @@
     , growHorizontal
     , getCursorPosition
     , showWidget
+    , getVisible
+    , setVisible
     , (<~)
     , (<~~)
 
@@ -156,6 +158,8 @@
 data WidgetImpl a = WidgetImpl {
       state :: !a
     -- ^The state of the widget.
+    , visible :: !Bool
+    -- ^Whether the widget is visible.
     , render_ :: Widget a -> DisplayRegion -> RenderContext -> IO Image
     -- ^The rendering routine of the widget.  Takes the widget itself,
     -- a region indicating how much space the rendering process has to
@@ -221,19 +225,37 @@
                     , " }"
                     ]
 
+-- |Set the visibility of a widget.  Invisible widgets do not grow in
+-- either direction, always render to an empty image, and never
+-- declare a cursor position.
+setVisible :: Widget a -> Bool -> IO ()
+setVisible wRef v = updateWidget wRef $ \st -> st { visible = v }
+
+-- |Get the visibility of a widget.
+getVisible :: Widget a -> IO Bool
+getVisible = (visible <~)
+
 -- |Does a widget grow horizontally?
 growHorizontal :: Widget a -> IO Bool
 growHorizontal w = do
-  act <- growHorizontal_ <~ w
-  st <- state <~ w
-  act st
+  v <- visible <~ w
+  case v of
+    True -> do
+           act <- growHorizontal_ <~ w
+           st <- state <~ w
+           act st
+    False -> return False
 
 -- |Does a widget grow vertically?
 growVertical :: Widget a -> IO Bool
 growVertical w = do
-  act <- growVertical_ <~ w
-  st <- state <~ w
-  act st
+  v <- visible <~ w
+  case v of
+    True -> do
+           act <- growVertical_ <~ w
+           st <- state <~ w
+           act st
+    False -> return False
 
 -- |Render a widget.  This function should be called by widget
 -- implementations, since it does more than 'render_'; this function
@@ -249,23 +271,27 @@
 render wRef sz ctx = do
   impl <- readIORef wRef
 
-  -- Merge the override attributes with the context.  If the overrides
-  -- haven't been set (still def_attr), they will have no effect on
-  -- the context attributes.
-  norm <- normalAttribute <~ wRef
-  foc <- focusAttribute <~ wRef
-  let newCtx = ctx { normalAttr = mergeAttr norm $ normalAttr ctx
-                   , focusAttr = mergeAttr foc $ focusAttr ctx
-                   }
+  v <- visible <~ wRef
+  case v of
+    False -> return empty_image
+    True -> do
+           -- Merge the override attributes with the context.  If the
+           -- overrides haven't been set (still def_attr), they will
+           -- have no effect on the context attributes.
+           norm <- normalAttribute <~ wRef
+           foc <- focusAttribute <~ wRef
+           let newCtx = ctx { normalAttr = mergeAttr norm $ normalAttr ctx
+                            , focusAttr = mergeAttr foc $ focusAttr ctx
+                            }
 
-  img <- render_ impl wRef sz newCtx
-  let imgsz =  DisplayRegion (image_width img) (image_height img)
+           img <- render_ impl wRef sz newCtx
+           let imgsz =  DisplayRegion (image_width img) (image_height img)
 
-  when (image_width img > region_width sz ||
-        image_height img > region_height sz) $ throw $ ImageTooBig (show impl) sz imgsz
+           when (image_width img > region_width sz ||
+                 image_height img > region_height sz) $ throw $ ImageTooBig (show impl) sz imgsz
 
-  setCurrentSize wRef $ DisplayRegion (image_width img) (image_height img)
-  return img
+           setCurrentSize wRef $ DisplayRegion (image_width img) (image_height img)
+           return img
 
 -- |Render a widget and set its position after rendering is complete.
 -- This is exported for internal use; widget implementations should
@@ -326,6 +352,7 @@
                      , currentSize = DisplayRegion 0 0
                      , currentPosition = DisplayRegion 0 0
                      , focused = False
+                     , visible = True
                      , gainFocusHandlers = gfhs
                      , loseFocusHandlers = lfhs
                      , keyEventHandler = \_ _ _ -> return False
@@ -614,8 +641,12 @@
 -- |Get the desired cursor position, if any, for a widget.
 getCursorPosition :: Widget a -> IO (Maybe DisplayRegion)
 getCursorPosition wRef = do
-  ci <- getCursorPosition_ <~ wRef
-  ci wRef
+  v <- visible <~ wRef
+  case v of
+    True -> do
+           ci <- getCursorPosition_ <~ wRef
+           ci wRef
+    False -> return Nothing
 
 -- |Return the current focus entry of a focus group.
 currentEntry :: Widget FocusGroup -> IO (Widget FocusEntry)
diff --git a/src/Graphics/Vty/Widgets/Dialog.hs b/src/Graphics/Vty/Widgets/Dialog.hs
--- a/src/Graphics/Vty/Widgets/Dialog.hs
+++ b/src/Graphics/Vty/Widgets/Dialog.hs
@@ -13,6 +13,7 @@
     )
 where
 
+import qualified Data.Text as T
 import Graphics.Vty.Widgets.Centering
 import Graphics.Vty.Widgets.Button
 import Graphics.Vty.Widgets.Padding
@@ -22,7 +23,7 @@
 import Graphics.Vty.Widgets.Core
 
 data Dialog = Dialog { dialogWidget :: Widget (Bordered Padded)
-                     , setDialogTitle :: String -> IO ()
+                     , setDialogTitle :: T.Text -> IO ()
                      , dialogAcceptHandlers :: Handlers Dialog
                      , dialogCancelHandlers :: Handlers Dialog
                      }
@@ -33,10 +34,10 @@
 -- |Create a new dialog with the specified embedded interface and
 -- title.  Returns the dialog itself and the 'FocusGroup' to which its
 -- buttons were added, for use in your application.
-newDialog :: (Show a) => Widget a -> String -> IO (Dialog, Widget FocusGroup)
+newDialog :: (Show a) => Widget a -> T.Text -> IO (Dialog, Widget FocusGroup)
 newDialog body title = do
-  okB <- newButton "OK"
-  cancelB <- newButton "Cancel"
+  okB <- newButton $ T.pack "OK"
+  cancelB <- newButton $ T.pack "Cancel"
 
   buttonBox <- (return $ buttonWidget okB) <++> (return $ buttonWidget cancelB)
   setBoxSpacing buttonBox 4
diff --git a/src/Graphics/Vty/Widgets/DirBrowser.hs b/src/Graphics/Vty/Widgets/DirBrowser.hs
--- a/src/Graphics/Vty/Widgets/DirBrowser.hs
+++ b/src/Graphics/Vty/Widgets/DirBrowser.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 -- |This module provides a directory browser interface widget.  For
 -- full details, please see the Vty-ui User's Manual.
 module Graphics.Vty.Widgets.DirBrowser
@@ -21,6 +22,7 @@
 import qualified Data.Map as Map
 import qualified Control.Exception as E
 import Control.Monad
+import qualified Data.Text as T
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
 import Graphics.Vty.Widgets.List
@@ -79,11 +81,21 @@
                                -- ^Used for device entries.
                                , browserSockAttr :: Attr
                                -- ^Used for socket entries.
+                               , browserShowHeader :: Bool
+                               -- ^Whether the browser header should
+                               -- be shown.
+                               , browserShowFooter :: Bool
+                               -- ^Whether the browser footer should
+                               -- be shown.
                                , browserCustomAnnotations :: [ (FilePath -> FileStatus -> Bool
-                                                               , FilePath -> FileStatus -> IO String
+                                                               , FilePath -> FileStatus -> IO T.Text
                                                                , Attr)
                                                              ]
                                -- ^File annotations.
+                               , browserIncludeEntry :: FilePath -> FileStatus -> Bool
+                               -- ^The predicate which determines
+                               -- which entries get listed in the
+                               -- browser.
                                }
 
 -- |The default browser skin with (hopefully) sane attribute defaults.
@@ -97,12 +109,15 @@
                                  , browserNamedPipeAttr = fgColor yellow
                                  , browserCharDevAttr = fgColor red
                                  , browserSockAttr = fgColor magenta
+                                 , browserShowHeader = True
+                                 , browserShowFooter = True
                                  , browserCustomAnnotations = []
+                                 , browserIncludeEntry = (const . const) True
                                  }
 
 -- |Apply annotations to a browser skin.
 withAnnotations :: BrowserSkin
-                -> [(FilePath -> FileStatus -> Bool, FilePath -> FileStatus -> IO String, Attr)]
+                -> [(FilePath -> FileStatus -> Bool, FilePath -> FileStatus -> IO T.Text, Attr)]
                 -> BrowserSkin
 withAnnotations sk as = sk { browserCustomAnnotations = browserCustomAnnotations sk ++ as }
 
@@ -111,13 +126,15 @@
 newDirBrowser :: BrowserSkin -> IO (DirBrowser, Widget FocusGroup)
 newDirBrowser bSkin = do
   path <- getCurrentDirectory
-  pathWidget <- plainText ""
-  errorText <- plainText "" >>= withNormalAttribute (browserErrorAttr bSkin)
-  header <- ((plainText " Path: ") <++> (return pathWidget) <++> (hFill ' ' 1))
+  pathWidget <- plainText T.empty
+  errorText <- plainText T.empty >>= withNormalAttribute (browserErrorAttr bSkin)
+  header <- ((plainText " Path: ")
+             <++> (return pathWidget) <++> (hFill ' ' 1))
             >>= withNormalAttribute (browserHeaderAttr bSkin)
 
-  fileInfo <- plainText ""
-  footer <- ((plainText " ") <++> (return fileInfo) <++> (hFill ' ' 1) <++> (return errorText))
+  fileInfo <- plainText T.empty
+  footer <- ((plainText " ")
+             <++> (return fileInfo) <++> (hFill ' ' 1) <++> (return errorText))
             >>= withNormalAttribute (browserHeaderAttr bSkin)
 
   l <- newList (browserUnfocusedSelAttr bSkin)
@@ -145,8 +162,11 @@
 
   l `onKeyPressed` handleBrowserKey b
   l `onSelectionChange` (\e -> clearError b >> handleSelectionChange b e)
-  b `onBrowserPathChange` setText (dirBrowserPathDisplay b)
+  b `onBrowserPathChange` (setText (dirBrowserPathDisplay b) . T.pack)
 
+  setVisible header $ browserShowHeader bSkin
+  setVisible footer $ browserShowFooter bSkin
+
   fg <- newFocusGroup
   _ <- addToFocusGroup fg ui
 
@@ -156,11 +176,14 @@
 -- |Report an error in the browser's error-reporting area.  Useful for
 -- reporting application-specific errors with the user's file
 -- selection.
-reportBrowserError :: DirBrowser -> String -> IO ()
-reportBrowserError b msg = setText (dirBrowserErrorWidget b) $ "Error: " ++ msg
+reportBrowserError :: DirBrowser -> T.Text -> IO ()
+reportBrowserError b msg = setText (dirBrowserErrorWidget b) $
+                           T.concat [ "Error: "
+                                    , msg
+                                    ]
 
 clearError :: DirBrowser -> IO ()
-clearError b = setText (dirBrowserErrorWidget b) ""
+clearError b = setText (dirBrowserErrorWidget b) T.empty
 
 -- |Register handlers to be invoked when the user makes a selection.
 onBrowseAccept :: DirBrowser -> (FilePath -> IO ()) -> IO ()
@@ -189,21 +212,24 @@
 handleSelectionChange b ev = do
   case ev of
     SelectionOff -> setText (dirBrowserFileInfo b) "-"
-    SelectionOn _ path _ -> setText (dirBrowserFileInfo b) =<< getFileInfo b path
+    SelectionOn _ path _ -> setText (dirBrowserFileInfo b) =<<
+                            (getFileInfo b path)
 
-getFileInfo :: DirBrowser -> FilePath -> IO String
+getFileInfo :: DirBrowser -> FilePath -> IO T.Text
 getFileInfo b path = do
   cur <- getDirBrowserPath b
   let newPath = cur </> path
   st <- getSymbolicLinkStatus newPath
   (_, mkAnn) <- fileAnnotation (dirBrowserSkin b) st cur path
   ann <- mkAnn
-  return $ path ++ ": " ++ ann
+  return $ T.concat [ T.pack (path ++ ": ")
+                    , ann
+                    ]
 
-builtInAnnotations :: FilePath -> BrowserSkin -> [(FilePath -> FileStatus -> Bool, FilePath -> FileStatus -> IO String, Attr)]
+builtInAnnotations :: FilePath -> BrowserSkin -> [(FilePath -> FileStatus -> Bool, FilePath -> FileStatus -> IO T.Text, Attr)]
 builtInAnnotations cur sk =
     [ (\_ s -> isRegularFile s
-      , \_ s -> return $ "regular file, " ++
+      , \_ s -> return $ T.pack $ "regular file, " ++
                 (show $ fileSize s) ++ " bytes"
       , def_attr)
     , (\_ s -> isSymbolicLink s,
@@ -213,7 +239,7 @@
                       else do
                         linkPath <- readSymbolicLink p
                         canonicalizePath $ cur </> linkPath
-          return $ "symbolic link to " ++ linkDest)
+          return $ T.pack $ "symbolic link to " ++ linkDest)
       , browserLinkAttr sk)
     , (\_ s -> isDirectory s, \_ _ -> return "directory", browserDirAttr sk)
     , (\_ s -> isBlockDevice s, \_ _ -> return "block device", browserBlockDevAttr sk)
@@ -222,14 +248,14 @@
     , (\_ s -> isSocket s, \_ _ -> return "socket", browserSockAttr sk)
     ]
 
-fileAnnotation :: BrowserSkin -> FileStatus -> FilePath -> FilePath -> IO (Attr, IO String)
+fileAnnotation :: BrowserSkin -> FileStatus -> FilePath -> FilePath -> IO (Attr, IO T.Text)
 fileAnnotation sk st cur shortPath = do
   let fullPath = cur </> shortPath
 
       annotation = getAnnotation' fullPath st $ (browserCustomAnnotations sk) ++
                    (builtInAnnotations cur sk)
 
-      getAnnotation' _ _ [] = (def_attr, return "")
+      getAnnotation' _ _ [] = (def_attr, return T.empty)
       getAnnotation' pth stat ((f,mkAnn,a):rest) =
           if f pth stat
           then (a, mkAnn pth stat)
@@ -262,7 +288,7 @@
                       entries <- getDirectoryContents cPath
                       return (True, entries))
                      `E.catch` \e -> do
-                             reportBrowserError b (ioeGetErrorString e)
+                             reportBrowserError b (T.pack $ ioeGetErrorString e)
                              return (False, [])
 
   when res $ do
@@ -304,11 +330,13 @@
     forM_ entries $ \entry -> do
       let fullPath = cur </> entry
       f <- getSymbolicLinkStatus fullPath
-      (attr, _) <- fileAnnotation (dirBrowserSkin b) f cur entry
-      w <- plainText " " <++> plainText entry
-      addToList (dirBrowserList b) entry w
-      ch <- getSecondChild w
-      setNormalAttribute ch attr
+      when (browserIncludeEntry (dirBrowserSkin b) fullPath f) $
+           do
+             (attr, _) <- fileAnnotation (dirBrowserSkin b) f cur entry
+             w <- plainText " " <++> plainText (T.pack entry)
+             addToList (dirBrowserList b) entry w
+             ch <- getSecondChild w
+             setNormalAttribute ch attr
 
 descend :: DirBrowser -> Bool -> IO ()
 descend b shouldSelect = do
diff --git a/src/Graphics/Vty/Widgets/Edit.hs b/src/Graphics/Vty/Widgets/Edit.hs
--- a/src/Graphics/Vty/Widgets/Edit.hs
+++ b/src/Graphics/Vty/Widgets/Edit.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, CPP #-}
 -- |This module provides a text-editing widget.  Edit widgets can
 -- operate in single- and multi-line modes.
 --
@@ -19,6 +19,21 @@
 -- * @Del@ / @Control-d@ - delete the current character
 --
 -- * @Backspace@ - delete the previous character
+--
+-- Edit widgets may be configured with a line limit which limits the
+-- number of lines of text the widget will store.  It does not provide
+-- any limit control on the length of its lines, though.
+--
+-- Edit widgets support multi-column characters.  (For some
+-- information, see <http://www.unicode.org/reports/tr11/>.)  When the
+-- edit widget scrolling reaches a point where a wide character cannot
+-- be drawn because it is bisected by the editing window's boundary,
+-- it will be replaced with an indicator (\"$\") until the scrolling
+-- window is moved enough to reveal the character.  This is done to
+-- preserve the relative alignment of all of the rows in the widget in
+-- the presence of characters of different widths.  Note that this is
+-- a visual aid only and does not affect the underlying text content
+-- of the widget.
 module Graphics.Vty.Widgets.Edit
     ( Edit
     , editWidget
@@ -26,47 +41,44 @@
     , getEditText
     , getEditCurrentLine
     , setEditText
-    , setEditCursorPosition
     , getEditCursorPosition
+    , setEditCursorPosition
     , setEditLineLimit
     , getEditLineLimit
+    , applyEdit
     , onActivate
     , onChange
     , onCursorMove
+#ifdef TESTING
+    , doClipping
+    , indicatorChar
+#endif
     )
 where
 
 import Control.Applicative ((<$>))
 import Control.Monad
-import Data.List
+import qualified Data.Text as T
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
 import Graphics.Vty.Widgets.Events
 import Graphics.Vty.Widgets.Util
+import Graphics.Vty.Widgets.TextClip
+import qualified Graphics.Vty.Widgets.TextZipper as Z
 
-data Edit = Edit { currentText :: [String]
-                 , cursorRow :: Int
-                 , cursorColumn :: Int
-                 , displayStart :: Int
-                 , displayWidth :: Int
-                 , topRow :: Int
-                 , visibleRows :: Int
+data Edit = Edit { contents :: Z.TextZipper T.Text
+                 , clipRect :: ClipRect
                  , activateHandlers :: Handlers (Widget Edit)
-                 , changeHandlers :: Handlers String
+                 , changeHandlers :: Handlers T.Text
                  , cursorMoveHandlers :: Handlers (Int, Int)
                  , lineLimit :: Maybe Int
                  }
 
 instance Show Edit where
     show e = concat [ "Edit { "
-                    , "currentText = ", show $ currentText e
-                    , ", cursorColumn = ", show $ cursorColumn e
-                    , ", cursorRow = ", show $ cursorRow e
-                    , ", topRow = ", show $ topRow e
+                    , "contents = ", show $ contents e
                     , ", lineLimit = ", show $ lineLimit e
-                    , ", visibleRows = ", show $ visibleRows e
-                    , ", displayStart = ", show $ displayStart e
-                    , ", displayWidth = ", show $ displayWidth e
+                    , ", clipRect = ", show $ clipRect e
                     , " }"
                     ]
 
@@ -76,13 +88,12 @@
   chs <- newHandlers
   cmhs <- newHandlers
 
-  let initSt = Edit { currentText = [""]
-                    , cursorRow = 0
-                    , cursorColumn = 0
-                    , displayStart = 0
-                    , displayWidth = 0
-                    , topRow = 0
-                    , visibleRows = 1
+  let initSt = Edit { contents = Z.textZipper []
+                    , clipRect = ClipRect { clipLeft = 0
+                                          , clipWidth = 0
+                                          , clipTop = 0
+                                          , clipHeight = 1
+                                          }
                     , activateHandlers = ahs
                     , changeHandlers = chs
                     , cursorMoveHandlers = cmhs
@@ -97,57 +108,72 @@
                 Just v | v == 1 -> return False
                 _ -> return True
 
-        , getCursorPosition_ =
-            \this -> do
-              f <- focused <~ this
-              pos <- getCurrentPosition this
-              curRow <- cursorRow <~~ this
-              curCol <- cursorColumn <~~ this
-              startCol <- displayStart <~~ this
-              startRow <- topRow <~~ this
+        , getCursorPosition_ = internalGetCursorPosition
+        , render_ = renderEditWidget
+        , keyEventHandler = editKeyEvent
+        }
+  return wRef
 
-              if f then
-                  return (Just $ pos `plusWidth` (toEnum (curCol - startCol)) `plusHeight` (toEnum (curRow - startRow))) else
-                  return Nothing
+internalGetCursorPosition :: Widget Edit -> IO (Maybe DisplayRegion)
+internalGetCursorPosition this = do
+  st <- getState this
+  f <- focused <~ this
+  pos <- getCurrentPosition this
 
-        , render_ =
-            \this size ctx -> do
-              resize this ( fromEnum $ region_height size
-                          , fromEnum $ region_width size )
+  let (cursorRow, _) = Z.cursorPosition (contents st)
+      Phys offset = physCursorCol st - clipLeft (clipRect st)
+      newPos = pos
+               `withWidth` (toEnum ((fromEnum $ region_width pos) + offset))
+               `plusHeight` (toEnum (cursorRow - (fromEnum $ clipTop $ clipRect st)))
 
-              st <- getState this
+  return $ if f then Just newPos else Nothing
 
-              let truncated l = take (displayWidth st)
-                                (drop (displayStart st) l)
+renderEditWidget :: Widget Edit -> DisplayRegion -> RenderContext -> IO Image
+renderEditWidget this size ctx = do
+  resize this ( Phys $ fromEnum $ region_height size
+              , Phys $ fromEnum $ region_width size )
 
-                  visibleLines = take (visibleRows st) $
-                                 drop (topRow st) (currentText st)
-                  truncatedLines = truncated <$> visibleLines
+  st <- getState this
+  isFocused <- focused <~ this
 
-                  nAttr = mergeAttrs [ overrideAttr ctx
-                                     , normalAttr ctx
-                                     ]
+  let nAttr = mergeAttrs [ overrideAttr ctx
+                         , normalAttr ctx
+                         ]
+      attr = if isFocused then focusAttr ctx else nAttr
 
-                  totalAllowedLines = fromEnum $ region_height size
-                  numEmptyLines = lim - length truncatedLines
-                      where
-                        lim = case lineLimit st of
-                                Just v -> min v totalAllowedLines
-                                Nothing -> totalAllowedLines
+      clipped = doClipping (Z.getText $ contents st) (clipRect st)
 
-                  emptyLines = replicate numEmptyLines ""
+      totalAllowedLines = fromEnum $ region_height size
+      numEmptyLines = lim - length clipped
+          where
+            lim = case lineLimit st of
+                    Just v -> min v totalAllowedLines
+                    Nothing -> totalAllowedLines
 
-              isFocused <- focused <~ this
-              let attr = if isFocused then focusAttr ctx else nAttr
-                  lineWidget s = string attr s
-                                 <|> char_fill attr ' ' (region_width size - (toEnum $ length s)) 1
+      emptyLines = replicate numEmptyLines ""
+      lineWidget s = let Phys physLineLength = sum $ chWidth <$> s
+                     in string attr s <|>
+                        char_fill attr ' ' (region_width size - toEnum physLineLength) 1
 
-              return $ vert_cat $ lineWidget <$> (truncatedLines ++ emptyLines)
+  return $ vert_cat $ lineWidget <$> (clipped ++ emptyLines)
 
-        , keyEventHandler = editKeyEvent
-        }
-  return wRef
+doClipping :: [T.Text] -> ClipRect -> [String]
+doClipping ls rect =
+    let sliced True = [indicatorChar]
+        sliced False = ""
+        truncatedLines = clip2d rect ls
 
+    in [ sliced lslice ++ (T.unpack r) ++ sliced rslice
+         | (r, lslice, rslice) <- truncatedLines ]
+
+-- |Convert a logical column number (corresponding to a character) to
+-- a physical column number (corresponding to a terminal cell).
+toPhysical :: Int -> [Char] -> Phys
+toPhysical col line = sum $ chWidth <$> take col line
+
+indicatorChar :: Char
+indicatorChar = '$'
+
 -- |Construct a text widget for editing a single line of text.
 -- Single-line edit widgets will send activation events when the user
 -- presses @Enter@ (see 'onActivate').
@@ -168,9 +194,9 @@
   setEditLineLimit wRef Nothing
   return wRef
 
--- |Set the limit on the number of lines for the edit widget.  Nothing
--- indicates no limit, while Just indicates a limit of the specified
--- number of lines.
+-- |Set the limit on the number of lines for the edit widget.
+-- 'Nothing' indicates no limit, while 'Just' indicates a limit of the
+-- specified number of lines.
 setEditLineLimit :: Widget Edit -> Maybe Int -> IO ()
 setEditLineLimit _ (Just v) | v <= 0 = return ()
 setEditLineLimit w v = updateWidgetState w $ \st -> st { lineLimit = v }
@@ -179,11 +205,34 @@
 getEditLineLimit :: Widget Edit -> IO (Maybe Int)
 getEditLineLimit = (lineLimit <~~)
 
-resize :: Widget Edit -> (Int, Int) -> IO ()
+resize :: Widget Edit -> (Phys, Phys) -> IO ()
 resize e (newHeight, newWidth) = do
-  updateWidgetState e $ \st -> st { visibleRows = newHeight }
-  setDisplayWidth e newWidth
+  updateWidgetState e $ \st ->
+      let newRect = (clipRect st) { clipHeight = newHeight
+                                  , clipWidth = newWidth
+                                  }
+          (cursorRow, _) = Z.cursorPosition $ contents st
+          adjusted = updateRect (Phys cursorRow, physCursorCol st) newRect
+      in st { clipRect = adjusted }
 
+  updateWidgetState e $ \s ->
+      let r = clipRect s
+          (_, cursorColumn) = Z.cursorPosition $ contents s
+          curLine = T.unpack $ Z.currentLine $ contents s
+          (_, _, ri) = clip1d (clipLeft r) (clipWidth r) (T.pack curLine)
+          newCharLen = if cursorColumn >= 0 && cursorColumn < length curLine
+                       then chWidth $ curLine !! cursorColumn
+                       else Phys 1
+
+          newPhysCol = toPhysical cursorColumn curLine
+          extra = if ri && newPhysCol >= ((clipLeft r) + (clipWidth r) - Phys 1)
+                  then newCharLen - 1
+                  else 0
+          newLeft = clipLeft (clipRect s) + extra
+      in s { clipRect = (clipRect s) { clipLeft = newLeft
+                                     }
+           }
+
 -- |Register handlers to be invoked when the edit widget has been
 -- ''activated'' (when the user presses Enter while the widget is
 -- focused).  These handlers will only be invoked when a single-line
@@ -207,322 +256,102 @@
 
 -- |Register handlers to be invoked when the edit widget's contents
 -- change.  Handlers will be passed the new contents.
-onChange :: Widget Edit -> (String -> IO ()) -> IO ()
+onChange :: Widget Edit -> (T.Text -> IO ()) -> IO ()
 onChange = addHandler (changeHandlers <~~)
 
 -- |Register handlers to be invoked when the edit widget's cursor
 -- position changes.  Handlers will be passed the new cursor position,
--- relative to the beginning of the string (position 0).
+-- relative to the beginning of the text (position (0, 0)).
 onCursorMove :: Widget Edit -> ((Int, Int) -> IO ()) -> IO ()
 onCursorMove = addHandler (cursorMoveHandlers <~~)
 
 -- |Get the current contents of the edit widget.  This returns all of
 -- the lines of text in the widget, separated by newlines.
-getEditText :: Widget Edit -> IO String
-getEditText = (((intercalate "\n") . currentText) <~~)
+getEditText :: Widget Edit -> IO T.Text
+getEditText = (((T.intercalate (T.pack "\n")) . Z.getText . contents) <~~)
 
 -- |Get the contents of the current line of the edit widget (the line
 -- on which the cursor is positioned).
-getEditCurrentLine :: Widget Edit -> IO String
-getEditCurrentLine e = do
-  ls <- currentText <~~ e
-  curL <- cursorRow <~~ e
-  return $ ls !! curL
-
-setEditCurrentLine :: Widget Edit -> String -> IO ()
-setEditCurrentLine e s = do
-  ls <- currentText <~~ e
-  curL <- cursorRow <~~ e
-
-  updateWidgetState e $ \st ->
-      st { currentText = repl curL s ls
-         }
+getEditCurrentLine :: Widget Edit -> IO T.Text
+getEditCurrentLine = ((Z.currentLine . contents) <~~)
 
 -- |Set the contents of the edit widget.  Newlines will be used to
 -- break up the text in multiline widgets.  If the edit widget has a
 -- line limit, only those lines within the limit will be set.
-setEditText :: Widget Edit -> String -> IO ()
+setEditText :: Widget Edit -> T.Text -> IO ()
 setEditText wRef str = do
-  oldS <- currentText <~~ wRef
   lim <- lineLimit <~~ wRef
-  s <- case lim of
-    Nothing -> return str
-    Just l -> return $ intercalate "\n" $ take l $ lines str
-  updateWidgetState wRef $ \st -> st { currentText = if null s
-                                                     then [""]
-                                                     else lines s
-                                     , cursorColumn = 0
-                                     , cursorRow = 0
+  let ls = case lim of
+             Nothing -> T.lines str
+             Just l -> take l $ T.lines str
+  updateWidgetState wRef $ \st -> st { contents = Z.textZipper ls
                                      }
-  when (oldS /= lines s) $ do
-    gotoBeginning wRef
-    notifyChangeHandlers wRef
+  notifyChangeHandlers wRef
 
--- |Set the current edit widget cursor position.  The tuple is (row,
--- column) with each starting at zero.  Invalid cursor positions will
--- be ignored.
-setEditCursorPosition :: Widget Edit -> (Int, Int) -> IO ()
-setEditCursorPosition wRef (newRow, newCol) = do
-  ls <- currentText <~~ wRef
+-- |Get the edit widget's current cursor position (row, column).
+getEditCursorPosition :: Widget Edit -> IO (Int, Int)
+getEditCursorPosition = ((Z.cursorPosition . contents) <~~)
 
-  -- First, check that the row is valid
-  case newRow >= 0 && newRow < (length ls) of
-    False -> return ()
-    True -> do
-      -- Then, if the row is valid, is the column valid for that row?
-      -- It's legal for the new position to be *after* the last
-      -- character (i.e., in the case of go-to-end)
-      case newCol >= 0 && newCol <= (length (ls !! newRow)) of
-        False -> return ()
-        True -> do
-              (oldRow, oldCol) <- getEditCursorPosition wRef
-              when ((newRow, newCol) /= (oldRow, oldCol)) $
-                   do
-                     st <- getState wRef
+-- |Set the cursor position to the specified row and column.  Invalid
+-- cursor positions will be ignored.
+setEditCursorPosition :: (Int, Int) -> Widget Edit -> IO ()
+setEditCursorPosition pos = applyEdit (Z.moveCursor pos)
 
-                     let newDisplayStart = if newCol >= (displayStart st + displayWidth st)
-                                           then newCol - displayWidth st + 1
-                                           else if newCol < displayStart st
-                                                then newCol
-                                                else displayStart st
-                         newTopRow = if newRow < topRow st
-                                     then newRow
-                                     else if newRow >= (topRow st + visibleRows st)
-                                          then newRow - visibleRows st + 1
-                                          else topRow st
+-- |Compute the physical cursor position (column) for the cursor in a
+-- given edit widget state.  The physical position is relative to the
+-- beginning of the current line (i.e., zero, as opposed to the
+-- displayStart and related state).
+physCursorCol :: Edit -> Phys
+physCursorCol s =
+    let curLine = T.unpack $ Z.currentLine $ contents s
+        (_, cursorColumn) = Z.cursorPosition $ contents s
+    in toPhysical cursorColumn curLine
 
-                     updateWidgetState wRef $ \s ->
-                         s { displayStart = newDisplayStart
-                           , topRow = newTopRow
-                           }
+-- |Apply an editing transformation to the edit widget's text.  If the
+-- transformation modifies the text or the cursor, the appropriate
+-- event handlers will be notified.  If a line limit is in effect and
+-- the transformation violates it, the transformation will be ignored.
+applyEdit :: (Z.TextZipper T.Text -> Z.TextZipper T.Text)
+          -> Widget Edit
+          -> IO ()
+applyEdit f this = do
+  oldC <- contents <~~ this
+  updateWidgetState this $ \s ->
+      let newSt = s { contents = f (contents s) }
+      in case lineLimit s of
+           Nothing -> newSt
+           Just l -> if length (Z.getText $ contents newSt) > l
+                     then s
+                     else newSt
 
-                     updateWidgetState wRef $ \s ->
-                         s { cursorRow = newRow
-                           , cursorColumn = newCol
-                           }
-                     notifyCursorMoveHandlers wRef
+  newC <- contents <~~ this
 
--- |Get the edit widget's current cursor position (row, column).
-getEditCursorPosition :: Widget Edit -> IO (Int, Int)
-getEditCursorPosition e = do
-  r <- cursorRow <~~ e
-  c <- cursorColumn <~~ e
-  return (r, c)
+  when (Z.getText oldC /= Z.getText newC) $
+       notifyChangeHandlers this
 
-setDisplayWidth :: Widget Edit -> Int -> IO ()
-setDisplayWidth this width =
-    updateWidgetState this $ \s ->
-        let newDispStart = if cursorColumn s - displayStart s >= width
-                           then cursorColumn s - width + 1
-                           else displayStart s
-        in s { displayWidth = width
-             , displayStart = newDispStart
-             }
+  when (Z.cursorPosition oldC /= Z.cursorPosition newC) $
+       notifyCursorMoveHandlers this
 
 editKeyEvent :: Widget Edit -> Key -> [Modifier] -> IO Bool
 editKeyEvent this k mods = do
+  let run f = applyEdit f this >> return True
   case (k, mods) of
-    (KASCII 'a', [MCtrl]) -> gotoBeginning this >> return True
-    (KASCII 'k', [MCtrl]) -> killToEOL this >> return True
-    (KASCII 'e', [MCtrl]) -> gotoEnd this >> return True
-    (KASCII 'd', [MCtrl]) -> delCurrentChar this >> return True
-    (KLeft, []) -> moveCursorLeft this >> return True
-    (KRight, []) -> moveCursorRight this >> return True
-    (KUp, []) -> moveCursorUp this >> return True
-    (KDown, []) -> moveCursorDown this >> return True
-    (KBS, []) -> deletePreviousChar this >> return True
-    (KDel, []) -> delCurrentChar this >> return True
-    (KASCII ch, []) -> insertChar this ch >> return True
-    (KHome, []) -> gotoBeginning this >> return True
-    (KEnd, []) -> gotoEnd this >> return True
+    (KASCII 'a', [MCtrl]) -> run Z.gotoBOL
+    (KASCII 'k', [MCtrl]) -> run Z.killToEOL
+    (KASCII 'e', [MCtrl]) -> run Z.gotoEOL
+    (KASCII 'd', [MCtrl]) -> run Z.deleteChar
+    (KLeft, []) -> run Z.moveLeft
+    (KRight, []) -> run Z.moveRight
+    (KUp, []) -> run Z.moveUp
+    (KDown, []) -> run Z.moveDown
+    (KBS, []) -> run Z.deletePrevChar
+    (KDel, []) -> run Z.deleteChar
+    (KASCII ch, []) -> run (Z.insertChar ch)
+    (KHome, []) -> run Z.gotoBOL
+    (KEnd, []) -> run Z.gotoEOL
     (KEnter, []) -> do
                    lim <- lineLimit <~~ this
                    case lim of
                      Just 1 -> notifyActivateHandlers this >> return True
-                     _ -> insertLineAtPoint this >> return True
+                     _ -> run Z.breakLine
     _ -> return False
-
-insertLineAtPoint :: Widget Edit -> IO ()
-insertLineAtPoint e = do
-  -- Bail if adding a new line would violate the line limit
-  lim <- lineLimit <~~ e
-  numLines <- (length . currentText) <~~ e
-
-  let continue = case lim of
-                   Just v | numLines + 1 > v -> False
-                   _ -> True
-
-  when continue $
-       do
-         -- Get information about current line so we can break the
-         -- current line
-         curL <- getEditCurrentLine e
-         curCol <- cursorColumn <~~ e
-         curRow <- cursorRow <~~ e
-         let r1 = take curCol curL
-             r2 = drop curCol curL
-         setEditCurrentLine e r1
-         updateWidgetState e $ \st ->
-             st { currentText = inject (curRow + 1) r2 (currentText st)
-                }
-         notifyChangeHandlers e
-         setEditCursorPosition e (curRow + 1, 0)
-
-killToEOL :: Widget Edit -> IO ()
-killToEOL this = do
-  -- Preserve some state since setEditText changes it.
-  curCol <- cursorColumn <~~ this
-  curLine <- getEditCurrentLine this
-  case null curLine of
-    False -> do
-      setEditCurrentLine this $ take curCol curLine
-      notifyChangeHandlers this
-    True -> do
-      curRow <- cursorRow <~~ this
-      numLines <- (length . currentText) <~~ this
-      if curRow == 0 && numLines == 1 then
-          return () else
-          do
-            let newRow = if curRow == numLines - 1 && numLines > 1
-                         then curRow - 1
-                         else curRow
-            updateWidgetState this $ \st ->
-                st { currentText = remove curRow (currentText st)
-                   }
-            notifyChangeHandlers this
-            setEditCursorPosition this (newRow, 0)
-
-deletePreviousChar :: Widget Edit -> IO ()
-deletePreviousChar this = do
-  curCol <- cursorColumn <~~ this
-  curRow <- cursorRow <~~ this
-  case curCol == 0 of
-    True ->
-        if curRow == 0
-        then return ()
-        else do
-          curLine <- getEditCurrentLine this
-          ls <- currentText <~~ this
-          let prevLine = ls !! (curRow - 1)
-          updateWidgetState this $ \st ->
-              st { currentText = repl (curRow - 1) (prevLine ++ curLine)
-                                 $ remove curRow (currentText st)
-                 }
-          setEditCursorPosition this (curRow - 1, length prevLine)
-          notifyChangeHandlers this
-
-    False -> do
-      moveCursorLeft this
-      delCurrentChar this
-
-gotoBeginning :: Widget Edit -> IO ()
-gotoBeginning wRef = do
-  updateWidgetState wRef $ \s -> s { displayStart = 0
-                                   }
-  curL <- cursorRow <~~ wRef
-  setEditCursorPosition wRef (curL, 0)
-
-gotoEnd :: Widget Edit -> IO ()
-gotoEnd wRef = do
-  curLine <- getEditCurrentLine wRef
-  curRow <- cursorRow <~~ wRef
-  setEditCursorPosition wRef (curRow, length curLine)
-
-moveCursorUp :: Widget Edit -> IO ()
-moveCursorUp wRef = do
-  st <- getState wRef
-  let newRow = if cursorRow st == 0
-               then 0
-               else cursorRow st - 1
-
-      prevLine = currentText st !! (cursorRow st - 1)
-      newCol = if cursorRow st == 0 || (cursorColumn st <= length prevLine)
-               then cursorColumn st
-               else length prevLine
-
-  setEditCursorPosition wRef (newRow, newCol)
-
-moveCursorDown :: Widget Edit -> IO ()
-moveCursorDown wRef = do
-  st <- getState wRef
-  let newRow = if cursorRow st == (length $ currentText st) - 1
-               then (length $ currentText st) - 1
-               else cursorRow st + 1
-
-      nextLine = currentText st !! (cursorRow st + 1)
-      newCol = if cursorRow st == (length $ currentText st) - 1
-               then cursorColumn st
-               else if cursorColumn st <= length nextLine
-                    then cursorColumn st
-                    else length nextLine
-
-  setEditCursorPosition wRef (newRow, newCol)
-
-moveCursorLeft :: Widget Edit -> IO ()
-moveCursorLeft wRef = do
-  st <- getState wRef
-  let newRow = if cursorRow st == 0
-               then 0
-               else if cursorColumn st == 0
-                    then cursorRow st - 1
-                    else cursorRow st
-      prevLine = currentText st !! (cursorRow st - 1)
-      newCol = if cursorColumn st == 0
-               then if cursorRow st == 0
-                    then 0
-                    else length prevLine
-               else cursorColumn st - 1
-  setEditCursorPosition wRef (newRow, newCol)
-
-moveCursorRight :: Widget Edit -> IO ()
-moveCursorRight wRef = do
-  st <- getState wRef
-  curL <- getEditCurrentLine wRef
-  let newRow = if cursorRow st == (length $ currentText st) - 1
-               then cursorRow st
-               else if cursorColumn st == length curL
-                    then cursorRow st + 1
-                    else cursorRow st
-      newCol = if cursorColumn st == length curL
-               then if cursorRow st == (length $ currentText st) - 1
-                    then cursorColumn st
-                    else 0
-               else cursorColumn st + 1
-  setEditCursorPosition wRef (newRow, newCol)
-
-insertChar :: Widget Edit -> Char -> IO ()
-insertChar wRef ch = do
-  curLine <- getEditCurrentLine wRef
-  updateWidgetState wRef $ \st ->
-      let newLine = inject (cursorColumn st) ch curLine
-          newViewStart =
-              if cursorColumn st == displayStart st + displayWidth st - 1
-              then displayStart st + 1
-              else displayStart st
-      in st { displayStart = newViewStart
-            , currentText = repl (cursorRow st) newLine (currentText st)
-            }
-  moveCursorRight wRef
-  notifyChangeHandlers wRef
-
-delCurrentChar :: Widget Edit -> IO ()
-delCurrentChar wRef = do
-  st <- getState wRef
-  curLine <- getEditCurrentLine wRef
-  case cursorColumn st < (length curLine) of
-    True ->
-        do
-          let newLine = remove (cursorColumn st) curLine
-          updateWidgetState wRef $ \s -> s { currentText = repl (cursorRow st) newLine (currentText st) }
-          notifyChangeHandlers wRef
-    False ->
-        -- If we are on the last line, do nothing, but if we aren't,
-        -- combine the next line with the current one
-        if cursorRow st == (length $ currentText st) - 1
-        then return ()
-        else do
-          let nextLine = currentText st !! (cursorRow st + 1)
-          updateWidgetState wRef $ \s ->
-              s { currentText = remove (cursorRow s + 1) $
-                                repl (cursorRow st) (curLine ++ nextLine) (currentText s)
-                }
diff --git a/src/Graphics/Vty/Widgets/List.hs b/src/Graphics/Vty/Widgets/List.hs
--- a/src/Graphics/Vty/Widgets/List.hs
+++ b/src/Graphics/Vty/Widgets/List.hs
@@ -14,7 +14,7 @@
     , SelectionEvent(..)
     , ActivateItemEvent(..)
     -- ** List creation
-    , newStringList
+    , newTextList
     , newList
     , addToList
     , insertIntoList
@@ -42,6 +42,7 @@
 import Data.Typeable
 import Control.Exception hiding (Handler)
 import Control.Monad
+import qualified Data.Text as T
 import qualified Data.Vector as V
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
@@ -419,13 +420,13 @@
 
   return $ vert_cat (visible_imgs ++ [filler])
 
--- |A convenience function to create a new list using 'String's as the
--- internal values and 'FormattedText' widgets to represent those
+-- |A convenience function to create a new list using 'Text' values as
+-- the internal values and 'FormattedText' widgets to represent those
 -- strings.
-newStringList :: Attr -- ^The attribute of the selected item
-              -> [String] -- ^The list items
-              -> IO (Widget (List String FormattedText))
-newStringList selAttr labels = do
+newTextList :: Attr -- ^The attribute of the selected item
+            -> [T.Text] -- ^The list items
+            -> IO (Widget (List T.Text FormattedText))
+newTextList selAttr labels = do
   list <- newList selAttr
   forM_ labels $ \l ->
       (addToList list l =<< plainText l)
diff --git a/src/Graphics/Vty/Widgets/ProgressBar.hs b/src/Graphics/Vty/Widgets/ProgressBar.hs
--- a/src/Graphics/Vty/Widgets/ProgressBar.hs
+++ b/src/Graphics/Vty/Widgets/ProgressBar.hs
@@ -15,16 +15,23 @@
 where
 
 import Control.Monad
+import qualified Data.Text as T
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
 import Graphics.Vty.Widgets.Events
 import Graphics.Vty.Widgets.Text
 import Graphics.Vty.Widgets.Alignment
+import Graphics.Vty.Widgets.Util
+import Graphics.Vty.Widgets.TextClip
+import Text.Trans.Tokenize
 
 data ProgressBar = ProgressBar { progressBarAmount :: Int
                                , onChangeHandlers :: Handlers Int
-                               , progressBarText :: String
+                               , progressBarText :: T.Text
                                , progressBarTextAlignment :: Alignment
+                               , progCompleteAttr :: Attr
+                               , progIncompleteAttr :: Attr
+                               , progTextWidget :: Widget FormattedText
                                }
 
 instance Show ProgressBar where
@@ -39,49 +46,72 @@
 newProgressBar :: Attr -> Attr -> IO (Widget ProgressBar)
 newProgressBar completeAttr incompleteAttr = do
   chs <- newHandlers
-  t <- plainText ""
-  let initSt = ProgressBar 0 chs "" AlignCenter
+  t <- plainText T.empty
+  let initSt = ProgressBar 0 chs T.empty AlignCenter completeAttr incompleteAttr t
   wRef <- newWidget initSt $ \w ->
           w { growHorizontal_ = const $ return True
             , render_ =
-                \this size ctx -> do
-                  -- Divide the available width according to the
-                  -- progress value
-                  prog <- progressBarAmount <~~ this
-                  txt <- progressBarText <~~ this
-                  al <- progressBarTextAlignment <~~ this
+                \this size ctx -> renderProgressBar size ctx =<< getState this
+            }
 
-                  let complete_width = fromEnum $ (toRational prog / toRational (100.0 :: Double)) *
-                                       (toRational $ fromEnum $ region_width size)
+  setProgress wRef 0
+  return wRef
 
-                      full_width = fromEnum $ region_width size
-                      full_str = take full_width $ mkStr txt al
+renderProgressBar :: DisplayRegion -> RenderContext -> ProgressBar -> IO Image
+renderProgressBar size ctx st = do
+  -- Divide the available width according to the progress value
+  let prog = progressBarAmount st
+      txt = progressBarText st
+      al = progressBarTextAlignment st
 
-                      mkStr s AlignLeft = s ++ replicate (full_width - length txt) ' '
-                      mkStr s AlignRight = replicate (full_width - length txt) ' ' ++ s
-                      mkStr s AlignCenter = concat [ half
-                                                   , s
-                                                   , half
-                                                   , if length half * 2 < (full_width + length txt)
-                                                     then " "
-                                                     else ""
-                                                   ]
-                          where
-                            half = replicate ((full_width - length txt) `div` 2) ' '
+      complete_width =
+          Phys $ fromEnum $ (toRational prog / toRational (100.0 :: Double)) *
+                   (toRational $ fromEnum $ region_width size)
 
-                      (complete_str, incomplete_str) = ( take complete_width full_str
-                                                       , drop complete_width full_str
-                                                       )
+      full_width = Phys $ fromEnum $ region_width size
+      full_str = truncateText full_width $ mkStr txt al
 
-                  setTextWithAttrs t [ (complete_str, completeAttr)
-                                     , (incomplete_str, incompleteAttr)
-                                     ]
-                  render t size ctx
-            }
+      mkStr s AlignLeft =
+          let diff = fromEnum $ full_width - textWidth txt
+          in T.concat [ s
+                      , T.pack $ replicate diff ' '
+                      ]
 
-  setProgress wRef 0
-  return wRef
+      mkStr s AlignRight =
+          let diff = fromEnum $ full_width - textWidth txt
+          in T.concat [ T.pack $ replicate diff ' '
+                      , s
+                      ]
 
+      mkStr s AlignCenter =
+          T.concat [ half
+                   , s
+                   , half
+                   , trailingSpc
+                   ]
+          where
+            diff = fromEnum $ full_width - textWidth txt
+            half = T.pack $ replicate (diff `div` 2) ' '
+            used_width = textWidth half * 2 + textWidth txt
+            trailingSpc =
+                if used_width < full_width
+                then T.singleton ' '
+                else T.empty
+
+
+      (leftPart, _, _) = clip1d 0 complete_width full_str
+      charCount = T.length leftPart
+
+      (complete_str, incomplete_str) = ( T.take charCount full_str
+                                       , T.drop charCount full_str
+                                       )
+
+  setTextWithAttrs (progTextWidget st)
+                       [ (complete_str, progCompleteAttr st)
+                       , (incomplete_str, progIncompleteAttr st)
+                       ]
+  render (progTextWidget st) size ctx
+
 -- |Register a handler to be invoked when the progress bar's progress
 -- value changes.  The handler will be passed the new progress value.
 onProgressChange :: Widget ProgressBar -> (Int -> IO ()) -> IO ()
@@ -101,7 +131,7 @@
     updateWidgetState p $ \st -> st { progressBarTextAlignment = al }
 
 -- |Set the progress bar's text label.
-setProgressText :: Widget ProgressBar -> String -> IO ()
+setProgressText :: Widget ProgressBar -> T.Text -> IO ()
 setProgressText p s =
     updateWidgetState p $ \st -> st { progressBarText = s }
 
diff --git a/src/Graphics/Vty/Widgets/Table.hs b/src/Graphics/Vty/Widgets/Table.hs
--- a/src/Graphics/Vty/Widgets/Table.hs
+++ b/src/Graphics/Vty/Widgets/Table.hs
@@ -26,6 +26,7 @@
 where
 
 import Data.Monoid
+import qualified Data.Text as T
 import Data.Typeable
 import Data.Word
 import Data.List
@@ -207,8 +208,7 @@
 
 -- |Create a table widget using a list of column specifications and a
 -- border style.
-newTable :: 
-            [ColumnSpec]
+newTable :: [ColumnSpec]
          -> BorderStyle
          -> IO (Widget Table)
 newTable specs borderSty = do
@@ -454,7 +454,7 @@
 -- |Add a heading row to a table.  Adds a row using the specified
 -- |labels and attribute.  Returns the widgets it constructed as a
 -- |side-effect in case you want to do something with them.
-addHeadingRow :: Widget Table -> Attr -> [String] -> IO [Widget FormattedText]
+addHeadingRow :: Widget Table -> Attr -> [T.Text] -> IO [Widget FormattedText]
 addHeadingRow tbl attr labels = do
   ws <- mapM (\s -> plainText s >>= withNormalAttribute attr) labels
   addRow tbl ws
@@ -462,7 +462,7 @@
 
 -- |Add a heading row to a table.  Adds a row using the specified
 -- |labels and attribute.
-addHeadingRow_ :: Widget Table -> Attr -> [String] -> IO ()
+addHeadingRow_ :: Widget Table -> Attr -> [T.Text] -> IO ()
 addHeadingRow_ tbl attr labels = addHeadingRow tbl attr labels >> return ()
 
 applyCellAlignment :: Alignment -> TableCell -> IO TableCell
@@ -523,7 +523,7 @@
 
 renderCell :: DisplayRegion -> TableCell -> RenderContext -> IO Image
 renderCell region EmptyCell ctx = do
-  w <- plainText ""
+  w <- plainText T.empty
   render w region ctx
 renderCell region (TableCell w _ _) ctx =
     render w region ctx
diff --git a/src/Graphics/Vty/Widgets/Text.hs b/src/Graphics/Vty/Widgets/Text.hs
--- a/src/Graphics/Vty/Widgets/Text.hs
+++ b/src/Graphics/Vty/Widgets/Text.hs
@@ -1,9 +1,8 @@
 {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, BangPatterns #-}
--- |This module provides functionality for rendering 'String's as
+-- |This module provides functionality for rendering 'Text' as
 -- 'Widget's, including functionality to make structural and/or visual
--- changes at rendering time.  To get started, turn your ordinary
--- 'String' into a 'Widget' with 'plainText'; for more control, use
--- 'textWidget'.
+-- changes at rendering time.  To get started, use 'plainText'; for
+-- more control, use 'textWidget'.
 module Graphics.Vty.Widgets.Text
     ( FormattedText
     -- *Constructing Text Widgets
@@ -26,6 +25,7 @@
 
 import Control.Applicative
 import Data.Monoid
+import qualified Data.Text as T
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
 import Text.Trans.Tokenize
@@ -66,16 +66,16 @@
                                         , " }"
                                         ]
 
--- |Construct a Widget directly from a String.  This is recommended if
--- you don't need to use a 'Formatter'.
-plainText :: String -> IO (Widget FormattedText)
+-- |Construct a Widget directly from a Text value.  This is
+-- recommended if you don't need to use a 'Formatter'.
+plainText :: T.Text -> IO (Widget FormattedText)
 plainText = textWidget nullFormatter
 
 -- |Construct a Widget directly from a list of strings and their
 -- attributes.
-plainTextWithAttrs :: [(String, Attr)] -> IO (Widget FormattedText)
+plainTextWithAttrs :: [(T.Text, Attr)] -> IO (Widget FormattedText)
 plainTextWithAttrs pairs = do
-  w <- textWidget nullFormatter ""
+  w <- textWidget nullFormatter T.empty
   setTextWithAttrs w pairs
   return w
 
@@ -86,13 +86,13 @@
 wrap :: Formatter
 wrap =
     Formatter $ \sz ts -> do
-      let width = fromEnum $ region_width sz
+      let width = Phys $ fromEnum $ region_width sz
       return $ wrapStream width ts
 
 -- |Construct a text widget formatted with the specified formatters
 -- and initial content.  The formatters will be applied in the order
 -- given here (and, depending on the formatter, order might matter).
-textWidget :: Formatter -> String -> IO (Widget FormattedText)
+textWidget :: Formatter -> T.Text -> IO (Widget FormattedText)
 textWidget format s = do
   let initSt = FormattedText { text = TS []
                              , formatter = format
@@ -130,7 +130,7 @@
 
 -- |Set the text value of a 'FormattedText' widget.  The specified
 -- string will be 'tokenize'd.
-setText :: Widget FormattedText -> String -> IO ()
+setText :: Widget FormattedText -> T.Text -> IO ()
 setText wRef s = setTextWithAttrs wRef [(s, def_attr)]
 
 -- |Set the text value of a 'FormattedText' widget directly, in case
@@ -138,7 +138,7 @@
 -- attributes.  The specified strings will each be 'tokenize'd, and
 -- tokens resulting from each 'tokenize' operation will be given the
 -- specified attribute in the tuple.
-setTextWithAttrs :: Widget FormattedText -> [(String, Attr)] -> IO ()
+setTextWithAttrs :: Widget FormattedText -> [(T.Text, Attr)] -> IO ()
 setTextWithAttrs wRef pairs = do
   let streams = map (\(s, a) -> tokenize s a) pairs
       ts = concat $ map streamEntities streams
@@ -172,7 +172,7 @@
       emptyLineLength = min (fromEnum $ region_width sz) maxLineLength
 
       ls = map truncLine $ map (map entityToken) $ findLines newText
-      truncLine = truncateLine (fromEnum $ region_width sz)
+      truncLine = truncateLine (Phys $ fromEnum $ region_width sz)
 
       -- When building images for empty lines, it's critical that we
       -- *don't* use the empty_image, because that will cause empty
@@ -188,7 +188,7 @@
       nullImg = string def_attr ""
 
       mkTokenImg :: Token Attr -> Image
-      mkTokenImg tok = string (finalAttr tok) (tokenStr tok)
+      mkTokenImg tok = string (finalAttr tok) (T.unpack $ tokenStr tok)
 
   return $ if region_height sz == 0
            then nullImg
diff --git a/src/Graphics/Vty/Widgets/TextClip.hs b/src/Graphics/Vty/Widgets/TextClip.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/Vty/Widgets/TextClip.hs
@@ -0,0 +1,116 @@
+-- |This module provides \"text clipping\" routines.  These routines
+-- are responsible for ensuring that logical characters are clipped
+-- properly when being laid out in a given physical region.  This is a
+-- bit tricky because some Unicode characters use two terminal columns
+-- and others (most) use one.  We have to take this into account when
+-- truncating text to fit into rendering regions, so we concentrate
+-- that logic here under the name of a \"clipping rectangle\" and
+-- functions to apply it.
+--
+-- Clipping functionality is provided in two forms: one- and
+-- two-dimensional clipping.  The former is useful for clipping a
+-- single line of text at a given offset and up to a given width.  The
+-- latter is useful for clipping a list of lines with respect to a 2-D
+-- clipping rectangle.
+module Graphics.Vty.Widgets.TextClip
+    ( ClipRect(..)
+    , clip1d
+    , clip2d
+    , updateRect
+    )
+where
+
+import Control.Applicative
+import Data.Maybe
+import qualified Data.Text as T
+import Graphics.Vty.Widgets.Util
+    ( Phys(..)
+    , chWidth
+    )
+
+-- |The type of clipping rectangles for 2-D clipping operations.  All
+-- values are 'Phys' values to indicate that we are dealing explicitly
+-- with physical column measurements rather than logical character
+-- positions.
+data ClipRect =
+    ClipRect { clipLeft :: Phys
+             -- ^The left margin of the clipping rectangle.
+             , clipTop :: Phys
+             -- ^The top row of the clipping rectangle.
+             , clipWidth :: Phys
+             -- ^The width, in columns, of the clipping rectangle.
+             , clipHeight :: Phys
+             -- ^The height, in rows, of the clipping rectangle.
+             }
+    deriving (Eq, Show)
+
+-- |One-dimensional text clipping.  Takes the left clipping margin, a
+-- clipping width, and a text string.  For example, @clip1d n w s@
+-- clips the string @s@ so that the result includes characters in @s@
+-- starting at position @n@ and including characters using no more
+-- than @w@ columns in width.  Returns the clipped text plus 'Bool's
+-- indicating whether wide characters were \"sliced\" on either side
+-- (left and right, respectively) of the clipping region.  This
+-- function guarantees that the text returned will always fit within
+-- the specified clipping region.  Since wide characters may be sliced
+-- during clipping, this may return a text string smaller than the
+-- clipping region.
+clip1d :: Phys -> Phys -> T.Text -> (T.Text, Bool, Bool)
+clip1d _ 0 _ = (T.empty, False, False)
+clip1d start len t = (T.pack result2, lSlice, rSlice)
+
+    where
+      pairs = [ (c, chWidth c) | c <- T.unpack t ]
+
+      exploded = concat $ mkExp <$> pairs
+      mkExp (a, i) = Just a : replicate (fromEnum i - 1) Nothing
+
+      -- First clip up to the starting position.
+      clip1 = drop (fromEnum start) exploded
+      -- Then clip according to the width.
+      clip2 = take (fromEnum len) clip1
+      -- Rest is whatever was left after clipping to the width.
+      rest = drop (fromEnum len) clip1
+
+      rSlice = length rest > 0 && head rest == Nothing
+      lSlice = length clip1 > 0 && head clip1 == Nothing
+
+      result1 = catMaybes clip2
+      result2 = if rSlice
+                then init result1
+                else result1
+
+-- |Two-dimensional text clipping.  Returns clipping data for each
+-- line as returned by 'clip1d', with the added behavior that it
+-- returns at most 'clipHeight' lines of text and uses 'clipTop' as
+-- the offset when clipping rows.
+clip2d :: ClipRect -> [T.Text] -> [(T.Text, Bool, Bool)]
+clip2d rect ls = clip1d left len <$> visibleLines
+        where
+          visibleLines = take (fromEnum height) $ drop (fromEnum top) ls
+          left = clipLeft rect
+          top = clipTop rect
+          len = clipWidth rect
+          height = clipHeight rect
+
+-- |Given a physical point and a clipping rectangle, adjust the
+-- clipping rectangle so that the point falls just inside the
+-- rectangle.  If the point is already within the rectangle, return
+-- the rectangle unmodified.  NB: this assumes that the physical
+-- position given has passed whatever validation checks are relevant
+-- for the user of the 'ClipRect'.  This function just performs a
+-- rectangle transformation.
+updateRect :: (Phys, Phys) -> ClipRect -> ClipRect
+updateRect (row, col) oldRect = adjustLeft $ adjustTop oldRect
+    where
+      adjustLeft old
+          | col < clipLeft oldRect = old { clipLeft = col }
+          | col >= clipLeft oldRect + clipWidth oldRect =
+              old { clipLeft = col - clipWidth old + 1 }
+          | otherwise = old
+
+      adjustTop old
+          | row < clipTop oldRect = old { clipTop = row }
+          | row >= clipTop oldRect + clipHeight oldRect =
+              old { clipTop = row - clipHeight old + 1 }
+          | otherwise = old
diff --git a/src/Graphics/Vty/Widgets/TextZipper.hs b/src/Graphics/Vty/Widgets/TextZipper.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/Vty/Widgets/TextZipper.hs
@@ -0,0 +1,297 @@
+-- |This module provides a two-dimensional text zipper data structure.
+-- This structure represents a body of text and an editing cursor
+-- which can be moved throughout the text, along with a set of editing
+-- transformations.
+--
+-- Text zippers are generalized over the set of data types that might
+-- be used to store lists of characters (e.g., 'String', 'T.Text',
+-- etc.).  As a result, the most general way to create a text zipper
+-- is to use 'mkZipper' and provide all of the functions required to
+-- manipulate the underlying text data.
+--
+-- A default implementation using 'T.Text' is provided and is used
+-- elsewhere in this library.
+module Graphics.Vty.Widgets.TextZipper
+    ( TextZipper
+
+    -- *Construction and extraction
+    , mkZipper
+    , textZipper
+    , getText
+    , currentLine
+    , cursorPosition
+
+    -- *Navigation functions
+    , moveCursor
+    , insertChar
+    , breakLine
+    , killToEOL
+    , gotoEOL
+    , gotoBOL
+    , deletePrevChar
+    , deleteChar
+    , moveRight
+    , moveLeft
+    , moveUp
+    , moveDown
+    )
+where
+
+import Data.Monoid
+import qualified Data.Text as T
+
+data TextZipper a =
+    TZ { toLeft :: a
+       , toRight :: a
+       , above :: [a]
+       , below :: [a]
+       , fromChar :: Char -> a
+       , drop_ :: Int -> a -> a
+       , take_ :: Int -> a -> a
+       , length_ :: a -> Int
+       , last_ :: a -> Char
+       , init_ :: a -> a
+       , null_ :: a -> Bool
+       }
+
+instance (Eq a) => Eq (TextZipper a) where
+    a == b = and [ toLeft a == toLeft b
+                 , toRight a == toRight b
+                 , above a == above b
+                 , below a == below b
+                 ]
+
+instance (Show a) => Show (TextZipper a) where
+    show tz = concat [ "TextZipper { "
+                     , "above = "
+                     , show $ above tz
+                     , "below = "
+                     , show $ below tz
+                     , "toLeft = "
+                     , show $ toLeft tz
+                     , "toRight = "
+                     , show $ toRight tz
+                     , " }"
+                     ]
+
+-- |Create a zipper using a custom text storage type.  Takes the
+-- initial text as well as all of the functions necessary to
+-- manipulate the underlying text values.
+mkZipper :: (Monoid a) =>
+            (Char -> a)
+         -- ^A singleton constructor.
+         -> (Int -> a -> a)
+         -- ^'drop'.
+         -> (Int -> a -> a)
+         -- ^'take'.
+         -> (a -> Int)
+         -- ^'length'.
+         -> (a -> Char)
+         -- ^'last'.
+         -> (a -> a)
+         -- ^'init'.
+         -> (a -> Bool)
+         -- ^'null'.
+         -> [a]
+         -- ^The initial lines of text.
+         -> TextZipper a
+mkZipper fromCh drp tk lngth lst int nl ls =
+    let (first, rest) = if null ls
+                        then (mempty, mempty)
+                        else (head ls, tail ls)
+    in TZ mempty first [] rest fromCh drp tk lngth lst int nl
+
+-- |Get the text contents of the zipper.
+getText :: (Monoid a) => TextZipper a -> [a]
+getText tz = concat [ above tz
+                    , [currentLine tz]
+                    , below tz
+                    ]
+
+-- |Get the cursor position of the zipper; returns @(row, col)@.
+-- @row@ ranges from @[0..num_rows-1]@ inclusive; @col@ ranges from
+-- @[0..length of current line]@ inclusive.  Column values equal to
+-- line width indicate a cursor that is just past the end of a line of
+-- text.
+cursorPosition :: TextZipper a -> (Int, Int)
+cursorPosition tz = (length $ above tz, length_ tz $ toLeft tz)
+
+-- |Move the cursor to the specified row and column.  Invalid cursor
+-- positions will be ignored.  Valid cursor positions range as
+-- described for 'cursorPosition'.
+moveCursor :: (Monoid a) => (Int, Int) -> TextZipper a -> TextZipper a
+moveCursor (row, col) tz =
+    let t = getText tz
+    in if row < 0
+           || row > length t
+           || col < 0
+           || col > length_ tz (t !! row)
+       then tz
+       else tz { above = take row t
+               , below = drop (row + 1) t
+               , toLeft = take_ tz col (t !! row)
+               , toRight = drop_ tz col (t !! row)
+               }
+
+lastLine :: TextZipper a -> Bool
+lastLine = (== 0) . length . below
+
+nextLine :: TextZipper a -> a
+nextLine = head . below
+
+-- |The line of text on which the zipper's cursor currently resides.
+currentLine :: (Monoid a) => TextZipper a -> a
+currentLine tz = (toLeft tz) `mappend` (toRight tz)
+
+-- |Insert a character at the current cursor position.  Move the
+-- cursor one position to the right.
+insertChar :: (Monoid a) => Char -> TextZipper a -> TextZipper a
+insertChar ch tz = tz { toLeft = toLeft tz `mappend` (fromChar tz ch) }
+
+-- |Insert a line break at the current cursor position.
+breakLine :: (Monoid a) => TextZipper a -> TextZipper a
+breakLine tz =
+    tz { above = above tz ++ [toLeft tz]
+       , toLeft = mempty
+       }
+
+-- |Move the cursor to the end of the current line.
+gotoEOL :: (Monoid a) => TextZipper a -> TextZipper a
+gotoEOL tz = tz { toLeft = currentLine tz
+                , toRight = mempty
+                }
+
+-- |Remove all text from the cursor position to the end of the current
+-- line.  If the cursor is at the beginning of a line and the line is
+-- empty, the entire line will be removed.
+killToEOL :: (Monoid a) => TextZipper a -> TextZipper a
+killToEOL tz
+    | (null_ tz $ toLeft tz) && (null_ tz $ toRight tz) &&
+      (not $ null $ below tz) =
+          tz { toRight = head $ below tz
+             , below = tail $ below tz
+             }
+    | otherwise = tz { toRight = mempty
+                     }
+
+-- |Delete the character preceding the cursor position, and move the
+-- cursor backwards by one character.
+deletePrevChar :: (Eq a, Monoid a) => TextZipper a -> TextZipper a
+deletePrevChar tz
+    | moveLeft tz == tz = tz
+    | otherwise = deleteChar $ moveLeft tz
+
+-- |Delete the character at the cursor position.  Leaves the cursor
+-- position unchanged.  If the cursor is at the end of a line of text,
+-- this combines the line with the line below.
+deleteChar :: (Monoid a) => TextZipper a -> TextZipper a
+deleteChar tz
+    -- Can we just remove a char from the current line?
+    | (not $ null_ tz (toRight tz)) =
+        tz { toRight = drop_ tz 1 $ toRight tz
+           }
+    -- Do we need to collapse the previous line onto the current one?
+    | null_ tz (toRight tz) && (not $ null $ below tz) =
+        tz { toRight = head $ below tz
+           , below = tail $ below tz
+           }
+    | otherwise = tz
+
+-- |Move the cursor to the beginning of the current line.
+gotoBOL :: (Monoid a) => TextZipper a -> TextZipper a
+gotoBOL tz = tz { toLeft = mempty
+                , toRight = currentLine tz
+                }
+
+-- |Move the cursor right by one position.  If the cursor is at the
+-- end of a line, the cursor is moved to the first position of the
+-- following line (if any).
+moveRight :: (Monoid a) => TextZipper a -> TextZipper a
+moveRight tz
+    -- Are we able to keep moving right on the current line?
+    | not (null_ tz (toRight tz)) =
+        tz { toLeft = toLeft tz
+                      `mappend` (take_ tz 1 $ toRight tz)
+           , toRight = drop_ tz 1 (toRight tz)
+           }
+    -- If we are going to go beyond the end of the current line, can
+    -- we move to the next one?
+    | not $ null (below tz) =
+        tz { above = above tz ++ [toLeft tz]
+           , below = tail $ below tz
+           , toLeft = mempty
+           , toRight = nextLine tz
+           }
+    | otherwise = tz
+
+-- |Move the cursor left by one position.  If the cursor is at the
+-- beginning of a line, the cursor is moved to the last position of
+-- the preceding line (if any).
+moveLeft :: (Monoid a) => TextZipper a -> TextZipper a
+moveLeft tz
+    -- Are we able to keep moving left on the current line?
+    | not $ null_ tz (toLeft tz) =
+        tz { toLeft = init_ tz $ toLeft tz
+           , toRight = fromChar tz (last_ tz (toLeft tz))
+                       `mappend` toRight tz
+           }
+    -- If we are going to go beyond the beginning of the current line,
+    -- can we move to the end of the previous one?
+    | not $ null (above tz) =
+        tz { above = init $ above tz
+           , below = currentLine tz : below tz
+           , toLeft = last $ above tz
+           , toRight = mempty
+           }
+    | otherwise = tz
+
+-- |Move the cursor up by one row.  If there no are rows above the
+-- current one, move to the first position of the current row.  If the
+-- row above is shorter, move to the end of that row.
+moveUp :: (Monoid a) => TextZipper a -> TextZipper a
+moveUp tz
+    -- Is there a line above at least as long as the current one?
+    | (not $ null (above tz)) &&
+      (length_ tz $ last $ above tz) >= length_ tz (toLeft tz) =
+        tz { below = currentLine tz : below tz
+           , above = init $ above tz
+           , toLeft = take_ tz (length_ tz $ toLeft tz) (last $ above tz)
+           , toRight = drop_ tz (length_ tz $ toLeft tz) (last $ above tz)
+           }
+    -- Or if there is a line above, just go to the end of it
+    | (not $ null (above tz)) =
+        tz { above = init $ above tz
+           , below = currentLine tz : below tz
+           , toLeft = last $ above tz
+           , toRight = mempty
+           }
+    -- If nothing else, go to the beginning of the current line
+    | otherwise = gotoBOL tz
+
+-- |Move the cursor down by one row.  If there are no rows below the
+-- current one, move to the last position of the current row.  If the
+-- row below is shorter, move to the end of that row.
+moveDown :: (Monoid a) => TextZipper a -> TextZipper a
+moveDown tz
+    -- Is there a line below at least as long as the current one?
+    | (not $ lastLine tz) &&
+      (length_ tz $ nextLine tz) >= length_ tz (toLeft tz) =
+        tz { below = tail $ below tz
+           , above = above tz ++ [currentLine tz]
+           , toLeft = take_ tz (length_ tz $ toLeft tz) (nextLine tz)
+           , toRight = drop_ tz (length_ tz $ toLeft tz) (nextLine tz)
+           }
+    -- Or if there is a line below, just go to the end of it
+    | (not $ null (below tz)) =
+        tz { above = above tz ++ [currentLine tz]
+           , below = tail $ below tz
+           , toLeft = nextLine tz
+           , toRight = mempty
+           }
+    -- If nothing else, go to the end of the current line
+    | otherwise = gotoEOL tz
+
+-- |Construct a zipper from 'T.Text' values.
+textZipper :: [T.Text] -> TextZipper T.Text
+textZipper =
+    mkZipper T.singleton T.drop T.take T.length T.last T.init T.null
diff --git a/src/Graphics/Vty/Widgets/Util.hs b/src/Graphics/Vty/Widgets/Util.hs
--- a/src/Graphics/Vty/Widgets/Util.hs
+++ b/src/Graphics/Vty/Widgets/Util.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving #-}
 module Graphics.Vty.Widgets.Util
     ( on
     , fgColor
@@ -13,11 +13,50 @@
     , remove
     , inject
     , repl
+    , takeMaxText
+    , takeMaxChars
+    , chWidth
+    , strWidth
+    , textWidth
+    , Phys(..)
     )
 where
 
+import Control.Applicative
 import Data.Word
+import qualified Data.Text as T
 import Graphics.Vty
+import Graphics.Vty.Image
+    ( safe_wcwidth
+    )
+
+-- A newtype to wrap physical screen coordinates, as opposed to
+-- character-logical coordinates.  Used when transforming cursor
+-- coordinates to screen coordinates and when computing the physical
+-- width of characters and strings.
+newtype Phys = Phys Int
+    deriving (Num, Eq, Show, Ord, Integral, Enum, Real)
+
+chWidth :: Char -> Phys
+chWidth = Phys . fromEnum . safe_wcwidth
+
+textWidth :: T.Text -> Phys
+textWidth = strWidth . T.unpack
+
+strWidth :: String -> Phys
+strWidth = sum . (chWidth <$>)
+
+takeMaxChars :: Phys -> [Char] -> [Char]
+takeMaxChars mx xs = f' (Phys 0) xs
+    where
+      f' _ [] = []
+      f' acc (c:cs) = let w = chWidth c
+                      in if acc + w <= mx
+                         then c : f' (acc + w) cs
+                         else []
+
+takeMaxText :: Phys -> T.Text -> T.Text
+takeMaxText mx xs = T.pack $ takeMaxChars mx $ T.unpack xs
 
 -- |Infix attribute constructor.  Use: foregroundColor `on`
 -- backgroundColor.
diff --git a/src/ListDemo.hs b/src/ListDemo.hs
deleted file mode 100644
--- a/src/ListDemo.hs
+++ /dev/null
@@ -1,134 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-do-bind #-}
-module Main where
-
-import System.Exit ( exitSuccess )
-import Graphics.Vty
-import Graphics.Vty.Widgets.All
-
-data AppElements =
-    AppElements { theList :: Widget (List String FormattedText)
-                , theBody :: Widget FormattedText
-                , theFooter :: Widget FormattedText
-                , theListLimit :: Widget (VLimit (List String FormattedText))
-                , uis :: Collection
-                }
-
-titleAttr = bright_white `on` blue
-focAttr = black `on` green
-bodyAttr = white `on` black
-selAttr = black `on` yellow
-keyAttr = fgColor magenta
-
-message1 :: String
-message1 = "This demonstration shows how list widgets behave. \n\
-           \See the keystrokes below to try the demo."
-
-message2 :: [(String, Attr)]
-message2 = [ ("- Press ", def_attr), ("q", keyAttr), (" to quit\n", def_attr)
-           , ("- Press ", def_attr), ("+", keyAttr)
-           , (" / ", def_attr), ("a", keyAttr)
-           , (" to add a list item\n", def_attr)
-           , ("- Press ", def_attr), ("-", keyAttr)
-           , (" / ", def_attr), ("d", keyAttr)
-           , (" to remove the selected list item\n", def_attr)
-           , ("- Press ", def_attr)
-           , ("up", keyAttr), (" / ", def_attr)
-           , ("down", keyAttr), (" / ", def_attr)
-           , ("page up", keyAttr), (" / ", def_attr)
-           , ("page down", keyAttr)
-           , (" to navigate the list\n", def_attr)
-           ]
-
-buildUi appst = do
-  msg1 <- plainText message1
-  setTextFormatter msg1 wrap
-
-  msg2 <- plainTextWithAttrs message2
-  setTextFormatter msg2 wrap
-
-  mainUi <- (hBorder >>= withBorderAttribute titleAttr)
-         <--> (return $ theList appst)
-         <--> ((return $ theFooter appst)
-               <++> (hBorder >>= withBorderAttribute titleAttr))
-
-  centered =<< hLimit 55 =<<
-             ((return msg1)
-                  <--> ((vLimit 25 mainUi >>= bordered)
-                        <--> return msg2 >>= withBoxSpacing 1)
-                    >>= withBoxSpacing 1)
-
--- Construct the application statea using the message map.
-mkAppElements :: IO AppElements
-mkAppElements = do
-  lw <- newStringList selAttr []
-  b <- textWidget wrap ""
-  ft <- plainText "" >>= withNormalAttribute titleAttr
-  ll <- vLimit 5 lw
-
-  c <- newCollection
-
-  return $ AppElements { theList = lw
-                       , theBody = b
-                       , theFooter = ft
-                       , theListLimit = ll
-                       , uis = c
-                       }
-
-updateBody :: AppElements -> Int -> IO ()
-updateBody st i = do
-  let msg = "This is the text for list entry " ++ (show $ i + 1)
-  setText (theBody st) msg
-
-updateFooterNums :: AppElements -> Widget (List a b) -> IO ()
-updateFooterNums st w = do
-  result <- getSelected w
-  sz <- getListSize w
-  let msg = case result of
-              Nothing -> "0/0"
-              Just (i, _) -> (show $ i + 1) ++ "/" ++ (show sz)
-  setText (theFooter st) msg
-
-main :: IO ()
-main = do
-  st <- mkAppElements
-
-  ui <- buildUi st
-  fg <- newFocusGroup
-
-  _ <- addToCollection (uis st) ui fg
-  _ <- addToFocusGroup fg (theList st)
-
-  (theList st) `onSelectionChange` \_ -> updateFooterNums st $ theList st
-  (theList st) `onItemAdded` \_ -> updateFooterNums st $ theList st
-  (theList st) `onItemRemoved` \_ -> updateFooterNums st $ theList st
-
-  let removeCurrentItem = do
-         result <- getSelected (theList st)
-         case result of
-           Nothing -> return ()
-           Just (i, _) -> removeFromList (theList st) i >> return ()
-      addNewItem = do
-         addToList (theList st) "unused" =<< plainText "a list item"
-
-  (theList st) `onKeyPressed` \_ k _ -> do
-         case k of
-           (KASCII 'q') -> exitSuccess
-           (KASCII '-') -> removeCurrentItem >> return True
-           (KASCII 'd') -> removeCurrentItem >> return True
-           (KASCII '+') -> addNewItem >> return True
-           (KASCII 'a') -> addNewItem >> return True
-           _ -> return False
-
-  -- We need to call these handlers manually because while they will
-  -- be called automatically as items are added to the list in the
-  -- future, the items currently in the list didn't call these because
-  -- they weren't registered at the time the items were added.  And
-  -- that was impossible because the list was created and populated
-  -- before we even got a reference to it, so we couldn't have set up
-  -- event handlers.
-  updateFooterNums st (theList st)
-
-  -- Enter the event loop.
-  runUi (uis st) $ defaultContext { normalAttr = bodyAttr
-                                  , focusAttr = focAttr
-                                  }
diff --git a/src/PhoneInputDemo.hs b/src/PhoneInputDemo.hs
deleted file mode 100644
--- a/src/PhoneInputDemo.hs
+++ /dev/null
@@ -1,78 +0,0 @@
-module Main where
-
--- This demo is discussed in the vty-ui user's manual.
-
-import Control.Monad
-
-import Graphics.Vty hiding (pad)
-import Graphics.Vty.Widgets.All
-
-data PhoneNumber = PhoneNumber String String String
-                   deriving (Show)
-
--- This type isn't pretty, but we have to specify the type of the
--- complete interface.  Initially you can let the compiler tell you
--- what it is.
-type T = Box (Box
-              (Box (Box (HFixed Edit) FormattedText) (HFixed Edit))
-              FormattedText) (HFixed Edit)
-
-data PhoneInput =
-   PhoneInput { phoneInputWidget :: Widget T
-              , edit1 :: Widget Edit
-              , edit2 :: Widget Edit
-              , edit3 :: Widget Edit
-              , activateHandlers :: Handlers PhoneNumber
-              }
-
-newPhoneInput :: IO (PhoneInput, Widget FocusGroup)
-newPhoneInput = do
-   ahs <- newHandlers
-   e1 <- editWidget
-   e2 <- editWidget
-   e3 <- editWidget
-   ui <- (hFixed 4 e1) <++>
-         (plainText "-") <++>
-         (hFixed 4 e2) <++>
-         (plainText "-") <++>
-         (hFixed 5 e3)
-
-   let w = PhoneInput ui e1 e2 e3 ahs
-       doFireEvent = const $ do
-         num <- mkPhoneNumber
-         fireEvent w (return . activateHandlers) num
-
-       mkPhoneNumber = do
-         s1 <- getEditText e1
-         s2 <- getEditText e2
-         s3 <- getEditText e3
-         return $ PhoneNumber s1 s2 s3
-
-   e1 `onActivate` doFireEvent
-   e2 `onActivate` doFireEvent
-   e3 `onActivate` doFireEvent
-
-   e1 `onChange` \s -> when (length s == 3) $ focus e2
-   e2 `onChange` \s -> when (length s == 3) $ focus e3
-
-   fg <- newFocusGroup
-   mapM_ (addToFocusGroup fg) [e1, e2, e3]
-   return (w, fg)
-
-onPhoneInputActivate :: PhoneInput
-                     -> (PhoneNumber -> IO ()) -> IO ()
-onPhoneInputActivate input handler =
-    addHandler (return . activateHandlers) input handler
-
-main :: IO ()
-main = do
-  (p, fg) <- newPhoneInput
-  p `onPhoneInputActivate` (error . show)
-
-  ui <- padded (phoneInputWidget p) (padLeftRight 5 `pad` padTopBottom 2)
-
-  c <- newCollection
-  _ <- addToCollection c ui fg
-
-  runUi c $ defaultContext { focusAttr = white `on` blue
-                           }
diff --git a/src/ProgressBarDemo.hs b/src/ProgressBarDemo.hs
deleted file mode 100644
--- a/src/ProgressBarDemo.hs
+++ /dev/null
@@ -1,56 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-unused-do-bind -fno-warn-missing-signatures #-}
-module Main where
-
-import Control.Concurrent (forkIO, threadDelay)
-import Control.Monad (forever)
-
-import System.Exit ( exitSuccess )
-import Graphics.Vty
-import Graphics.Vty.Widgets.All
-
--- Visual attributes.
-focAttr = black `on` green
-bodyAttr = bright_green `on` black
-completeAttr = white `on` red
-incompleteAttr = red `on` white
-
-setupProgessBar :: IO (Widget ProgressBar)
-setupProgessBar = do
-  pb <- newProgressBar completeAttr incompleteAttr
-  setProgressTextAlignment pb AlignCenter
-
-  pb `onProgressChange` \val ->
-      setProgressText pb $ "Progress (" ++ show val ++ " %)"
-
-  return pb
-
-setupProgressBarThread :: Widget ProgressBar -> IO ()
-setupProgressBarThread pb = do
-  forkIO $ forever $ do
-    let act i = do
-          threadDelay $ 1 * 1000 * 1000
-          schedule $ setProgress pb (i `mod` 101)
-          act $ i + 4
-    act 0
-  return ()
-
-setupDialog :: (Show a) => Widget a -> IO (Dialog, Widget FocusGroup)
-setupDialog ui = do
-  (dlg, fg) <- newDialog ui "Progress Bar Demo"
-  dlg `onDialogAccept` const exitSuccess
-  dlg `onDialogCancel` const exitSuccess
-  return (dlg, fg)
-
-main :: IO ()
-main = do
-  pb <- setupProgessBar
-  setupProgressBarThread pb
-
-  (dlg, dlgFg) <- setupDialog pb
-
-  c <- newCollection
-  _ <- addToCollection c (dialogWidget dlg) dlgFg
-
-  runUi c $ defaultContext { normalAttr = bodyAttr
-                           , focusAttr = focAttr
-                           }
diff --git a/src/Text/Trans/Tokenize.hs b/src/Text/Trans/Tokenize.hs
--- a/src/Text/Trans/Tokenize.hs
+++ b/src/Text/Trans/Tokenize.hs
@@ -19,6 +19,7 @@
 
     -- * Manipulation
     , truncateLine
+    , truncateText
     , wrapStream
     , findLines
 #ifdef TESTING
@@ -28,22 +29,25 @@
     )
 where
 
+import Control.Applicative
 import Data.List
     ( inits
     )
+import qualified Data.Text as T
+import Graphics.Vty.Widgets.Util
 
 -- |The type of text tokens.  These should consist of printable
 -- characters and NOT presentation characters (e.g., newlines).  Each
 -- type of token should have as its contents a string of characters
 -- all of the same type.  Tokens are generalized over an attribute
 -- type which can be used to annotate each token.
-data Token a = S { tokenStr :: !String
+data Token a = S { tokenStr :: !T.Text
                  -- ^The token's string.
                  , tokenAttr :: !a
                  -- ^The token's attribute.
                  }
              -- ^Non-whitespace tokens.
-             | WS { tokenStr :: !String
+             | WS { tokenStr :: !T.Text
                   -- ^The token's string.
                   , tokenAttr :: !a
                   -- ^The token's attribute.
@@ -90,8 +94,8 @@
 
 -- |Get the length of a token's string.
 tokenLen :: Token a -> Int
-tokenLen (S s _) = length s
-tokenLen (WS s _) = length s
+tokenLen (S s _) = T.length s
+tokenLen (WS s _) = T.length s
 
 wsChars :: [Char]
 wsChars = [' ', '\t']
@@ -121,32 +125,41 @@
 
 -- |Given a text stream, serialize the stream to its original textual
 -- representation.  This discards token attribute metadata.
-serialize :: TextStream a -> String
-serialize (TS es) = concat $ map serializeEntity es
+serialize :: TextStream a -> T.Text
+serialize (TS es) = T.concat $ serializeEntity <$> es
     where
-      serializeEntity NL = "\n"
+      serializeEntity NL = T.pack "\n"
       serializeEntity (T (WS s _)) = s
       serializeEntity (T (S s _)) = s
 
 -- |Tokenize a string and apply a default attribute to every token in
 -- the resulting text stream.
-tokenize :: String -> a -> TextStream a
+tokenize :: T.Text -> a -> TextStream a
 tokenize s def = TS $ findEntities s
     where
-      findEntities [] = []
-      findEntities str@(c:_) = nextEntity : findEntities (drop nextLen str)
+      findEntities str
+          | T.null str = []
+          | otherwise = nextEntity : findEntities (T.drop nextLen str)
           where
+            c = T.head str
             (nextEntity, nextLen) = if isWs c
-                                    then (T (WS nextWs def), length nextWs)
+                                    then (T (WS nextWs def), T.length nextWs)
                                     else if c == '\n'
                                          then (NL, 1)
-                                         else (T (S nextStr def), length nextStr)
-            nextWs = takeWhile isWs str
-            nextStr = takeWhile (\ch -> not $ ch `elem` ('\n':wsChars)) str
+                                         else (T (S nextStr def), T.length nextStr)
+            nextWs = T.takeWhile isWs str
+            nextStr = T.takeWhile (\ch -> not $ ch `elem` ('\n':wsChars)) str
 
+-- |Same as 'truncateLine' but for 'Text' values.
+truncateText :: Phys -> T.Text -> T.Text
+truncateText width t =
+    let TS ts = tokenize t ()
+        tokens = entityToken <$> ts
+    in T.concat $ tokenStr <$> truncateLine width tokens
+
 -- |Given a list of tokens, truncate the list so that its underlying
 -- string representation does not exceed the specified column width.
-truncateLine :: Int -> [Token a] -> [Token a]
+truncateLine :: Phys -> [Token a] -> [Token a]
 truncateLine l _ | l < 0 = error $ "truncateLine cannot truncate at length = " ++ show l
 truncateLine _ [] = []
 truncateLine width ts =
@@ -158,14 +171,14 @@
     -- If there are no passing cases (i.e., remaining is null), just
     -- return 'width' characters of the first token.
     if null remaining
-    then [first_tok { tokenStr = take width $ tokenStr first_tok }]
+    then [first_tok { tokenStr = takeMaxText width $ tokenStr first_tok }]
     else if length tokens == length ts
          then tokens
-         else if null $ tokenStr lastToken
+         else if T.null $ tokenStr lastToken
               then tokens
               else tokens ++ [lastToken]
     where
-      lengths = map (length . tokenStr) ts
+      lengths = map (sum . (chWidth <$>) . T.unpack . tokenStr) ts
       cases = reverse $ inits lengths
       remaining = dropWhile ((> width) . sum) cases
       tokens = take (length $ head remaining) ts
@@ -173,28 +186,32 @@
 
       first_tok = ts !! 0
       last_tok = ts !! (length tokens)
-      lastToken = last_tok { tokenStr = take (width - truncLength) $ tokenStr last_tok }
+      lastToken = last_tok { tokenStr = takeMaxText (width - truncLength) $
+                                        tokenStr last_tok
+                           }
 
 -- |Given a text stream and a wrapping width, return a new
 -- 'TextStream' with newlines inserted in appropriate places to wrap
--- the text at the specified column.  This function results in text
--- wrapped without leading or trailing whitespace on wrapped lines,
--- although it preserves leading whitespace in the text which was not
--- the cause of the wrapping transformation.
-wrapStream :: (Eq a) => Int -> TextStream a -> TextStream a
+-- the text at the specified column (not character position).
+--
+-- This function results in text wrapped without leading or trailing
+-- whitespace on wrapped lines, although it preserves leading
+-- whitespace in the text which was not the cause of the wrapping
+-- transformation.
+wrapStream :: (Eq a) => Phys -> TextStream a -> TextStream a
 wrapStream width (TS stream) = TS $ reverse $ dropWhile (== NL) $ reverse $ wrapAll' 0 stream
     where
-      wrapAll' :: Int -> [TextStreamEntity a] -> [TextStreamEntity a]
+      wrapAll' :: Phys -> [TextStreamEntity a] -> [TextStreamEntity a]
       wrapAll' _ [] = []
       wrapAll' _ (NL:rest) = NL : wrapAll' 0 rest
       wrapAll' accum (T t:rest) =
-          if (length $ tokenStr t) + accum > width
+          if (textWidth $ tokenStr t) + accum > width
           then if isWhitespace t
                then [NL] ++ wrapAll' 0 (dropWhile isWsEnt rest)
-               else if accum == 0 && ((length $ tokenStr t) >= width)
-                    then [T t, NL] ++ wrapAll' 0 (dropWhile isWsEnt rest)
-                    else [NL, T t] ++ wrapAll' (length $ tokenStr t) rest
-          else T t : wrapAll' (accum + (length $ tokenStr t)) rest
+               else if accum == 0 && ((textWidth $ tokenStr t) >= width)
+                    then [T t] ++ wrapAll' (textWidth $ tokenStr t) (dropWhile isWsEnt rest)
+                    else [NL, T t] ++ wrapAll' (textWidth $ tokenStr t) rest
+          else T t : wrapAll' (accum + (textWidth $ tokenStr t)) rest
 
 partitions :: (a -> Bool) -> [a] -> [[a]]
 partitions _ [] = []
diff --git a/test/TestDriver.hs b/test/TestDriver.hs
--- a/test/TestDriver.hs
+++ b/test/TestDriver.hs
@@ -7,10 +7,16 @@
 
 import qualified Tests.FormattedText as FormattedText
 import qualified Tests.Tokenize as Tokenize
+import qualified Tests.Edit as Edit
+import qualified Tests.TextClip as TextClip
+import qualified Tests.TextZipper as TextZipper
 
 tests :: [Property]
 tests = concat [ FormattedText.tests
                , Tokenize.tests
+               , Edit.tests
+               , TextClip.tests
+               , TextZipper.tests
                ]
 
 main :: IO ()
diff --git a/test/src/Tests/Edit.hs b/test/src/Tests/Edit.hs
new file mode 100644
--- /dev/null
+++ b/test/src/Tests/Edit.hs
@@ -0,0 +1,46 @@
+module Tests.Edit where
+
+import Control.Applicative
+import Data.Char ( isPrint )
+import Test.QuickCheck
+import Tests.Instances ()
+import qualified Data.Text as T
+
+import Graphics.Vty.Widgets.Edit
+import Graphics.Vty.Widgets.Util
+import Graphics.Vty.Widgets.TextClip
+
+stringGen :: Gen String
+stringGen = listOf1 charGen
+
+textGen :: Gen T.Text
+textGen = T.pack <$> (listOf $ oneof [ pure 'a'
+                                     , pure ' '
+                                     , pure '台'
+                                     ])
+
+charGen :: Gen Char
+charGen = oneof [ arbitrary `suchThat` (\c -> isPrint c)
+                , pure '台'
+                ]
+
+doClippingTest :: Property
+doClippingTest =
+    property $ forAll textGen $ \t ->
+        forAll (choose (Phys 0, textWidth t + Phys 10)) $ \leftCrop ->
+        forAll (choose (Phys 0, textWidth t + Phys 10)) $ \resWidth ->
+            let rect = ClipRect { clipLeft = leftCrop
+                                , clipWidth = resWidth
+                                , clipTop = 0
+                                , clipHeight = 1
+                                }
+                [(_, ls, rs)] = clip2d rect [t]
+                [res] = doClipping [t] rect
+            in strWidth res == 0
+                   || and [ not ls || (ls && head res == indicatorChar)
+                          , not rs || (rs && last res == indicatorChar)
+                          ]
+
+tests :: [Property]
+tests = [ label "doClipping clips and adds indicators" doClippingTest
+        ]
diff --git a/test/src/Tests/FormattedText.hs b/test/src/Tests/FormattedText.hs
--- a/test/src/Tests/FormattedText.hs
+++ b/test/src/Tests/FormattedText.hs
@@ -4,6 +4,7 @@
 import Test.QuickCheck.Monadic
 
 import Control.Applicative
+import qualified Data.Text as T
 
 import Graphics.Vty
 import Graphics.Vty.Widgets.Text
@@ -42,11 +43,12 @@
         img3 <- run $ render w2 sz defaultContext
         return $ img1 == img3 && img1 /= img2
 
-textString :: Gen String
-textString = listOf $ oneof [ pure 'a'
-                            , pure '\n'
-                            , pure ' '
-                            ]
+textString :: Gen T.Text
+textString = T.pack <$> (listOf $ oneof [ pure 'a'
+                                        , pure '\n'
+                                        , pure ' '
+                                        , pure '台'
+                                        ])
 
 tests :: [Property]
 tests = [ label "text: newlines rendered correctly" textHeight
diff --git a/test/src/Tests/Instances.hs b/test/src/Tests/Instances.hs
--- a/test/src/Tests/Instances.hs
+++ b/test/src/Tests/Instances.hs
@@ -2,9 +2,11 @@
 module Tests.Instances where
 
 import Test.QuickCheck
+import System.Random
 import Control.Applicative ( (<*>), (<$>), pure )
 
 import Graphics.Vty
+import Graphics.Vty.Widgets.Util
 
 instance (Show a, Arbitrary a, Eq a) => Arbitrary (MaybeDefault a) where
     arbitrary = oneof [ pure Default
@@ -24,3 +26,15 @@
     arbitrary = DisplayRegion <$> coord <*> coord
         where
           coord = sized $ \n -> fromIntegral <$> choose (0, n)
+
+instance Random Phys where
+    random g =
+        let (val, g') = random g
+        in (Phys val, g')
+
+    randomR (Phys a, Phys b) g =
+        let (val, g') = randomR (a, b) g
+        in (Phys val, g')
+
+instance Arbitrary Phys where
+    arbitrary = Phys <$> arbitrary
diff --git a/test/src/Tests/TextClip.hs b/test/src/Tests/TextClip.hs
new file mode 100644
--- /dev/null
+++ b/test/src/Tests/TextClip.hs
@@ -0,0 +1,60 @@
+module Tests.TextClip
+    ( tests
+    )
+where
+
+import Control.Applicative
+import Test.QuickCheck
+
+import Tests.Instances ()
+import qualified Data.Text as T
+import Graphics.Vty.Widgets.TextClip
+import Graphics.Vty.Widgets.Util
+
+rectGen :: Gen ClipRect
+rectGen = ClipRect
+          <$> (arbitrary `suchThat` (>= Phys 0))
+          <*> (arbitrary `suchThat` (>= Phys 0))
+          <*> (arbitrary `suchThat` (>= Phys 0))
+          <*> (arbitrary `suchThat` (>= Phys 0))
+
+textGen :: Gen T.Text
+textGen = T.pack <$> (listOf $ oneof [ pure 'a'
+                                     , pure ' '
+                                     , pure '台'
+                                     ])
+
+docGen :: Gen [T.Text]
+docGen = listOf1 textGen
+
+clip1dWidth :: Property
+clip1dWidth =
+    forAll rectGen $ \rect ->
+        forAll textGen $ \t ->
+            let (r, _, _) = clip1d (clipLeft rect) (clipWidth rect) t
+            in textWidth r <= clipWidth rect
+
+clipSliceLeft :: Bool
+clipSliceLeft =
+    let (_, ls, _) = clip1d (Phys 1) (Phys 2) $ T.pack "台台"
+    in ls
+
+clipSliceRight :: Bool
+clipSliceRight =
+    let (_, _, rs) = clip1d (Phys 1) (Phys 2) $ T.pack "台台"
+    in rs
+
+clip2dBounds :: Property
+clip2dBounds =
+    forAll rectGen $ \rect ->
+        forAll docGen $ \t ->
+            let result = clip2d rect t
+                check (r, _, _) = textWidth r <= clipWidth rect
+            in all check result && (Phys $ length result) <= clipHeight rect
+
+tests :: [Property]
+tests = [ label "clip1d guarantees width bound" clip1dWidth
+        , label "clip1d indicates left slices" $ property clipSliceLeft
+        , label "clip1d indicates right slices" $ property clipSliceRight
+        , label "clip2d guarantees width and height bounds" clip2dBounds
+        ]
diff --git a/test/src/Tests/TextZipper.hs b/test/src/Tests/TextZipper.hs
new file mode 100644
--- /dev/null
+++ b/test/src/Tests/TextZipper.hs
@@ -0,0 +1,43 @@
+module Tests.TextZipper
+    ( tests
+    )
+where
+
+import Control.Applicative
+import Test.QuickCheck
+
+import Tests.Instances ()
+import qualified Data.Text as T
+import Graphics.Vty.Widgets.TextZipper
+
+textGen :: Gen T.Text
+textGen = T.pack <$> (listOf $ oneof [ pure 'a'
+                                     , pure ' '
+                                     ])
+
+docGen :: Gen [T.Text]
+docGen = listOf1 textGen
+
+validCursorPositions :: [T.Text] -> Gen (Int, Int)
+validCursorPositions ts = do
+  r <- elements [0..length ts - 1]
+  c <- elements [0..T.length (ts !! r)]
+  return (r, c)
+
+cursorCheck :: Property
+cursorCheck =
+    forAll docGen $ \doc ->
+        forAll (validCursorPositions doc) $ \pos ->
+            pos == cursorPosition (moveCursor pos $ textZipper doc)
+
+insertDelCheck :: Property
+insertDelCheck =
+    forAll docGen $ \doc ->
+        forAll (validCursorPositions doc) $ \pos ->
+            let orig = moveCursor pos $ textZipper doc
+            in orig == (deletePrevChar $ insertChar 'X' orig)
+
+tests :: [Property]
+tests = [ label "get/set cursor position are consistent" cursorCheck
+        , label "insert/delete are inverses" insertDelCheck
+        ]
diff --git a/test/src/Tests/Tokenize.hs b/test/src/Tests/Tokenize.hs
--- a/test/src/Tests/Tokenize.hs
+++ b/test/src/Tests/Tokenize.hs
@@ -1,40 +1,52 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE OverloadedStrings #-}
 module Tests.Tokenize where
 
+import Control.Applicative
 import Data.Char ( isPrint )
 import Test.QuickCheck
-
+import Tests.Util
 import Text.Trans.Tokenize
+import Graphics.Vty.Widgets.Util
+import qualified Data.Text as T
 
 lineGen :: Gen [Token ()]
 lineGen = listOf1 $ oneof [wsgen, strgen]
     where
       strgen = do
         s <- stringGen
-        return $ S s ()
+        return $ S (T.pack s) ()
 
       wsgen = do
         s <- listOf1 $ elements " \t"
-        return $ WS s ()
+        return $ WS (T.pack s) ()
 
 stringGen :: Gen String
 stringGen = listOf1 charGen
 
+textGen :: Gen T.Text
+textGen = T.pack <$> stringGen
+
 charGen :: Gen Char
-charGen = arbitrary `suchThat` (\c -> isPrint c)
+charGen = oneof [ arbitrary `suchThat` (\c -> isPrint c)
+                , pure '台'
+                ]
 
 tests :: [Property]
-tests = [ label "tokenize and serialize work" $ property $ forAll stringGen $
+tests = [ label "tokenize and serialize work" $
+                property $ forAll textGen $
                     \s -> (serialize $ tokenize s ()) == s
 
-        , label "truncateLine leaves short lines unchanged" $ property $ forAll lineGen $
-                    \ts -> ts == truncateLine (length $ serialize (TS (map T ts))) ts
+        , label "truncateLine leaves short lines unchanged" $
+                property $ forAll lineGen $
+                    \ts -> ts == truncateLine (lineLength ts) ts
 
         -- Bound the truncation width at twice the size of the input
         -- since huge cases are silly.
-        , label "truncateLine truncates long lines" $ property $ forAll lineGen $
-                    \ts -> forAll (choose (0, 2 * (length $ serialize (TS (map T ts))))) $
-                           \width -> length (serialize $ (TS (map T $ truncateLine width ts))) <= width
+        , label "truncateLine truncates long lines" $
+                property $ forAll lineGen $
+                    \ts -> forAll (choose (0, 2 * (lineLength ts))) $
+                           \width -> (lineLength $ truncateLine width ts) <= width
 
         , label "wrapStream does the right thing with whitespace" $ property $
                 and [ wrapStream 5 (TS [T (S "foo" ()), T (WS " " ()), T (S "bar" ())]) ==
@@ -90,8 +102,9 @@
         -- be broken up.
         , label "wrapLine wraps long lines when possible" $
                 property $ forAll lineGen $
-                    \ts -> forAll (choose ((length $ tokenStr $ ts !! 0), (length $ serialize (TS $ map T ts)) - 1)) $
-                           \width -> let ls = findLines new
-                                         TS new = wrapStream width $ TS $ map T ts
-                                     in all (\l -> (length (serialize $ TS l)) <= width || (length l == 1)) ls
+                    \ts -> forAll (choose ((textWidth $ tokenStr $ ts !! 0), (lineLength ts - Phys 1))) $
+                           \width -> let TS new = wrapStream width $ TS $ T <$> ts
+                                         ls = findLines new
+                                         check l = (lineLength $ entityToken <$> l) <= width || (length l == 1)
+                                     in all check ls
         ]
diff --git a/test/src/Tests/Util.hs b/test/src/Tests/Util.hs
--- a/test/src/Tests/Util.hs
+++ b/test/src/Tests/Util.hs
@@ -1,7 +1,11 @@
 module Tests.Util where
 
+import Control.Applicative
+import qualified Data.Text as T
 import Graphics.Vty
 import Graphics.Vty.Widgets.Core
+import Graphics.Vty.Widgets.Util
+import Text.Trans.Tokenize
 import Test.QuickCheck
 import Test.QuickCheck.Monadic
 import Tests.Instances ()
@@ -14,8 +18,8 @@
 count _ [] = 0
 count f (a:as) = count f as + if f a then 1 else 0
 
-numNewlines :: String -> Int
-numNewlines = count (== '\n')
+numNewlines :: T.Text -> Int
+numNewlines = count (== '\n') . T.unpack
 
 sizeTest :: (Show a) => IO (Widget a) -> PropertyM IO Bool
 sizeTest mkWidget =
@@ -26,3 +30,6 @@
           return $ image_height img == 0 && image_width img == 0 else
           return $ image_width img <= region_width sz &&
                  image_height img <= region_height sz
+
+lineLength :: [Token a] -> Phys
+lineLength = sum . (textWidth <$>) . (tokenStr <$>)
diff --git a/vty-ui.cabal b/vty-ui.cabal
--- a/vty-ui.cabal
+++ b/vty-ui.cabal
@@ -1,5 +1,5 @@
 Name:                vty-ui
-Version:             1.5.1
+Version:             1.6
 Synopsis:
   An interactive terminal user interface library for Vty
 
@@ -23,7 +23,7 @@
 Build-Type:          Simple
 License:             BSD3
 License-File:        LICENSE
-Cabal-Version:       >= 1.6
+Cabal-Version:       >= 1.8
 Homepage:            http://jtdaugherty.github.com/vty-ui/
 
 Data-Files:
@@ -65,6 +65,9 @@
     doc/ch4/Padded.tex
     doc/ch4/ProgressBar.tex
     doc/ch4/Table.tex
+    doc/ch5/main.tex
+    doc/ch5/TextClip.tex
+    doc/ch5/TextZipper.tex
     doc/macros.tex
     doc/Makefile
     doc/vty-ui-users-manual.tex
@@ -75,8 +78,8 @@
   type:     git
   location: git://github.com/jtdaugherty/vty-ui.git
 
-Flag testing
-    Description:     Build for testing
+Flag no-tests
+    Description:     Skip build of testing executable
     Default:         False
 
 Flag demos
@@ -86,15 +89,16 @@
 Library
   Build-Depends:
     base >= 4 && < 5,
-    vty >= 4.6 && < 4.8,
-    containers >= 0.2 && < 0.5,
+    vty >= 4.7.0.18,
+    containers >= 0.2 && < 0.6,
     regex-base >= 0.93 && < 0.94,
-    directory >= 1.0 && < 1.2,
+    directory >= 1.0 && < 1.3,
     filepath >= 1.1 && < 1.4,
-    unix >= 2.4 && < 2.6,
-    mtl >= 2.0 && < 2.1,
-    stm >= 2.1 && < 2.3,
+    unix >= 2.4 && < 2.7,
+    mtl >= 2.0 && < 2.2,
+    stm >= 2.1 && < 2.5,
     array >= 0.3.0.0 && < 0.5.0.0,
+    text >= 0.11,
     vector >= 0.9
 
   GHC-Options:       -Wall
@@ -104,6 +108,8 @@
           Graphics.Vty.Widgets.All
           Graphics.Vty.Widgets.Alignment
           Graphics.Vty.Widgets.Text
+          Graphics.Vty.Widgets.TextClip
+          Graphics.Vty.Widgets.TextZipper
           Graphics.Vty.Widgets.Core
           Graphics.Vty.Widgets.Box
           Graphics.Vty.Widgets.List
@@ -129,115 +135,119 @@
 
 Executable vty-ui-tests
   Build-Depends:
-    QuickCheck >= 2.4 && < 2.5
+    base >= 4 && < 5,
+    QuickCheck >= 2.4 && < 2.6,
+    random >= 1.0,
+    text,
+    vty,
+    vty-ui
 
   CPP-Options: -DTESTING
   GHC-Options: -Wall
 
-  if !flag(testing)
+  if flag(no-tests)
     Buildable:     False
   end
   Hs-Source-Dirs:  src,test,test/src
   Main-is:         TestDriver.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
 
   Other-Modules:
         Tests.Instances
         Tests.Util
+        Tests.Edit
         Tests.FormattedText
         Tests.Tokenize
+        Tests.TextClip
+        Tests.TextZipper
 
 Executable vty-ui-list-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         ListDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    text,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-progressbar-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         ProgressBarDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    text,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-complex-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         ComplexDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
+    mtl >= 2.0 && < 2.2,
     bytestring >= 0.9 && < 1.0,
     time >= 1.1 && < 1.5,
     old-locale >= 1.0 && < 1.1,
-    vty >= 4.6 && < 4.8
+    vty >= 4.7.0.18,
+    text,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-dirbrowser-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         DirBrowserDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-phoneinput-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         PhoneInputDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    text,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-dialog-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         DialogDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    text,
+    vty-ui
   if !flag(demos)
     Buildable: False
 
 Executable vty-ui-edit-demo
-  Hs-Source-Dirs:  src
+  Hs-Source-Dirs:  demos
   GHC-Options:     -Wall
   Main-is:         EditDemo.hs
-  if os(darwin)
-    Extra-Lib-Dirs:  /usr/lib
   Build-Depends:
     base >= 4 && < 5,
-    mtl >= 2.0 && < 2.1,
-    vty >= 4.6 && < 4.8
+    mtl >= 2.0 && < 2.2,
+    vty >= 4.7.0.18,
+    vty-ui
   if !flag(demos)
     Buildable: False
