diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.1.0.3
+
+- Add `prettySpan` function
+
 # 0.1.0.2
 
 - Remove unused `bytestring` dependency
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Olle Fredriksson (c) 2017
+Copyright Olle Fredriksson (c) 2017-2019
 
 All rights reserved.
 
diff --git a/parsix.cabal b/parsix.cabal
--- a/parsix.cabal
+++ b/parsix.cabal
@@ -1,5 +1,5 @@
 name:                parsix
-version:             0.1.0.2
+version:             0.1.0.3
 synopsis:            Parser combinators with slicing, error recovery, and syntax highlighting
 description:         A parser combinator library based on 'parsers' (like 'trifecta') with slicing, error recovery, and syntax highlighted diagnostics
 homepage:            https://github.com/ollef/parsix
@@ -7,7 +7,7 @@
 license-file:        LICENSE
 author:              Olle Fredriksson
 maintainer:          fredriksson.olle@gmail.com
-copyright:           2017-2018 Olle Fredriksson
+copyright:           2017-2019 Olle Fredriksson
 category:            Parsing
 build-type:          Simple
 extra-source-files:  README.md
diff --git a/src/Text/Parsix/Parser/Internal.hs b/src/Text/Parsix/Parser/Internal.hs
--- a/src/Text/Parsix/Parser/Internal.hs
+++ b/src/Text/Parsix/Parser/Internal.hs
@@ -165,6 +165,21 @@
   lookAhead (Parser p) = Parser
     $ \s0 s e0 e pos -> p s0 (\a _ _ -> s a mempty pos) e0 e pos
 
+runParser
+  :: Parser a
+  -> Text
+  -> FilePath
+  -> Position
+  -> Result (a, Position, Highlights)
+runParser (Parser p) inp file start = p
+  (\res _ -> Success (res, start, mempty))
+  (\res _ pos hl -> Success (res, pos, hl))
+  (\err -> Failure $ Error err start inp mempty file)
+  (\err pos hl -> Failure $ Error err pos inp hl file)
+  start
+  mempty
+  inp
+
 parseFromFile :: MonadIO m => Parser a -> FilePath -> m (Maybe a)
 parseFromFile p file = do
   result <- parseFromFileEx p file
@@ -182,16 +197,7 @@
 -- | @parseText p i file@ runs a parser @p@ on @i@. @file@ is only used for
 -- reporting errors.
 parseText :: Parser a -> Text -> FilePath -> Result a
-parseText (Parser p) inp file = p
-  (\res _ -> Success res)
-  (\res _ _pos _hl -> Success res)
-  (\err -> Failure $ Error err start inp mempty file)
-  (\err pos hl -> Failure $ Error err pos inp hl file)
-  start
-  mempty
-  inp
-  where
-    start = Position 0 0 0
+parseText p inp file = (\(a, _, _) -> a) <$> runParser p inp file (Position 0 0 0)
 
 -- | @parseString p i file@ runs a parser @p@ on @i@. @file@ is only used for
 -- reporting errors.
diff --git a/src/Text/Parsix/Position.hs b/src/Text/Parsix/Position.hs
--- a/src/Text/Parsix/Position.hs
+++ b/src/Text/Parsix/Position.hs
@@ -44,27 +44,43 @@
   -> Text
   -> Highlights
   -> Doc AnsiStyle
-prettyPosition style pos inp hl
-  = rowStringPadding <> bar <> line
-  <> prettyRow <> bar <+> fmap style (positionRow pos inp hl) <> line
-  <> rowStringPadding <> bar <+> pretty positionPadding <> annotate (color Red) "^"
+prettyPosition style pos = prettySpan style $ Span pos pos
+
+data Span = Span
+  { spanStart :: !Position
+  , spanEnd :: !Position
+  } deriving (Eq, Ord, Show)
+
+prettySpan
+  :: (Highlight -> AnsiStyle)
+  -> Span
+  -> Text
+  -> Highlights
+  -> Doc AnsiStyle
+prettySpan style (Span startPos endPos) inp hl
+  = rowNumberStringPadding <> bar <> line
+  <> prettyRowNumber <> bar <+> fmap style rowString <> line
+  <> rowNumberStringPadding <> bar <+> pretty positionPadding <> annotate (color Red) ("^" <> pretty (Text.replicate squiggleLength "~"))
   where
+    rowString = positionRow startPos inp hl
     barHighlight = annotate (color Blue)
     bar = barHighlight "|"
-    prettyRow = barHighlight $ pretty rowString
-    rowString = Text.pack (show $ visualRow pos + 1) <> " "
-    rowStringPadding = pretty $ Text.replicate (Text.length rowString) " "
+    prettyRowNumber = barHighlight $ pretty rowNumberString
+    rowNumberString = Text.pack (show $ visualRow startPos + 1) <> " "
+    rowNumberStringPadding = pretty $ Text.replicate (Text.length rowNumberString) " "
 
     positionPadding
       = Text.map go
       $ codeUnitSlice start end inp
       where
         start = prevNewline inp end
-        end = codeUnits pos
+        end = codeUnits startPos
         go '\t' = '\t'
         go _ = ' '
 
-data Span = Span
-  { spanStart :: !Position
-  , spanEnd :: !Position
-  } deriving (Eq, Ord, Show)
+    squiggleEnd =
+      if visualRow endPos > visualRow startPos then
+        nextNewline inp $ codeUnits startPos
+      else
+       codeUnits endPos
+    squiggleLength = squiggleEnd - codeUnits startPos - 1
diff --git a/tests/Empty.hs b/tests/Empty.hs
--- a/tests/Empty.hs
+++ b/tests/Empty.hs
@@ -6,6 +6,6 @@
 
 tests :: TestTree
 tests = testGroup "Empty parsers"
-  [ testProperty "mempty succeeds" $ isSuccess . parse (mempty :: Parser ())
+  [ testProperty "mempty succeeds" $ Util.isSuccess . parse (mempty :: Parser ())
   , testProperty "empty fails" $ isFailure . parse empty
   ]
diff --git a/tests/NotFollowedBy.hs b/tests/NotFollowedBy.hs
--- a/tests/NotFollowedBy.hs
+++ b/tests/NotFollowedBy.hs
@@ -16,6 +16,6 @@
     $ \x y -> isFailure
     $ parse (anyChar *> notFollowedBy (char y) *> char y) x
   , testProperty "4"
-    $ \x -> isSuccess
+    $ \x -> Util.isSuccess
     $ parse (notFollowedBy (char x) <|> char x *> pure ()) [x]
   ]
