diff --git a/megaparsec-tests.cabal b/megaparsec-tests.cabal
--- a/megaparsec-tests.cabal
+++ b/megaparsec-tests.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            megaparsec-tests
-version:         9.8.1
+version:         9.8.2
 license:         BSD-2-Clause
 license-file:    LICENSE.md
 maintainer:      Mark Karpov <markkarpov92@gmail.com>
@@ -32,7 +32,7 @@
         containers >=0.5 && <0.9,
         hspec >=2 && <3,
         hspec-megaparsec >=2 && <3,
-        megaparsec ==9.8.1,
+        megaparsec ==9.8.2,
         mtl >=2.2.2 && <3,
         text >=0.2 && <2.2,
         transformers >=0.4 && <0.7
@@ -40,7 +40,7 @@
     if flag(dev)
         ghc-options:
             -Wall -Werror -Wredundant-constraints -Wpartial-fields
-            -Wunused-packages
+            -Wunused-packages -haddock -Winvalid-haddock
 
     else
         ghc-options: -O2 -Wall
@@ -72,7 +72,7 @@
         containers >=0.5 && <0.9,
         hspec >=2 && <3,
         hspec-megaparsec >=2 && <3,
-        megaparsec ==9.8.1,
+        megaparsec ==9.8.2,
         megaparsec-tests,
         mtl >=2.2.2 && <3,
         scientific >=0.3.1 && <0.4,
@@ -83,7 +83,7 @@
     if flag(dev)
         ghc-options:
             -Wall -Werror -Wredundant-constraints -Wpartial-fields
-            -Wunused-packages
+            -Wunused-packages -haddock -Winvalid-haddock
 
     else
         ghc-options: -O2 -Wall
