diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
 Brick changelog
 ---------------
 
+0.45
+----
+
+API changes:
+ * List got a new `listFindBy` function (thanks Fraser Tweedale). This
+   function uses a predicate to find a matching element in the list and
+   move the cursor to that item.
+ * Data.Text.Markup got a new `empty` function (#213)
+
 0.44.1
 ------
 
diff --git a/brick.cabal b/brick.cabal
--- a/brick.cabal
+++ b/brick.cabal
@@ -1,5 +1,5 @@
 name:                brick
-version:             0.44.1
+version:             0.45
 synopsis:            A declarative terminal user interface library
 description:
   Write terminal applications painlessly with 'brick'! You write an
diff --git a/src/Brick/Widgets/Border/Style.hs b/src/Brick/Widgets/Border/Style.hs
--- a/src/Brick/Widgets/Border/Style.hs
+++ b/src/Brick/Widgets/Border/Style.hs
@@ -10,8 +10,7 @@
 --
 -- To use these in your widgets, see
 -- 'Brick.Widgets.Core.withBorderStyle'. By default, widgets rendered
--- without a specified border style use 'unicode' via the 'Default'
--- instance provided by 'BorderStyle'.
+-- without a specified border style use 'unicode' style.
 module Brick.Widgets.Border.Style
   ( BorderStyle(..)
   , borderStyleFromChar
diff --git a/src/Brick/Widgets/FileBrowser.hs b/src/Brick/Widgets/FileBrowser.hs
--- a/src/Brick/Widgets/FileBrowser.hs
+++ b/src/Brick/Widgets/FileBrowser.hs
@@ -293,7 +293,7 @@
 -- search string and/or entry filtering.
 --
 -- If the directory scan raises an 'IOException', the exception is
--- stored the browser and is accessible with 'fileBrowserException'. If
+-- stored in the browser and is accessible with 'fileBrowserException'. If
 -- no exception is raised, the exception field is cleared. Regardless of
 -- whether an exception is raised, @..@ is always presented as a valid
 -- option in the browser.
diff --git a/src/Brick/Widgets/List.hs b/src/Brick/Widgets/List.hs
--- a/src/Brick/Widgets/List.hs
+++ b/src/Brick/Widgets/List.hs
@@ -41,6 +41,7 @@
   , listMoveBy
   , listMoveTo
   , listMoveToElement
+  , listFindBy
   , listMoveUp
   , listMoveDown
   , listMoveByPages
@@ -67,16 +68,15 @@
 import Prelude hiding (reverse, splitAt)
 
 #if !MIN_VERSION_base(4,8,0)
-import Control.Applicative ((<$>), (<*>), pure, (<|>))
+import Control.Applicative ((<$>), (<*>), pure)
 import Data.Foldable (Foldable, find, toList)
 import Data.Traversable (Traversable)
 #else
-import Control.Applicative ((<|>))
 import Data.Foldable (find, toList)
 #endif
 import Control.Monad.Trans.State (evalState, get, put)
 
-import Lens.Micro ((^.), (^?), (&), (.~), (%~), _2, _head)
+import Lens.Micro ((^.), (^?), (&), (.~), (%~), _2, _head, set)
 import Data.Functor (($>))
 import Data.List.NonEmpty (NonEmpty((:|)))
 import Data.Maybe (fromMaybe)
@@ -545,18 +545,32 @@
         -- is in the list, so we don't need to know the length
         clamp 0 (if null t then length (l ^. listElementsL) - 1 else i) i
 
--- | Set the selected index for a list to the index of the specified
--- element if it is in the list, or leave the list unmodified otherwise.
+-- | Set the selected index for a list to the index of the first
+-- occurence of the specified element if it is in the list, or leave
+-- the list unmodified otherwise.
 --
--- Complexity: same as 'traverse' for the container type (typically
--- /O(n)/).
-listMoveToElement :: (Eq e, Traversable t)
+-- /O(n)/.  Only evaluates as much of the container as needed.
+listMoveToElement :: (Eq e, Foldable t, Splittable t)
                   => e
                   -> GenericList n t e
                   -> GenericList n t e
-listMoveToElement e l =
-    let i = fmap fst $ find ((== e) . snd) $ imap (,) (l^.listElementsL)
-    in l & listSelectedL %~ (i <|>)
+listMoveToElement e = listFindBy (== e) . set listSelectedL Nothing
+
+-- | Set the selected index to the next element matching the
+-- predicate.  If there is no selected element, the search starts at
+-- the beginning.  If no matching element is found, leave the list
+-- unmodified.
+--
+-- /O(n)/.  Only evaluates as much of the container as needed.
+listFindBy :: (Foldable t, Splittable t)
+           => (e -> Bool)
+           -> GenericList n t e
+           -> GenericList n t e
+listFindBy test l =
+    let start = maybe 0 (+1) (l ^. listSelectedL)
+        (_, t) = splitAt start (l ^. listElementsL)
+        result = find (test . snd) . zip [0..] . toList $ t
+    in maybe id (set listSelectedL . Just . (start +) . fst) result l
 
 -- | Return a list's selected element, if any.
 --
diff --git a/src/Data/Text/Markup.hs b/src/Data/Text/Markup.hs
--- a/src/Data/Text/Markup.hs
+++ b/src/Data/Text/Markup.hs
@@ -12,6 +12,7 @@
   , fromList
   , fromText
   , toText
+  , empty
   , (@@)
   )
 where
@@ -51,6 +52,10 @@
 -- | Extract the text from markup, discarding the markup metadata.
 toText :: (Eq a) => Markup a -> T.Text
 toText = T.concat . (fst <$>) . concat . markupToList
+
+-- | Test whether the markup is empty.
+empty :: Markup a -> Bool
+empty (Markup ls) = null ls
 
 -- | Set the metadata for a range of character positions in a piece of
 -- markup. This is useful for, e.g., syntax highlighting.
diff --git a/tests/List.hs b/tests/List.hs
--- a/tests/List.hs
+++ b/tests/List.hs
@@ -12,6 +12,7 @@
 
 import Prelude hiding (reverse, splitAt)
 
+import Data.Foldable (find)
 import Data.Function (on)
 import qualified Data.List
 import Data.Maybe (isNothing)
@@ -38,6 +39,7 @@
   | MoveBy Int
   | MoveTo Int
   | MoveToElement a
+  | FindElement a
   deriving (Show)
 
 instance Arbitrary a => Arbitrary (ListMoveOp a) where
@@ -47,6 +49,7 @@
     , MoveBy <$> arbitrary
     , MoveTo <$> arbitrary
     , MoveToElement <$> arbitrary
+    , FindElement <$> arbitrary
     ]
 
 -- List operations.  We don't have "page"-based movement operations
@@ -67,7 +70,7 @@
     , (1, Replace <$> arbitrary <*> arbitrary)
     , (1, pure Clear)
     , (1, pure Reverse)
-    , (5, arbitrary)
+    , (6, arbitrary)
     ]
 
 -- Turn a ListOp into a List endomorphism
@@ -88,6 +91,7 @@
 moveOp (MoveBy n) = listMoveBy n
 moveOp (MoveTo n) = listMoveTo n
 moveOp (MoveToElement a) = listMoveToElement a
+moveOp (FindElement a) = listFindBy (== a)
 
 applyListOps
   :: (Foldable t)
@@ -155,6 +159,31 @@
   in
     fmap snd sel == Just a
 
+-- inserting an element and repeatedly seeking it always
+-- reaches the element we inserted, at the index where we
+-- inserted it.
+--
+prop_insertFindBy :: (Eq a) => [ListOp a] -> List n a -> Int -> a -> Bool
+prop_insertFindBy ops l i a =
+  let
+    l' = applyListOps op ops l
+    l'' = set listSelectedL Nothing . listInsert i a $ l'
+    seeks = converging ((==) `on` (^. listSelectedL)) (listFindBy (== a)) l''
+    i' = clamp 0 (length (l' ^. listElementsL)) i -- we can't have inserted past len
+  in
+    (find ((== Just i') . (^. listSelectedL)) seeks >>= listSelectedElement)
+    == Just (i', a)
+
+-- Find will never decrease the selected index.  Note that we don't
+-- need to handle the 'Nothing' case because ∀a. Nothing < Just a.
+prop_findByNonDecreasing :: (Eq a) => [ListOp a] -> List n a -> a -> Bool
+prop_findByNonDecreasing ops l a =
+  let
+    l' = applyListOps op ops l
+    l'' = listFindBy (== a) l'
+  in
+    l' ^. listSelectedL <= l'' ^. listSelectedL
+
 -- inserting then deleting always yields a list with the original elems
 prop_insertRemove :: (Eq a) => Int -> a -> List n a -> Bool
 prop_insertRemove i a l =
@@ -188,11 +217,17 @@
   in
     l' ^. listElementsL == l ^. listElementsL
 
+-- Apply @f@ until @test a (f a) == True@, then return @a@.
 converge :: (a -> a -> Bool) -> (a -> a) -> a -> a
-converge test f a
-  | test (f a) a = a
-  | otherwise = converge test f (f a)
+converge test f = last . converging test f
 
+-- Apply @f@ until @test a (f a) == True@, returning the start,
+-- intermediate and final values as a list.
+converging :: (a -> a -> Bool) -> (a -> a) -> a -> [a]
+converging test f a
+  | test a (f a) = [a]
+  | otherwise = a : converging test f (f a)
+
 -- listMoveUp always reaches 0 (or list is empty)
 prop_moveUp :: (Eq a) => [ListOp a] -> List n a -> Bool
 prop_moveUp ops l =
@@ -338,6 +373,18 @@
     l' = listMoveBy 1 l
   in
     l' ^. listSelectedL == Just 1
+
+-- listFindBy is lazy
+prop_findByLazy :: Bool
+prop_findByLazy =
+  let
+    v = L (1:2:3:4:undefined) :: L Int
+    l = list () v 1 & listSelectedL .~ Nothing
+    l' = listFindBy even l
+    l'' = listFindBy even l'
+  in
+    l' ^. listSelectedL == Just 1
+    && l'' ^. listSelectedL == Just 3
 
 
 return []