diff --git a/tests/Text/Megaparsec/Byte/LexerSpec.hs b/tests/Text/Megaparsec/Byte/LexerSpec.hs
--- a/tests/Text/Megaparsec/Byte/LexerSpec.hs
+++ b/tests/Text/Megaparsec/Byte/LexerSpec.hs
@@ -78,10 +78,10 @@
             let p = decimal :: Parser Integer
                 s = B.pack (a : as)
             prs p s `shouldFailWith` err 0 (utok a <> elabel "integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (decimal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (decimal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "integer")
 
   describe "binary" $ do
     context "when stream begins with binary digits" $
@@ -100,10 +100,10 @@
                 s = B.pack (a : as)
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "binary integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (binary :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "binary integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (binary :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "binary integer")
 
   describe "octal" $ do
     context "when stream begins with octal digits" $
@@ -122,10 +122,10 @@
                 s = B.pack (a : as)
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "octal integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (octal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "octal integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (octal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "octal integer")
 
   describe "hexadecimal" $ do
     context "when stream begins with hexadecimal digits" $
@@ -152,10 +152,10 @@
                 s = B.pack (a : as)
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "hexadecimal integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (hexadecimal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "hexadecimal integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (hexadecimal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "hexadecimal integer")
 
   describe "scientific" $ do
     context "when stream begins with a number" $
@@ -196,10 +196,10 @@
               s = B8.pack $ showFFloatAlt Nothing (n :: Double) "err!"
           prs p s `shouldParse` fromFloatDigits n
           prs' p s `succeedsLeaving` "err!"
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (scientific :: Parser Scientific) ""
-          `shouldFailWith` err 0 (ueof <> elabel "digit")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (scientific :: Parser Scientific) ""
+        `shouldFailWith` err 0 (ueof <> elabel "digit")
 
   describe "float" $ do
     context "when stream begins with a float" $
@@ -237,10 +237,10 @@
               s = B8.pack $ showFFloatAlt Nothing (n :: Double) "err!"
           prs p s `shouldParse` n
           prs' p s `succeedsLeaving` "err!"
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (float :: Parser Double) ""
-          `shouldFailWith` err 0 (ueof <> elabel "digit")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (float :: Parser Double) ""
+        `shouldFailWith` err 0 (ueof <> elabel "digit")
     context "when there is float with just exponent" $
       it "parses it all right" $
         do
diff --git a/tests/Text/Megaparsec/ByteSpec.hs b/tests/Text/Megaparsec/ByteSpec.hs
--- a/tests/Text/Megaparsec/ByteSpec.hs
+++ b/tests/Text/Megaparsec/ByteSpec.hs
@@ -46,12 +46,12 @@
             let s = "\r" <> B.singleton ch
             prs eol s
               `shouldFailWith` err 0 (utoks s <> elabel "end of line")
-    context "when input stream is '\\r'" $
-      it "signals correct parse error" $
-        prs eol "\r"
-          `shouldFailWith` err
-            0
-            (utok 13 <> elabel "end of line")
+    context "when input stream is '\\r'"
+      $ it "signals correct parse error"
+      $ prs eol "\r"
+        `shouldFailWith` err
+          0
+          (utok 13 <> elabel "end of line")
     context "when stream does not begin with newline or CRLF sequence" $
       it "signals correct parse error" $
         property $ \ch s ->
@@ -61,12 +61,12 @@
               `shouldFailWith` err
                 0
                 (utoks (B.take 2 s') <> elabel "end of line")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs eol ""
-          `shouldFailWith` err
-            0
-            (ueof <> elabel "end of line")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs eol ""
+        `shouldFailWith` err
+          0
+          (ueof <> elabel "end of line")
 
   describe "tab" $
     checkStrLit "tab" "\t" (tokenToChunk bproxy <$> tab)
@@ -103,9 +103,9 @@
               s = " " <> s0 <> s1
           prs space1 s `shouldParse` ()
           prs' space1 s `succeedsLeaving` s1
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs space1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs space1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
 
   describe "hspace1" $ do
     context "when stream does not start with a space character" $
@@ -123,9 +123,9 @@
               s = " " <> s0 <> s1
           prs hspace1 s `shouldParse` ()
           prs' hspace1 s `succeedsLeaving` s1
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs hspace1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs hspace1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
 
   describe "controlChar" $
     checkCharPred "control character" (isControl . toChar) controlChar
@@ -194,9 +194,9 @@
               us = B.take (B.length ts) s'
           prs p s' `shouldFailWith` err 0 (utoks us <> etoks ts)
           prs' p s' `failsLeaving` s'
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> etoks ts)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> etoks ts)
 
 checkCharPred :: String -> (Word8 -> Bool) -> Parser Word8 -> SpecWith ()
 checkCharPred name f p = do
@@ -214,9 +214,9 @@
           let s' = B.singleton ch <> s
           prs p s' `shouldFailWith` err 0 (utok ch <> elabel name)
           prs' p s' `failsLeaving` s'
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
 
 checkCharRange :: String -> [Word8] -> Parser Word8 -> SpecWith ()
 checkCharRange name tchs p = do
@@ -227,9 +227,9 @@
           let s' = B.singleton tch <> s
           prs p s' `shouldParse` tch
           prs' p s' `succeedsLeaving` s
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
 
 prs ::
   -- | Parser to run
diff --git a/tests/Text/Megaparsec/Char/LexerSpec.hs b/tests/Text/Megaparsec/Char/LexerSpec.hs
--- a/tests/Text/Megaparsec/Char/LexerSpec.hs
+++ b/tests/Text/Megaparsec/Char/LexerSpec.hs
@@ -288,9 +288,9 @@
           let s = "\\"
           prs p s `shouldFailWith` err 0 (utok '\\' <> elabel "literal character")
           prs' p s `failsLeaving` s
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs p "" `shouldFailWith` err 0 (ueof <> elabel "literal character")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs p "" `shouldFailWith` err 0 (ueof <> elabel "literal character")
     context "when given a long escape sequence" $
       it "parses it correctly" $
         property $ \s' -> do
@@ -321,10 +321,10 @@
             let p = decimal :: Parser Integer
                 s = a : as
             prs p s `shouldFailWith` err 0 (utok a <> elabel "integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (decimal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (decimal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "integer")
 
   describe "binary" $ do
     context "when stream begins with binary digits" $
@@ -343,10 +343,10 @@
                 s = a : as
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "binary integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (binary :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "binary integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (binary :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "binary integer")
 
   describe "octal" $ do
     context "when stream begins with octal digits" $
@@ -365,10 +365,10 @@
                 s = a : as
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "octal integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (octal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "octal integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (octal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "octal integer")
 
   describe "hexadecimal" $ do
     context "when stream begins with hexadecimal digits" $
@@ -387,10 +387,10 @@
                 s = a : as
             prs p s
               `shouldFailWith` err 0 (utok a <> elabel "hexadecimal integer")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (hexadecimal :: Parser Integer) ""
-          `shouldFailWith` err 0 (ueof <> elabel "hexadecimal integer")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (hexadecimal :: Parser Integer) ""
+        `shouldFailWith` err 0 (ueof <> elabel "hexadecimal integer")
 
   describe "scientific" $ do
     context "when stream begins with a number" $
@@ -430,10 +430,10 @@
               s = showFFloatAlt Nothing (n :: Double) "err!"
           prs p s `shouldParse` fromFloatDigits n
           prs' p s `succeedsLeaving` "err!"
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (scientific :: Parser Scientific) ""
-          `shouldFailWith` err 0 (ueof <> elabel "digit")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (scientific :: Parser Scientific) ""
+        `shouldFailWith` err 0 (ueof <> elabel "digit")
 
   describe "float" $ do
     context "when stream begins with a float" $
@@ -471,10 +471,10 @@
               s = showFFloatAlt Nothing (n :: Double) "err!"
           prs p s `shouldParse` n
           prs' p s `succeedsLeaving` "err!"
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs (float :: Parser Double) ""
-          `shouldFailWith` err 0 (ueof <> elabel "digit")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs (float :: Parser Double) ""
+        `shouldFailWith` err 0 (ueof <> elabel "digit")
     context "when there is float with just exponent" $
       it "parses it all right" $
         do
diff --git a/tests/Text/Megaparsec/CharSpec.hs b/tests/Text/Megaparsec/CharSpec.hs
--- a/tests/Text/Megaparsec/CharSpec.hs
+++ b/tests/Text/Megaparsec/CharSpec.hs
@@ -38,12 +38,12 @@
           ch /= '\n' ==> do
             let s = ['\r', ch]
             prs eol s `shouldFailWith` err 0 (utoks s <> elabel "end of line")
-    context "when input stream is '\\r'" $
-      it "signals correct parse error" $
-        prs eol "\r"
-          `shouldFailWith` err
-            0
-            (utok '\r' <> elabel "end of line")
+    context "when input stream is '\\r'"
+      $ it "signals correct parse error"
+      $ prs eol "\r"
+        `shouldFailWith` err
+          0
+          (utok '\r' <> elabel "end of line")
     context "when stream does not begin with newline or CRLF sequence" $
       it "signals correct parse error" $
         property $ \ch s ->
@@ -53,12 +53,12 @@
               `shouldFailWith` err
                 0
                 (utoks (take 2 s') <> elabel "end of line")
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs eol ""
-          `shouldFailWith` err
-            0
-            (ueof <> elabel "end of line")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs eol ""
+        `shouldFailWith` err
+          0
+          (ueof <> elabel "end of line")
 
   describe "tab" $
     checkStrLit "tab" "\t" (pure <$> tab)
@@ -95,9 +95,9 @@
               s = ' ' : s0 ++ s1
           prs space1 s `shouldParse` ()
           prs' space1 s `succeedsLeaving` s1
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs space1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs space1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
 
   describe "hspace1" $ do
     context "when stream does not start with a space character" $
@@ -115,9 +115,9 @@
               s = ' ' : s0 ++ s1
           prs hspace1 s `shouldParse` ()
           prs' hspace1 s `succeedsLeaving` s1
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs hspace1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs hspace1 "" `shouldFailWith` err 0 (ueof <> elabel "white space")
 
   describe "controlChar" $
     checkCharPred "control character" isControl controlChar
@@ -181,9 +181,9 @@
           prs latin1Char "б"
             `shouldFailWith` err 0 (utok 'б' <> elabel "Latin-1 character")
           prs' latin1Char "в" `failsLeaving` "в"
-    context "when stream is empty" $
-      it "signals correct parse error" $
-        prs latin1Char "" `shouldFailWith` err 0 (ueof <> elabel "Latin-1 character")
+    context "when stream is empty"
+      $ it "signals correct parse error"
+      $ prs latin1Char "" `shouldFailWith` err 0 (ueof <> elabel "Latin-1 character")
 
   describe "charCategory" $ do
     context "when parser corresponding to general category of next char is used" $
@@ -318,9 +318,9 @@
               us = take (length ts) s'
           prs p s' `shouldFailWith` err 0 (utoks us <> etoks ts)
           prs' p s' `failsLeaving` s'
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> etoks ts)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> etoks ts)
 
 checkCharPred :: String -> (Char -> Bool) -> Parser Char -> SpecWith ()
 checkCharPred name f p = do
@@ -338,9 +338,9 @@
           let s' = ch : s
           prs p s' `shouldFailWith` err 0 (utok ch <> elabel name)
           prs' p s' `failsLeaving` s'
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
 
 checkCharRange :: String -> String -> Parser Char -> SpecWith ()
 checkCharRange name tchs p = do
@@ -351,9 +351,9 @@
           let s' = tch : s
           prs p s' `shouldParse` tch
           prs' p s' `succeedsLeaving` s
-  context "when stream is empty" $
-    it "signals correct parse error" $
-      prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
+  context "when stream is empty"
+    $ it "signals correct parse error"
+    $ prs p "" `shouldFailWith` err 0 (ueof <> elabel name)
 
 -- | Randomly change the case in the given string.
 fuzzyCase :: String -> Gen String
diff --git a/tests/Text/Megaparsec/ErrorSpec.hs b/tests/Text/Megaparsec/ErrorSpec.hs
--- a/tests/Text/Megaparsec/ErrorSpec.hs
+++ b/tests/Text/Megaparsec/ErrorSpec.hs
@@ -3,6 +3,7 @@
 module Text.Megaparsec.ErrorSpec (spec) where
 
 import Control.Exception (Exception (..))
+import qualified Data.ByteString as B
 import Data.Functor.Identity
 import Data.List (isInfixOf, isSuffixOf, sort)
 import Data.List.NonEmpty (NonEmpty (..))
@@ -106,6 +107,21 @@
           pe = err 4 (utok 'd' <> etok 'x') :: PE
       mkBundlePE s pe
         `shouldBe` "1:4:\n  |\n1 | abc\SOHdef\SOHghi\n  |    ^\nunexpected 'd'\nexpecting 'x'\n"
+    it "expands tabs after wide characters to the same width as the column count" $ do
+      let s = "日本\tx" :: String
+          pe = err 3 (utok 'x' <> etok 'd') :: PE
+      mkBundlePE s pe
+        `shouldBe` "1:9:\n  |\n1 | 日本    x\n  |         ^\nunexpected 'x'\nexpecting 'd'\n"
+    it "expands tabs after zero-width characters to the same width as the column count" $ do
+      let s = "\SOH\SOH\tx" :: String
+          pe = err 3 (utok 'x' <> etok 'd') :: PE
+      mkBundlePE s pe
+        `shouldBe` "1:9:\n  |\n1 | \SOH\SOH        x\n  |         ^\nunexpected 'x'\nexpecting 'd'\n"
+    it "shows position marker for byte streams with zero-width bytes" $ do
+      let s = B.pack (replicate 8 0xad ++ [0x78]) -- soft hyphens and 'x'
+          pe = err 8 (utok 0x78 <> etok 0x64) :: ParseError B.ByteString Void
+      mkBundlePE s pe
+        `shouldBe` "1:1:\n  |\n1 | \173\173\173\173\173\173\173\173x\n  | ^\nunexpected 'x'\nexpecting 'd'\n"
     it "shows position marker for newline errors at end of line" $ do
       let s = "abc\n" :: String
           pe = err 3 (utok '\n' <> elabel "end of line") :: PE
@@ -142,6 +158,10 @@
               pe = err 3 (ueof <> etok 'x') :: PE
           mkBundlePE s pe
             `shouldBe` "1:1:\n  |\n1 | <empty line>\n  | ^\nunexpected end of input\nexpecting 'x'\n"
+    -- In these cases the offending line does not include the part of the
+    -- line that precedes the starting column, so it is shorter than the
+    -- reported column suggests and the marker ends up past its end. That is
+    -- still more helpful than omitting the marker.
     context "starting column in bundle is greater than 1" $ do
       context "and less than parse error column" $
         it "is rendered correctly" $
@@ -161,7 +181,7 @@
                           }
                     }
             errorBundlePretty bundle
-              `shouldBe` "1:6:\n  |\n1 | foo\n  | \nunexpected 'o'\nexpecting 'x'\n"
+              `shouldBe` "1:6:\n  |\n1 | foo\n  |      ^\nunexpected 'o'\nexpecting 'x'\n"
       context "and greater than parse error column" $
         it "is rendered correctly" $
           do
@@ -180,7 +200,7 @@
                           }
                     }
             errorBundlePretty bundle
-              `shouldBe` "1:10:\n  |\n1 | foo\n  | \nunexpected 'o'\nexpecting 'x'\n"
+              `shouldBe` "1:10:\n  |\n1 | foo\n  |          ^\nunexpected 'o'\nexpecting 'x'\n"
     it "takes tab width into account correctly" $
       property $ \w' i' -> do
         let w = unPos w'
diff --git a/tests/Text/Megaparsec/StreamSpec.hs b/tests/Text/Megaparsec/StreamSpec.hs
--- a/tests/Text/Megaparsec/StreamSpec.hs
+++ b/tests/Text/Megaparsec/StreamSpec.hs
@@ -20,6 +20,7 @@
 import Test.Hspec.Megaparsec.AdHoc
 import Test.QuickCheck
 import Text.Megaparsec
+import qualified Text.Megaparsec.Unicode as Unicode
 
 spec :: Spec
 spec = do
@@ -45,9 +46,9 @@
         property $ \chk ->
           chunkEmpty sproxy chk === (chunkLength sproxy chk <= 0)
     describe "take1_" $ do
-      context "when input in empty" $
-        it "returns Nothing" $
-          take1_ ("" :: String) === Nothing
+      context "when input in empty"
+        $ it "returns Nothing"
+        $ take1_ ("" :: String) === Nothing
       context "when input is not empty" $
         it "unconses a token" $
           property $ \s ->
@@ -98,9 +99,9 @@
         property $ \chk ->
           chunkEmpty bproxy chk === (chunkLength bproxy chk <= 0)
     describe "take1_" $ do
-      context "when input in empty" $
-        it "returns Nothing" $
-          take1_ ("" :: B.ByteString) === Nothing
+      context "when input in empty"
+        $ it "returns Nothing"
+        $ take1_ ("" :: B.ByteString) === Nothing
       context "when input is not empty" $
         it "unconses a token" $
           property $ \s ->
@@ -129,6 +130,20 @@
     describeShowTokens bproxy quotedWordGen
     describeReachOffset bproxy
     describeReachOffsetNoLine bproxy
+    describe "column calculation" $
+      it "advances columns by the display width of the byte as Latin-1" $
+        forM_ (filter (`notElem` [0x09, 0x0a]) [minBound .. maxBound]) $ \w -> do
+          let pst =
+                PosState
+                  { pstateInput = B.singleton w,
+                    pstateOffset = 0,
+                    pstateSourcePos = initialPos "",
+                    pstateTabWidth = defaultTabWidth,
+                    pstateLinePrefix = ""
+                  }
+              c = sourceColumn (pstateSourcePos (reachOffsetNoLine 1 pst))
+          (w, unPos c)
+            `shouldBe` (w, 1 + Unicode.charLength (chr (fromIntegral w)))
 
   describe "Lazy ByteString instance of Stream" $ do
     describe "tokenToChunk" $
@@ -152,9 +167,9 @@
         property $ \chk ->
           chunkEmpty blproxy chk === (chunkLength blproxy chk <= 0)
     describe "take1_" $ do
-      context "when input in empty" $
-        it "returns Nothing" $
-          take1_ ("" :: BL.ByteString) === Nothing
+      context "when input in empty"
+        $ it "returns Nothing"
+        $ take1_ ("" :: BL.ByteString) === Nothing
       context "when input is not empty" $
         it "unconses a token" $
           property $ \s ->
@@ -206,9 +221,9 @@
         property $ \chk ->
           chunkEmpty tproxy chk === (chunkLength tproxy chk <= 0)
     describe "take1_" $ do
-      context "when input in empty" $
-        it "returns Nothing" $
-          take1_ ("" :: T.Text) === Nothing
+      context "when input in empty"
+        $ it "returns Nothing"
+        $ take1_ ("" :: T.Text) === Nothing
       context "when input is not empty" $
         it "unconses a token" $
           property $ \s ->
@@ -259,9 +274,9 @@
         property $ \chk ->
           chunkEmpty tlproxy chk === (chunkLength tlproxy chk <= 0)
     describe "take1_" $ do
-      context "when input in empty" $
-        it "returns Nothing" $
-          take1_ ("" :: TL.Text) === Nothing
+      context "when input in empty"
+        $ it "returns Nothing"
+        $ take1_ ("" :: TL.Text) === Nothing
       context "when input is not empty" $
         it "unconses a token" $
           property $ \s ->
@@ -468,6 +483,16 @@
             SourcePos n l c = pstateSourcePos pst
             w = pstateTabWidth pst
         r `shouldBe` SourcePos n l (toNextTab w c)
+    it "returns correct SourcePos (zero-width)" $
+      property $ \pst' -> do
+        let pst =
+              (pst' :: PosState s)
+                { pstateInput = "\173" :: s -- soft hyphen
+                }
+            o = pstateOffset pst + 1
+            r = pstateSourcePos . snd $ reachOffset o pst
+            SourcePos n l c = pstateSourcePos pst
+        r `shouldBe` SourcePos n l c
     it "returns correct SourcePos (other)" $
       property $ \pst' -> do
         let pst =
@@ -563,6 +588,16 @@
             SourcePos n l c = pstateSourcePos pst
             w = pstateTabWidth pst
         r `shouldBe` SourcePos n l (toNextTab w c)
+    it "returns correct SourcePos (zero-width)" $
+      property $ \pst' -> do
+        let pst =
+              (pst' :: PosState s)
+                { pstateInput = "\173" :: s -- soft hyphen
+                }
+            o = pstateOffset pst + 1
+            r = pstateSourcePos (reachOffsetNoLine o pst)
+            SourcePos n l c = pstateSourcePos pst
+        r `shouldBe` SourcePos n l c
     it "returns correct SourcePos (other)" $
       property $ \pst' -> do
         let pst =
diff --git a/tests/Text/Megaparsec/UnicodeSpec.hs b/tests/Text/Megaparsec/UnicodeSpec.hs
--- a/tests/Text/Megaparsec/UnicodeSpec.hs
+++ b/tests/Text/Megaparsec/UnicodeSpec.hs
@@ -1,20 +1,104 @@
 module Text.Megaparsec.UnicodeSpec (spec) where
 
+import Control.Monad (forM_)
 import Test.Hspec
 import qualified Text.Megaparsec.Unicode as Unicode
 
 spec :: Spec
 spec = do
-  describe "stringLength" $
+  describe "stringLength" $ do
     it "computes correct length in the presence of wide chars" $
       Unicode.stringLength "123 구구 이면" `shouldBe` 13
+    it "computes correct length in the presence of zero-width chars" $
+      -- A letter, a combining acute accent that occupies no cell of its
+      -- own, and a wide ideograph.
+      Unicode.stringLength "e\769日" `shouldBe` 3
   describe "charLength" $ do
-    it "returns 1 for non-wide chars" $
-      Unicode.charLength 'a' `shouldBe` 1
+    it "returns 1 for ordinary chars" $
+      expectLengths 1 narrowChars
     it "returns 2 for wide chars" $
-      Unicode.charLength '구' `shouldBe` 2
+      expectLengths 2 wideChars
+    it "returns 0 for zero-width chars" $
+      expectLengths 0 zeroWidthChars
+    it "agrees with isWideChar and isZeroWidthChar for every code point" $
+      -- Among other things this pins down the shortcut that both functions
+      -- take for the characters below the top of the Latin-1 range.
+      let inconsistent ch =
+            let n = Unicode.charLength ch
+             in n `notElem` [0, 1, 2]
+                  || Unicode.isWideChar ch /= (n == 2)
+                  || Unicode.isZeroWidthChar ch /= (n == 0)
+       in take 5 (filter inconsistent [minBound .. maxBound]) `shouldBe` []
   describe "isWideChar" $ do
     it "returns False for non-wide chars" $
       Unicode.isWideChar 'a' `shouldBe` False
     it "returns True for wide chars" $
       Unicode.isWideChar '구' `shouldBe` True
+    it "returns False for wide chars that combine with their neighbour" $
+      -- U+302A is Wide by East Asian Width but it is a non-spacing mark, so
+      -- it does not occupy two columns of its own.
+      Unicode.isWideChar '\12330' `shouldBe` False
+  describe "isZeroWidthChar" $ do
+    it "returns False for ordinary chars" $
+      Unicode.isZeroWidthChar 'a' `shouldBe` False
+    it "returns True for control chars" $
+      Unicode.isZeroWidthChar '\SOH' `shouldBe` True
+    it "returns True for combining marks" $
+      Unicode.isZeroWidthChar '\769' `shouldBe` True
+
+-- | Check that every character in the collection has the given length,
+-- naming the offending one if it does not.
+expectLengths :: Int -> [(String, Char)] -> Expectation
+expectLengths n cs =
+  forM_ cs $ \(name, ch) ->
+    (name, Unicode.charLength ch) `shouldBe` (name, n)
+
+-- | Characters that occupy a single column.
+narrowChars :: [(String, Char)]
+narrowChars =
+  [ ("U+0061 latin small letter a", 'a'),
+    ("U+00E9 latin small letter e with acute", '\233'),
+    ("U+0416 cyrillic capital letter zhe", '\1046'),
+    ("U+05D0 hebrew letter alef", '\1488'),
+    ("U+0E01 thai character ko kai", '\3585'),
+    ("U+2192 rightwards arrow", '\8594')
+  ]
+
+-- | Characters that occupy two columns. The comments give the version of
+-- the Unicode standard that assigned them, which is what makes them a
+-- reasonable regression test for stale character width data.
+wideChars :: [(String, Char)]
+wideChars =
+  [ ("U+4E00 cjk unified ideograph (1.1)", '\19968'),
+    ("U+9FFF cjk unified ideograph (14.0)", '\40959'),
+    ("U+4DBF cjk unified ideograph (13.0)", '\19903'),
+    ("U+31BF bopomofo letter (14.0)", '\12735'),
+    ("U+AC00 hangul syllable ga", '\44032'),
+    ("U+FF21 fullwidth latin capital letter a", '\65313'),
+    ("U+1F600 grinning face (6.1)", '\128512'),
+    ("U+1F6DD playground slide (14.0)", '\128733'),
+    ("U+1FAE0 melting face (14.0)", '\129760'),
+    ("U+30000 cjk unified ideograph extension g (13.0)", '\196608'),
+    ("U+31350 cjk unified ideograph extension h (15.0)", '\201552')
+  ]
+
+-- | Characters that occupy no columns at all.
+zeroWidthChars :: [(String, Char)]
+zeroWidthChars =
+  [ ("U+0001 start of heading", '\SOH'),
+    ("U+00AD soft hyphen", '\173'),
+    ("U+0301 combining acute accent", '\769'),
+    ("U+0651 arabic shadda", '\1617'),
+    ("U+093C devanagari sign nukta", '\2364'),
+    ("U+0BCD tamil sign virama", '\3021'),
+    ("U+0E31 thai character mai han akat", '\3633'),
+    ("U+1AB0 combining doubled circumflex accent", '\6832'),
+    ("U+1DC0 combining dotted grave accent", '\7616'),
+    ("U+200D zero width joiner", '\8205'),
+    ("U+2060 word joiner", '\8288'),
+    ("U+20E3 combining enclosing keycap", '\8419'),
+    ("U+FE0F variation selector-16", '\65039'),
+    ("U+FEFF zero width no-break space", '\65279'),
+    ("U+11FF hangul jongseong ssangnieun", '\4607'),
+    ("U+E0101 variation selector-18", '\917761')
+  ]
diff --git a/tests/Text/MegaparsecSpec.hs b/tests/Text/MegaparsecSpec.hs
--- a/tests/Text/MegaparsecSpec.hs
+++ b/tests/Text/MegaparsecSpec.hs
@@ -222,12 +222,35 @@
             pRightAssociative = (a <|> (b <|> c)) >> char 'd'
             e = err 0 (utok 'a' <> etok 'd' <> etok 'b')
             s = "aaa"
-        context "left associative" $
-          it "fails with the right error" $
-            prs pLeftAssociative s `shouldFailWith` e
-        context "right associative" $
-          it "fails with the right error" $
-            prs pRightAssociative s `shouldFailWith` e
+        context "left associative"
+          $ it "fails with the right error"
+          $ prs pLeftAssociative s `shouldFailWith` e
+        context "right associative"
+          $ it "fails with the right error"
+          $ prs pRightAssociative s `shouldFailWith` e
+      context "when a branch fails ahead of the alternation due to try" $ do
+        context "when the other branch fails where the alternation started" $
+          it "reports the error there keeping its expected items" $ do
+            let p = try (char 'a' >> char 'b') <|> char 'c'
+            prs p "ad" `shouldFailWith` err 0 (utok 'a' <> etok 'c')
+            prs' p "ad" `failsLeaving` "ad"
+        context "when both branches fail ahead at the same offset" $
+          it "prefers the longest match" $ do
+            let p = try (char 'a' >> char 'b') <|> try (char 'a' >> char 'c')
+            prs p "ad" `shouldFailWith` err 1 (utok 'd' <> etok 'b' <> etok 'c')
+        context "when both branches fail ahead at different offsets" $
+          it "prefers the longest match" $ do
+            let p =
+                  try (char 'a' >> char 'b')
+                    <|> try (char 'a' >> char 'd' >> char 'e')
+            prs p "adx" `shouldFailWith` err 2 (utok 'x' <> etok 'e')
+      it "is associative no matter where the branches fail" $
+        property $ \b1 b2 b3 -> do
+          let p = altBranch b1
+              q = altBranch b2
+              r = altBranch b3
+          prs ((p <|> q) <|> r) altBranchInput
+            `shouldBe` prs (p <|> (q <|> r)) altBranchInput
       it "associativity of fold over alternatives should not matter" $ do
         let p = asum [empty, string ">>>", empty, return "foo"] <?> "bar"
             p' = bsum [empty, string ">>>", empty, return "foo"] <?> "bar"
@@ -922,9 +945,9 @@
               grs' p s (`failsLeaving` (b : as))
 
     describe "eof" $ do
-      context "when input stream is empty" $
-        it "succeeds" $
-          grs eof "" (`shouldParse` ())
+      context "when input stream is empty"
+        $ it "succeeds"
+        $ grs eof "" (`shouldParse` ())
       context "when input stream is not empty" $
         it "signals correct error message" $
           property $ \a as -> do
@@ -1106,6 +1129,11 @@
                     p = takeP Nothing 0
                 grs p s (`shouldParse` "")
                 grs' p s (`succeedsLeaving` s)
+        it "does not consume input, so the next alternative is tried" $
+          property $ \s -> do
+            let p :: (MonadParsec Void String m) => m String
+                p = (takeP Nothing 0 *> empty) <|> pure "alt"
+            grs p s (`shouldParse` "alt")
       context "when taking <0 tokens" $ do
         context "when stream is empty" $
           it "succeeds returning zero-length chunk" $
@@ -1121,6 +1149,11 @@
                     p = takeP Nothing n
                 grs p s (`shouldParse` "")
                 grs' p s (`succeedsLeaving` s)
+        it "does not consume input, so the next alternative is tried" $
+          property $ \(Negative n) s -> do
+            let p :: (MonadParsec Void String m) => m String
+                p = (takeP Nothing n *> empty) <|> pure "alt"
+            grs p s (`shouldParse` "alt")
       context "when taking >0 tokens" $ do
         context "when stream is empty" $ do
           context "with label" $
@@ -1157,6 +1190,12 @@
                     (s0, s1) = splitAt n s
                 grs p s (`shouldParse` s0)
                 grs' p s (`succeedsLeaving` s1)
+        it "consumes input, so the next alternative is not tried" $
+          property $ \(Positive n) s ->
+            length s >= n ==> do
+              let p :: (MonadParsec Void String m) => m String
+                  p = (takeP Nothing n *> empty) <|> pure "alt"
+              grs p s (`shouldFailWith` err n mempty)
       context "when failing right after takeP (testing hints)" $
         it "there are no hints to influence the parse error" $
           property $ \(Positive n) s ->
@@ -1287,9 +1326,9 @@
             let s' = ch : s
             grs p s' (`shouldParse` ch)
             grs' p s' (`succeedsLeaving` s)
-      context "when stream is empty" $
-        it "signals correct parse error" $
-          grs p "" (`shouldFailWith` err 0 ueof)
+      context "when stream is empty"
+        $ it "signals correct parse error"
+        $ grs p "" (`shouldFailWith` err 0 ueof)
 
     describe "anySingleBut" $ do
       context "when stream begins with the character specified as argument" $
@@ -1308,9 +1347,9 @@
                   p = anySingleBut ch
               grs p s (`shouldParse` head s)
               grs' p s (`succeedsLeaving` tail s)
-      context "when stream is empty" $
-        it "signals correct parse error" $
-          grs (anySingleBut 'a') "" (`shouldFailWith` err 0 ueof)
+      context "when stream is empty"
+        $ it "signals correct parse error"
+        $ grs (anySingleBut 'a') "" (`shouldFailWith` err 0 ueof)
 
     describe "oneOf" $ do
       context "when stream begins with one of specified characters" $
@@ -1839,6 +1878,36 @@
   s ->
   Bool
 eqParser p1 p2 s = runParser p1 "" s == runParser p2 "" s
+
+-- | A description of a branch of an alternation, used for testing the
+-- associativity of ('<|>'). Every branch fails on 'altBranchInput', but
+-- depending on the constructor it fails either exactly where the
+-- alternation started or ahead of that position, which is the interesting
+-- case because it can only happen due to 'try'.
+data AltBranch
+  = -- | Fail without consuming input
+    AltAt Char
+  | -- | Fail one token ahead, but backtrack
+    AltAhead Char
+  deriving (Eq, Show)
+
+instance Arbitrary AltBranch where
+  arbitrary = do
+    f <- elements [AltAt, AltAhead]
+    -- Neither 'a' nor 'd' so that the branch is guaranteed to fail on
+    -- 'altBranchInput'.
+    f <$> elements "bcexyz"
+
+-- | The input on which the parsers produced by 'altBranch' are supposed to
+-- be run.
+altBranchInput :: String
+altBranchInput = "ad"
+
+-- | Turn an 'AltBranch' into a parser that fails on 'altBranchInput'.
+altBranch :: AltBranch -> Parser Char
+altBranch = \case
+  AltAt c -> char c
+  AltAhead c -> try (char 'a' >> char c)
 
 mkBundle ::
   State s e ->
